import AppKit
import VoiceCatCore

final class ConnectWindowController: NSWindowController, NSWindowDelegate {

    // MARK: - UI

    private let serverTableView = NSTableView()
    private let serverScrollView = NSScrollView()
    private let addButton = NSButton()
    private let editButton = NSButton()
    private let removeButton = NSButton()
    private let connectButton = NSButton()
    private let statusLabel = NSTextField(labelWithString: "Select a server and click Connect.")

    // MARK: - State

    private var servers: [SavedServer] = ServerListStore.load()
    private var client: VoiceCatClient?
    private var identityDialogShown = false
    private var mainWindowController: MainWindowController?

    // MARK: - Init

    init() {
        let window = NSWindow(
            contentRect: NSRect(x: 0, y: 0, width: 420, height: 320),
            styleMask: [.titled, .closable, .miniaturizable],
            backing: .buffered,
            defer: false
        )
        window.title = "VoiceCat — Connect"
        window.center()
        super.init(window: window)
        window.delegate = self
        buildUI()
        refreshServerList()
    }

    required init?(coder: NSCoder) { fatalError() }

    // MARK: - UI construction

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

        // Server list
        let col = NSTableColumn(identifier: NSUserInterfaceItemIdentifier("server"))
        col.title = "Saved Servers"
        serverTableView.addTableColumn(col)
        serverTableView.headerView = nil
        serverTableView.dataSource = self
        serverTableView.delegate = self
        serverTableView.doubleAction = #selector(connectClicked)
        serverTableView.target = self
        serverTableView.setAccessibilityLabel("Saved servers")

        serverScrollView.documentView = serverTableView
        serverScrollView.hasVerticalScroller = true
        serverScrollView.borderType = .bezelBorder
        serverScrollView.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(serverScrollView)

        // Buttons row
        configureButton(addButton, title: "Add…", action: #selector(addClicked))
        configureButton(editButton, title: "Edit…", action: #selector(editClicked))
        configureButton(removeButton, title: "Remove", action: #selector(removeClicked))

        let buttonStack = NSStackView(views: [addButton, editButton, removeButton, NSView()])
        buttonStack.orientation = .horizontal
        buttonStack.spacing = 8
        buttonStack.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(buttonStack)

        // Status
        statusLabel.translatesAutoresizingMaskIntoConstraints = false
        statusLabel.textColor = .secondaryLabelColor
        statusLabel.setAccessibilityLabel("Connection status")
        contentView.addSubview(statusLabel)

        // Connect button
        connectButton.title = "Connect"
        connectButton.bezelStyle = .rounded
        connectButton.keyEquivalent = "\r"
        connectButton.target = self
        connectButton.action = #selector(connectClicked)
        connectButton.translatesAutoresizingMaskIntoConstraints = false
        connectButton.setAccessibilityLabel("Connect to selected server")
        contentView.addSubview(connectButton)

        NSLayoutConstraint.activate([
            serverScrollView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 12),
            serverScrollView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 12),
            serverScrollView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -12),
            serverScrollView.bottomAnchor.constraint(equalTo: buttonStack.topAnchor, constant: -8),

            buttonStack.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 12),
            buttonStack.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -12),
            buttonStack.bottomAnchor.constraint(equalTo: statusLabel.topAnchor, constant: -12),

            statusLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 12),
            statusLabel.trailingAnchor.constraint(equalTo: connectButton.leadingAnchor, constant: -8),
            statusLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -16),

            connectButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -12),
            connectButton.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -12),
            connectButton.widthAnchor.constraint(equalToConstant: 90),
        ])
    }

    private func configureButton(_ button: NSButton, title: String, action: Selector) {
        button.title = title
        button.bezelStyle = .rounded
        button.target = self
        button.action = action
        button.translatesAutoresizingMaskIntoConstraints = false
    }

    // MARK: - Server list management

    private func refreshServerList() {
        serverTableView.reloadData()
        updateButtonStates()
    }

    private func updateButtonStates() {
        let hasSelection = serverTableView.selectedRow >= 0
        connectButton.isEnabled = hasSelection
        editButton.isEnabled = hasSelection
        removeButton.isEnabled = hasSelection
    }

    @objc private func addClicked() {
        let sheet = AddServerSheet(editing: nil)
        sheet.onComplete = { [weak self] server in
            guard let self, let server else { return }
            self.servers.append(server)
            ServerListStore.save(self.servers)
            self.refreshServerList()
            let newRow = self.servers.count - 1
            self.serverTableView.selectRowIndexes(IndexSet(integer: newRow), byExtendingSelection: false)
        }
        presentSheet(sheet)
    }

    @objc private func editClicked() {
        let row = serverTableView.selectedRow
        guard row >= 0 else { return }
        let existing = servers[row]
        let sheet = AddServerSheet(editing: existing)
        sheet.onComplete = { [weak self] server in
            guard let self, let server else { return }
            self.servers[row] = server
            ServerListStore.save(self.servers)
            self.refreshServerList()
        }
        presentSheet(sheet)
    }

    @objc private func removeClicked() {
        let row = serverTableView.selectedRow
        guard row >= 0 else { return }
        let server = servers[row]
        let alert = NSAlert()
        alert.messageText = "Remove server?"
        alert.informativeText = "Remove '\(server.displayString)' from the saved-server list?"
        alert.addButton(withTitle: "Remove")
        alert.addButton(withTitle: "Cancel")
        alert.alertStyle = .warning
        guard let window else { return }
        alert.beginSheetModal(for: window) { [weak self] response in
            guard response == .alertFirstButtonReturn, let self else { return }
            if let tag = self.servers[row].keychainTag {
                ServerListStore.deletePassword(tag: tag)
            }
            self.servers.remove(at: row)
            ServerListStore.save(self.servers)
            self.refreshServerList()
        }
    }

    // MARK: - Connect flow

    @objc private func connectClicked() {
        let row = serverTableView.selectedRow
        guard row >= 0 else { return }
        startConnect(server: servers[row])
    }

    private func startConnect(server: SavedServer) {
        setBusy(true)
        setStatus("Connecting…")

        try? FileManager.default.createDirectory(atPath: ServerListStore.appSupportURL.path,
                                                  withIntermediateDirectories: true)
        let config = VoiceCatConfig(
            clientName: "VoiceCat-macOS",
            clientVersion: "0.0.1",
            logLevel: .info,
            tofuStorePath: ServerListStore.tofuStorePath
        )
        let newClient = VoiceCatClient(config: config)
        client = newClient
        identityDialogShown = false

        newClient.onEvent = { [weak self] event in
            self?.handleEvent(event, server: server)
        }

        let connectResult = newClient.connect(host: server.host, port: server.port)
        guard connectResult == .ok else {
            setStatus("Connect failed: \(connectResult)")
            cleanupFailedAttempt()
            return
        }

        switch server.authMode {
        case .guest:
            let nick = server.nickname?.isEmpty == false ? server.nickname! : NSFullUserName()
            newClient.authenticateGuest(nick)
        case .password:
            let username = server.savedUsername ?? ""
            if let tag = server.keychainTag, let password = ServerListStore.loadPassword(tag: tag) {
                newClient.authenticateUser(username, password: password)
            } else {
                let pwSheet = PasswordPromptSheet(prompt: "Password for \(username)@\(server.host):")
                pwSheet.onComplete = { [weak self, weak newClient] password in
                    guard let self, let newClient else { return }
                    guard let password else {
                        self.setStatus("Cancelled.")
                        self.cleanupFailedAttempt()
                        return
                    }
                    newClient.authenticateUser(username, password: password)
                }
                presentSheet(pwSheet)
            }
        }
    }

    private func handleEvent(_ event: VoiceCatEvent, server: SavedServer) {
        switch event.type {
        case .connectionState:
            let label: String
            switch event.connectionState {
            case .connecting:        label = "Connecting…"
            case .tlsHandshake:      label = "TLS handshake…"
            case .verifyingIdentity: label = "Verifying server identity…"
            case .authenticating:    label = "Authenticating…"
            case .connected:         label = "Connected."
            default:                 label = statusLabel.stringValue
            }
            setStatus(label)

        case .serverIdentity:
            handleServerIdentity(tofuStatus: event.tofuStatus ?? .firstConnect,
                                 displayText: client?.getServerIdentityDisplay() ?? "")

        case .authResult:
            if event.result == .ok {
                let nickname = server.authMode == .guest
                    ? (server.nickname?.isEmpty == false ? server.nickname! : NSFullUserName())
                    : (server.savedUsername ?? "")
                authSucceeded(client: client!, selfUserId: event.userId, nickname: nickname)
            } else {
                setStatus("Authentication failed: \(event.text ?? event.result.description)")
                cleanupFailedAttempt()
            }

        case .disconnected:
            if client != nil {
                setStatus(event.text.map { "Disconnected: \($0)" } ?? "Disconnected.")
                cleanupFailedAttempt()
            }

        default:
            break
        }
    }

    private func handleServerIdentity(tofuStatus: VoiceCatTofuStatus, displayText: String) {
        if identityDialogShown { return }
        if tofuStatus == .matched {
            client?.confirmServerIdentity(accept: true)
            return
        }
        identityDialogShown = true
        let sheet = ServerIdentitySheet(tofuStatus: tofuStatus, displayFingerprint: displayText)
        sheet.onComplete = { [weak self] accepted in
            self?.client?.confirmServerIdentity(accept: accepted)
            if !accepted {
                self?.setStatus("Server identity rejected.")
                self?.cleanupFailedAttempt()
            }
        }
        presentSheet(sheet)
    }

    private func authSucceeded(client: VoiceCatClient, selfUserId: UInt32, nickname: String) {
        client.onEvent = nil
        let mainWC = MainWindowController(client: client, selfUserId: selfUserId, nickname: nickname)
        self.mainWindowController = mainWC
        mainWC.showWindow(nil)
        self.client = nil
        close()
    }

    private func cleanupFailedAttempt() {
        client?.onEvent = nil
        client = nil
        setBusy(false)
    }

    // MARK: - Helpers

    private func setStatus(_ text: String) {
        statusLabel.stringValue = text
    }

    private func setBusy(_ busy: Bool) {
        serverTableView.isEnabled = !busy
        addButton.isEnabled = !busy
        connectButton.isEnabled = !busy && serverTableView.selectedRow >= 0
        editButton.isEnabled = !busy && serverTableView.selectedRow >= 0
        removeButton.isEnabled = !busy && serverTableView.selectedRow >= 0
    }

    private func presentSheet(_ vc: NSViewController) {
        if let parent = window?.contentViewController {
            parent.presentAsSheet(vc)
        } else {
            let contentVC = NSViewController()
            contentVC.view = window!.contentView!
            window?.contentViewController = contentVC
            contentVC.presentAsSheet(vc)
        }
    }

    // MARK: - NSWindowDelegate

    func windowWillClose(_ notification: Notification) {
        client?.onEvent = nil
        client = nil
    }
}

