import Foundation
import Network
import os

@MainActor
@Observable
final class HostServer {
    var isHosting = false
    var connectedRemoteName: String?
    private(set) var actualPort: UInt16?

    private let dbPath: String
    private var listener: NWListener?
    private var commandTransport: NDJSONTransport?
    private var commandConnection: NWConnection?
    private var player: PlayerViewModel?
    private var db: DatabaseService?
    private var stateTimer: Timer?

    private let logger = RemoteLogger.host

    init(dbPath: String) {
        self.dbPath = dbPath
    }

    /// Configure the server with a player and database for command dispatch.
    /// Pass `nil` for either if not needed (e.g. DB-only serving without a player).
    func configure(player: PlayerViewModel?, db: DatabaseService?) {
        self.player = player
        self.db = db
    }

    /// Start the Bonjour listener on a random TCP port.
    func start() throws {
        let params = NWParameters.tcp
        let listener = try NWListener(using: params)
        listener.service = NWListener.Service(type: "_musicremote._tcp")

        listener.stateUpdateHandler = { [weak self] state in
            Task { @MainActor [weak self] in
                guard let self else { return }
                switch state {
                case .ready:
                    if let port = listener.port?.rawValue {
                        self.actualPort = port
                        self.logger.info("Listener ready on port \(port)")
                    }
                    self.isHosting = true
                case .failed(let error):
                    self.logger.error("Listener failed: \(error.localizedDescription)")
                    self.isHosting = false
                case .cancelled:
                    self.isHosting = false
                default:
                    break
                }
            }
        }

        listener.newConnectionHandler = { [weak self] connection in
            Task { @MainActor [weak self] in
                self?.handleNewConnection(connection)
            }
        }

        listener.start(queue: .main)
        self.listener = listener

        // Start periodic playback-state timer
        stateTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
            Task { @MainActor [weak self] in
                self?.sendPlaybackState()
            }
        }
    }

    /// Stop the server, close all connections, and remove the Bonjour advertisement.
    func stop() {
        stateTimer?.invalidate()
        stateTimer = nil
        commandTransport?.close()
        commandTransport = nil
        commandConnection?.cancel()
        commandConnection = nil
        connectedRemoteName = nil
        listener?.cancel()
        listener = nil
        actualPort = nil
        isHosting = false
    }

    // MARK: - Connection Handling

    /// Receive the first chunk of data from a new connection to determine the HTTP route.
    private func handleNewConnection(_ connection: NWConnection) {
        connection.stateUpdateHandler = { [weak self] state in
            Task { @MainActor [weak self] in
                if case .failed(let error) = state {
                    self?.logger.error("Connection failed: \(error.localizedDescription)")
                }
            }
        }

        connection.start(queue: .main)

        // Read the initial HTTP request line to determine the route
        connection.receive(minimumIncompleteLength: 1, maximumLength: 65_536) { [weak self] data, _, _, error in
            Task { @MainActor [weak self] in
                guard let self else { return }

                if let error {
                    self.logger.error("Failed to read request: \(error.localizedDescription)")
                    connection.cancel()
                    return
                }

                guard let data, let request = String(data: data, encoding: .utf8) else {
                    connection.cancel()
                    return
                }

                self.routeRequest(request, on: connection)
            }
        }
    }

    /// Parse the HTTP request line and dispatch to the appropriate handler.
    private func routeRequest(_ request: String, on connection: NWConnection) {
        let firstLine = request.split(separator: "\r\n").first.map(String.init) ?? request
        logger.info("Request: \(firstLine)")

        if firstLine.hasPrefix("GET /db") {
            handleDBRequest(on: connection)
        } else if firstLine.hasPrefix("GET /cmd") {
            handleCommandRequest(on: connection)
        } else {
            sendHTTP(status: "404 Not Found", body: Data("Not Found".utf8), on: connection, close: true)
        }
    }

    // MARK: - GET /db

    /// Serve the SQLite database as an HTTP response.
    /// Uses SQLite's backup API to produce a self-contained copy that includes
    /// all WAL data, avoiding races with concurrent writers.
    private func handleDBRequest(on connection: NWConnection) {
        do {
            let data: Data
            if let db {
                // Create a temporary copy via the backup API so the served file
                // is self-contained (no WAL/SHM dependency) and consistent.
                let tempURL = FileManager.default.temporaryDirectory
                    .appendingPathComponent(UUID().uuidString + ".sqlite")
                defer { try? FileManager.default.removeItem(at: tempURL) }
                try db.backup(to: tempURL.path)
                data = try Data(contentsOf: tempURL)
            } else {
                // Fallback: serve the raw file when no DatabaseService is configured
                data = try Data(contentsOf: URL(fileURLWithPath: dbPath))
            }
            logger.info("Serving database (\(data.count) bytes)")
            sendHTTP(
                status: "200 OK",
                body: data,
                contentType: "application/octet-stream",
                on: connection,
                close: true
            )
        } catch {
            logger.error("Failed to read database: \(error.localizedDescription)")
            sendHTTP(
                status: "500 Internal Server Error",
                body: Data("Failed to read database".utf8),
                on: connection,
                close: true
            )
        }
    }

    // MARK: - GET /cmd

    /// Upgrade the connection to an NDJSON command channel.
    private func handleCommandRequest(on connection: NWConnection) {
        // Only one command channel at a time
        if commandTransport != nil {
            logger.warning("Rejecting second command channel")
            sendHTTP(
                status: "409 Conflict",
                body: Data("Command channel already connected".utf8),
                on: connection,
                close: true
            )
            return
        }

        // Send the HTTP 200 response header, then upgrade to NDJSON streaming
        let header = "HTTP/1.1 200 OK\r\nContent-Type: application/x-ndjson\r\nTransfer-Encoding: chunked\r\nConnection: keep-alive\r\n\r\n"
        connection.send(content: Data(header.utf8), completion: .contentProcessed { [weak self] error in
            Task { @MainActor [weak self] in
                guard let self else { return }

                if let error {
                    self.logger.error("Failed to send cmd header: \(error.localizedDescription)")
                    connection.cancel()
                    return
                }

                self.setupCommandTransport(on: connection)
            }
        })
    }

    /// Wire up the NDJSON transport for reading commands and sending events.
    private func setupCommandTransport(on connection: NWConnection) {
        let transport = NDJSONTransport(connection: connection, logger: logger)
        self.commandTransport = transport
        self.commandConnection = connection

        transport.onLine = { [weak self] line in
            Task { @MainActor [weak self] in
                self?.handleIncomingLine(line)
            }
        }

        transport.onClose = { [weak self] in
            Task { @MainActor [weak self] in
                guard let self else { return }
                self.logger.info("Command channel closed")
                self.commandTransport = nil
                self.commandConnection = nil
                self.connectedRemoteName = nil
            }
        }

        transport.startReceiving()
        logger.info("Command channel established")

        // Send initial playback state
        sendPlaybackState()
    }

    // MARK: - Command Dispatch

    /// Process an incoming NDJSON line — try handshake first, then remote command.
    private func handleIncomingLine(_ line: String) {
        guard let data = line.data(using: .utf8) else { return }
        let decoder = JSONDecoder()

        // Try parsing as a handshake message first
        if let handshake = try? decoder.decode(HandshakeMessage.self, from: data) {
            logger.info("Handshake received: v\(handshake.protocolVersion), app \(handshake.appVersion)")
            connectedRemoteName = handshake.appVersion
            sendPlaybackState()
            return
        }

        // Parse as a remote command
        do {
            let command = try decoder.decode(RemoteCommand.self, from: data)
            logger.info("Received command: \(String(describing: command))")
            dispatchCommand(command)
        } catch {
            logger.error("Failed to decode command: \(error.localizedDescription)")
            commandTransport?.send(HostEvent.error(message: "Invalid command"))
        }
    }

    /// Execute the remote command against the player.
    private func dispatchCommand(_ command: RemoteCommand) {
        guard let player else {
            logger.warning("No player configured, ignoring command")
            return
        }

        switch command {
        case .play(let trackId, let queueIds):
            handlePlayCommand(trackId: trackId, queueIds: queueIds, player: player)
        case .pause:
            player.pause()
        case .resume:
            player.resume()
        case .next:
            player.next()
        case .previous:
            player.previous()
        case .seek(let position):
            player.seek(to: position)
        case .setVolume(let level):
            player.setVolume(level)
        case .toggleShuffle:
            player.toggleShuffle()
        case .refreshDB:
            commandTransport?.send(HostEvent.dbReady)
            return // Don't send playback state for refreshDB, just the dbReady event
        }

        // After each command, send current playback state
        sendPlaybackState()
    }

    /// Handle the play command by fetching tracks and setting up the queue.
    private func handlePlayCommand(trackId: Int64, queueIds: [Int64], player: PlayerViewModel) {
        guard let db else {
            logger.warning("No database configured, cannot handle play command")
            commandTransport?.send(HostEvent.error(message: "No database available"))
            return
        }

        do {
            let tracks = try db.fetchTracksByIds(queueIds)
            guard let track = tracks.first(where: { $0.id == trackId }) else {
                logger.warning("Track \(trackId) not found in database")
                commandTransport?.send(HostEvent.error(message: "Track not found"))
                return
            }
            player.setQueue(tracks)
            player.play(track)
        } catch {
            logger.error("Failed to fetch tracks: \(error.localizedDescription)")
            commandTransport?.send(HostEvent.error(message: "Database error"))
        }
    }

    // MARK: - State Updates

    /// Build and send the current playback state to the connected remote.
    private func sendPlaybackState() {
        guard let transport = commandTransport else { return }

        let payload = PlaybackStatePayload(
            trackId: player?.currentTrack?.id,
            isPlaying: player?.isPlaying ?? false,
            currentTime: player?.currentTime ?? 0,
            duration: player?.duration ?? 0,
            volume: player?.volume ?? 0.65,
            isShuffled: player?.isShuffled ?? false
        )

        transport.send(HostEvent.playbackState(payload))
    }

    // MARK: - HTTP Helper

    /// Send an HTTP response with the given status, body, and content type.
    private func sendHTTP(
        status: String,
        body: Data?,
        contentType: String = "text/plain",
        on connection: NWConnection,
        close: Bool
    ) {
        let bodyData = body ?? Data()
        let header = "HTTP/1.1 \(status)\r\nContent-Type: \(contentType)\r\nContent-Length: \(bodyData.count)\r\nConnection: \(close ? "close" : "keep-alive")\r\n\r\n"
        var responseData = Data(header.utf8)
        responseData.append(bodyData)

        connection.send(content: responseData, completion: .contentProcessed { _ in
            if close {
                connection.cancel()
            }
        })
    }
}
