import Foundation
import VoiceCatCore

struct PendingIdentity: Identifiable {
    let id = UUID()
    let displayText: String
    let tofuStatus: VoiceCatTofuStatus
}

@Observable
@MainActor
final class AppState {
    var servers: [SavedServer] = ServerListStore.shared.load()
    var session: SessionState?

    // Connect-flow state
    var isConnecting = false
    var connectStatus = ""
    var showAddServer = false
    var editingServer: SavedServer?
    var showPasswordPrompt = false
    var pendingIdentity: PendingIdentity?

    private var connectingClient: VoiceCatClient?
    private(set) var connectingServer: SavedServer?
    private var identityHandled = false

    // MARK: - Server list management

    func addServer(_ server: SavedServer, password: String?) {
        if let pw = password, !pw.isEmpty {
            ServerListStore.shared.savePassword(pw, tag: server.keychainTag)
        }
        servers.append(server)
        ServerListStore.shared.save(servers)
    }

    func updateServer(_ server: SavedServer, password: String?) {
        if let pw = password, !pw.isEmpty {
            ServerListStore.shared.savePassword(pw, tag: server.keychainTag)
        }
        if let idx = servers.firstIndex(where: { $0.id == server.id }) {
            servers[idx] = server
        }
        ServerListStore.shared.save(servers)
    }

    func removeServer(_ server: SavedServer) {
        ServerListStore.shared.deletePassword(tag: server.keychainTag)
        servers.removeAll(where: { $0.id == server.id })
        ServerListStore.shared.save(servers)
    }

    // MARK: - Connect flow

    func connectTo(_ server: SavedServer) {
        guard !isConnecting else { return }
        isConnecting = true
        connectStatus = "Connecting…"
        connectingServer = server
        identityHandled = false

        let config = VoiceCatConfig(
            clientName: "VoiceCat-iOS",
            clientVersion: "0.0.1",
            logLevel: .info,
            tofuStorePath: ServerListStore.shared.tofuStorePath)
        let client = VoiceCatClient(config: config)
        connectingClient = client

        client.onEvent = { [weak self] ev in
            Task { @MainActor [weak self] in self?.handleConnectEvent(ev, server: server) }
        }
        // Put the core into external-playback mode BEFORE connect, so the flag is set on the
        // io thread before any message is processed. The server sends AuthResult immediately
        // followed by ServerStateSnapshot; handle_server_state runs ensure_audio_running() on
        // the io thread, and if external_playback_ were still false at that point the core
        // would open a hardware miniaudio playback+capture device (see the matching fix in
        // vc_client::ensure_audio_running). Setting it here — before connect — guarantees the
        // unified external path is in effect from the first frame. setExternalPlayback only
        // flips an atomic + forwards to the engine's setter; both are safe pre-connect.
        client.setExternalPlayback(true)
        client.connect(host: server.host, port: server.port)

        // Auth is queued immediately — the core serialises it behind TLS + TOFU.
        switch server.authMode {
        case .guest:
            let nick = (server.nickname?.isEmpty == false) ? server.nickname! : "iOS User"
            client.authenticateGuest(nick)
        case .password:
            let savedPw = ServerListStore.shared.loadPassword(tag: server.keychainTag)
            if let pw = savedPw, !pw.isEmpty {
                client.authenticateUser(server.savedUsername, password: pw)
            } else {
                showPasswordPrompt = true
            }
        }
    }

    func disconnect() {
        session?.stopMicStream()
        session?.client.disconnect()
        IOSAudioEngine.shared.stop()
        AudioSessionManager.shared.deactivateSession()
        session = nil
        connectingClient?.disconnect()
        connectingClient = nil
        connectingServer = nil
        isConnecting = false
        connectStatus = ""
        showPasswordPrompt = false
        pendingIdentity = nil
    }

    // MARK: - Auth actions (called from prompt sheets)

    func authenticateUser(username: String, password: String) {
        connectingClient?.authenticateUser(username, password: password)
        showPasswordPrompt = false
    }

    func confirmServerIdentity(accept: Bool) {
        connectingClient?.confirmServerIdentity(accept: accept)
        pendingIdentity = nil
        if !accept { cancelConnect() }
    }

    func cancelConnect() {
        connectingClient?.disconnect()
        connectingClient = nil
        connectingServer = nil
        isConnecting = false
        connectStatus = ""
        showPasswordPrompt = false
        pendingIdentity = nil
    }

    // MARK: - Connect event handler

    private func handleConnectEvent(_ ev: VoiceCatEvent, server: SavedServer) {
        switch ev.type {
        case .connectionState:
            switch ev.connectionState {
            case .connecting:        connectStatus = "Connecting…"
            case .tlsHandshake:      connectStatus = "TLS handshake…"
            case .authenticating:    connectStatus = "Authenticating…"
            case .verifyingIdentity: connectStatus = "Verifying server identity…"
            case .connected:         connectStatus = "Connected"
            default: break
            }
        case .serverIdentity:
            guard !identityHandled else { break }
            let tofuStatus = ev.tofuStatus ?? .firstConnect
            if tofuStatus == .matched {
                connectingClient?.confirmServerIdentity(accept: true)
            } else {
                identityHandled = true
                let displayText = connectingClient?.getServerIdentityDisplay() ?? ""
                pendingIdentity = PendingIdentity(displayText: displayText, tofuStatus: tofuStatus)
            }
        case .authResult:
            if ev.result == .ok {
                guard let client = connectingClient else { break }
                let perms = client.getPermissions()
                let newSession = SessionState(client: client, selfUserId: ev.userId, permissions: perms)
                connectingClient = nil
                isConnecting = false
                connectStatus = ""
                showPasswordPrompt = false
                self.session = newSession
                EventFeedback.shared.play(.login)
                EventFeedback.shared.speak("Connected")
                // External-playback mode was enabled before connect() so the core never opens a
                // miniaudio device on iOS (the single ordering rule of the unified audio path).
                // Now activate the session and start the engine in listening mode so remote audio
                // plays the moment someone talks, even before we join voice (no "can't hear anyone").
                do {
                    try AudioSessionManager.shared.ensureSessionActive()
                } catch {
                    print("Audio session activate on connect failed: \(error)")
                }
                IOSAudioEngine.shared.startListening(client: client)
            } else {
                connectStatus = "Auth failed: \(ev.result.description)"
                showPasswordPrompt = true
            }
        case .disconnected:
            if session == nil { cancelConnect() }
            else {
                IOSAudioEngine.shared.stop()
                AudioSessionManager.shared.deactivateSession()
                session = nil; isConnecting = false
            }
        case .error:
            connectStatus = ev.text ?? "Unknown error"
            if session == nil { isConnecting = false }
        default:
            break
        }
    }
}
