import Cocoa
import ServiceManagement
import UserNotifications

class SettingsWindowController: NSWindowController {
    weak var appDelegate: AppDelegate?

    let presets: [(label: String, interval: TimeInterval)] = [
        ("30 minutes", 1800),
        ("1 hour", 3600),
        ("6 hours", 21600),
        ("12 hours", 43200),
        ("24 hours", 86400),
    ]

    var intervalPopup: NSPopUpButton!
    var customMinutesField: NSTextField!
    var launchAtLoginCheckbox: NSButton!
    var greedyCheckbox: NSButton!
    var autoUpgradeFormulaeCheckbox: NSButton!
    var autoUpgradeCasksCheckbox: NSButton!
    var notifyCheckbox: NSButton!
    var showCountCheckbox: NSButton!
    var confirmCheckbox: NSButton!

    convenience init(appDelegate: AppDelegate) {
        let window = NSWindow(
            contentRect: NSRect(x: 0, y: 0, width: 380, height: 200),
            styleMask: [.titled, .closable],
            backing: .buffered,
            defer: false
        )
        window.title = "BrewBar Settings"
        // the controller keeps the window alive; without this, closing the
        // window would deallocate it and reopening would crash
        window.isReleasedWhenClosed = false

        self.init(window: window)
        self.appDelegate = appDelegate

        buildUI()
        syncUI()
        window.center()
    }

    // MARK: - Layout