// MARK: - NSTableViewDataSource / Delegate

extension ConnectWindowController: NSTableViewDataSource, NSTableViewDelegate {
    func numberOfRows(in tableView: NSTableView) -> Int { servers.count }

    func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
        let id = NSUserInterfaceItemIdentifier("serverCell")
        let cell = tableView.makeView(withIdentifier: id, owner: nil) as? NSTableCellView
                   ?? makeTableCellView(identifier: id)
        cell.textField?.stringValue = servers[row].displayString
        return cell
    }

    func tableViewSelectionDidChange(_ notification: Notification) {
        updateButtonStates()
    }

    private func makeTableCellView(identifier: NSUserInterfaceItemIdentifier) -> NSTableCellView {
        let cell = NSTableCellView()
        cell.identifier = identifier
        let tf = NSTextField(labelWithString: "")
        tf.translatesAutoresizingMaskIntoConstraints = false
        cell.addSubview(tf)
        cell.textField = tf
        NSLayoutConstraint.activate([
            tf.leadingAnchor.constraint(equalTo: cell.leadingAnchor, constant: 4),
            tf.trailingAnchor.constraint(equalTo: cell.trailingAnchor, constant: -4),
            tf.centerYAnchor.constraint(equalTo: cell.centerYAnchor),
        ])
        return cell
    }
}

// MARK: - Helper