    func buildUI() {
        guard let contentView = window?.contentView else { return }

        intervalPopup = NSPopUpButton()
        for preset in presets {
            intervalPopup.addItem(withTitle: preset.label)
        }
        intervalPopup.addItem(withTitle: "Custom")
        intervalPopup.target = self
        intervalPopup.action = #selector(presetPicked(_:))

        customMinutesField = NSTextField()
        customMinutesField.placeholderString = "60"
        customMinutesField.alignment = .right
        customMinutesField.target = self
        customMinutesField.action = #selector(customMinutesEntered(_:))
        customMinutesField.widthAnchor.constraint(equalToConstant: 60).isActive = true

        let frequencyRow = NSStackView(views: [
            NSTextField(labelWithString: "Check for updates every:"),
            intervalPopup,
        ])
        frequencyRow.orientation = .horizontal

        let customRow = NSStackView(views: [
            customMinutesField,
            NSTextField(labelWithString: "minutes"),
        ])
        customRow.orientation = .horizontal

        launchAtLoginCheckbox = NSButton(
            checkboxWithTitle: "Launch BrewBar at login",
            target: self,
            action: #selector(toggleLaunchAtLogin(_:))
        )

        greedyCheckbox = NSButton(
            checkboxWithTitle: "Include self-updating casks (--greedy)",
            target: self,
            action: #selector(toggleGreedy(_:))
        )

        let greedyNote = noteRow(
            "Some apps (Chrome, Firefox…) update themselves, so Homebrew normally ignores them. When enabled, BrewBar also lists them as outdated and includes them in Upgrade All and auto-upgrades."
        )

        autoUpgradeFormulaeCheckbox = NSButton(
            checkboxWithTitle: "Automatically upgrade formulae (command-line tools)",
            target: self,
            action: #selector(toggleAutoUpgradeFormulae(_:))
        )

        autoUpgradeCasksCheckbox = NSButton(
            checkboxWithTitle: "Automatically upgrade casks (apps)",
            target: self,
            action: #selector(toggleAutoUpgradeCasks(_:))
        )

        let caskWarningRow = noteRow(
            "⚠️ Upgrading a cask can close and replace the app while it is running (e.g. your browser), without warning."
        )

        notifyCheckbox = NSButton(
            checkboxWithTitle: "Notify when new updates are found",
            target: self,
            action: #selector(toggleNotify(_:))
        )

        showCountCheckbox = NSButton(
            checkboxWithTitle: "Show outdated count in the menu bar",
            target: self,
            action: #selector(toggleShowCount(_:))
        )

        confirmCheckbox = NSButton(
            checkboxWithTitle: "Ask for confirmation before Upgrade All",
            target: self,
            action: #selector(toggleConfirm(_:))
        )

        let stack = NSStackView(views: [
            frequencyRow,
            customRow,
            greedyCheckbox,
            greedyNote,
            separator(),
            autoUpgradeFormulaeCheckbox,
            autoUpgradeCasksCheckbox,
            caskWarningRow,
            separator(),
            notifyCheckbox,
            showCountCheckbox,
            confirmCheckbox,
            launchAtLoginCheckbox,
        ])
        stack.orientation = .vertical
        stack.alignment = .leading
        stack.spacing = 12

        stack.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(stack)
        NSLayoutConstraint.activate([
            stack.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 20),
            stack.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
            stack.trailingAnchor.constraint(lessThanOrEqualTo: contentView.trailingAnchor, constant: -20),
            stack.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -20),
        ])

        contentView.layoutSubtreeIfNeeded()
        window?.setContentSize(contentView.fittingSize)
    }

    /// Push current values into the controls. Called on every open, since
    /// values may have changed while the window was closed.
    func syncUI() {
        guard let appDelegate else { return }

        let interval = appDelegate.refreshInterval
        if let presetIndex = presets.firstIndex(where: { $0.interval == interval }) {
            intervalPopup.selectItem(at: presetIndex)
        } else {
            intervalPopup.selectItem(at: presets.count) // Custom
        }
        customMinutesField.stringValue = "\(Int(interval / 60))"

        // SMAppService is the source of truth — the user may have changed
        // this in System Settings > Login Items while we weren't looking
        launchAtLoginCheckbox.state = SMAppService.mainApp.status == .enabled ? .on : .off
        greedyCheckbox.state = Settings.includeGreedyCasks ? .on : .off
        autoUpgradeFormulaeCheckbox.state = Settings.autoUpgradeFormulae ? .on : .off
        autoUpgradeCasksCheckbox.state = Settings.autoUpgradeCasks ? .on : .off
        notifyCheckbox.state = Settings.notifyOnNewUpdates ? .on : .off
        showCountCheckbox.state = Settings.showCountInMenuBar ? .on : .off
        confirmCheckbox.state = Settings.confirmBeforeUpgradeAll ? .on : .off
    }

    func separator() -> NSBox {
        let box = NSBox()
        box.boxType = .separator
        return box
    }

    /// Small gray explanatory text, indented to read as belonging to the
    /// checkbox above it.
    func noteRow(_ text: String) -> NSStackView {
        let label = NSTextField(wrappingLabelWithString: text)
        label.font = .systemFont(ofSize: NSFont.smallSystemFontSize)
        label.textColor = .secondaryLabelColor
        label.preferredMaxLayoutWidth = 320

        let spacer = NSView()
        spacer.widthAnchor.constraint(equalToConstant: 18).isActive = true

        let row = NSStackView(views: [spacer, label])
        row.orientation = .horizontal
        return row
    }

    // MARK: - Actions

    @objc func presetPicked(_ sender: NSPopUpButton) {
        guard sender.indexOfSelectedItem < presets.count else {
            window?.makeFirstResponder(customMinutesField)
            return
        }
        applyInterval(presets[sender.indexOfSelectedItem].interval)
    }

    @objc func customMinutesEntered(_ sender: NSTextField) {
        guard let minutes = Int(sender.stringValue), minutes > 0 else {
            syncUI() // reject invalid input by restoring the current value
            return
        }
        applyInterval(TimeInterval(minutes * 60))
    }

    func applyInterval(_ interval: TimeInterval) {
        appDelegate?.refreshInterval = interval
        appDelegate?.restartTimer()
        syncUI()
    }

    @objc func toggleGreedy(_ sender: NSButton) {
        Settings.includeGreedyCasks = sender.state == .on
        appDelegate?.fetchOutdated() // the list contents just changed meaning
    }

    @objc func toggleAutoUpgradeFormulae(_ sender: NSButton) {
        Settings.autoUpgradeFormulae = sender.state == .on
    }

    @objc func toggleAutoUpgradeCasks(_ sender: NSButton) {
        Settings.autoUpgradeCasks = sender.state == .on
    }

    @objc func toggleNotify(_ sender: NSButton) {
        guard sender.state == .on else {
            Settings.notifyOnNewUpdates = false
            return
        }

        UNUserNotificationCenter.current().requestAuthorization(options: [.alert]) { granted, _ in
            // callback arrives on a background queue; UI needs main
            DispatchQueue.main.async {
                Settings.notifyOnNewUpdates = granted
                sender.state = granted ? .on : .off
            }
        }
    }

    @objc func toggleShowCount(_ sender: NSButton) {
        Settings.showCountInMenuBar = sender.state == .on
        appDelegate?.updateMenuBarCount() // reflect immediately, no refetch needed
    }

    @objc func toggleConfirm(_ sender: NSButton) {
        Settings.confirmBeforeUpgradeAll = sender.state == .on
    }

    @objc func toggleLaunchAtLogin(_ sender: NSButton) {
        do {
            if sender.state == .on {
                try SMAppService.mainApp.register()
            } else {
                try SMAppService.mainApp.unregister()
            }
        } catch {
            sender.state = sender.state == .on ? .off : .on // revert on failure

            let alert = NSAlert()
            alert.alertStyle = .warning
            alert.messageText = "Could not change login item"
            alert.informativeText = error.localizedDescription
            alert.runModal()
        }
    }
}
