import Foundation
import Network
import SwiftUI

/// Minimal HTTP/1.1 server for LAN import (GET /, health/status; POST /upload).
/// Intended for trusted local networks only — no upload authentication.
@MainActor
final class LanImportServer: ObservableObject {
    @Published private(set) var isRunning = false
    @Published private(set) var port: UInt16 = 8787
    @Published private(set) var lanIPv4: String?
    @Published private(set) var lastStatusMessage: String = "未启动"
    @Published private(set) var importSummary: ImportSummary = .empty
    @Published private(set) var stoppedByBackground = false

    private var listener: NWListener?
    private var libraryStore: LibraryStore?

    struct ImportSummary {
        var imported: Int = 0
        var skippedOther: Int = 0
        var failed: Int = 0
        var lastError: String?

        static let empty = ImportSummary()

        var descriptionText: String {
            "导入 \(imported) · 跳过 \(skippedOther) · 失败 \(failed)"
                + (lastError.map { " · \($0)" } ?? "")
        }
    }

    var baseURL: URL? {
        guard isRunning, let ip = lanIPv4 else { return nil }
        return URL(string: "http://\(ip):\(port)/")
    }

    var displayURL: String {
        guard let ip = lanIPv4 else { return "等待获取 IP…" }
        return "http://\(ip):\(port)"
    }

    func attach(store: LibraryStore) {
        self.libraryStore = store
        port = UInt16(clamping: store.settings().lanServerPort)
    }

    func recordImport(imported: Int = 0, skippedOther: Int = 0, failed: Int = 0, lastError: String? = nil) {
        var summary = importSummary
        summary.imported += imported
        summary.skippedOther += skippedOther
        summary.failed += failed
        if let lastError { summary.lastError = lastError }
        importSummary = summary
    }

    func start(preferredPort: UInt16? = nil) {
        stop()
        stoppedByBackground = false
        if let preferredPort {
            port = preferredPort
            libraryStore?.settings().lanServerPort = Int(preferredPort)
        }
        lanIPv4 = Self.primaryLANIPv4()
        do {
            let parameters = NWParameters.tcp
            parameters.allowLocalEndpointReuse = true
            let listener = try NWListener(using: parameters, on: NWEndpoint.Port(rawValue: port)!)
            listener.service = NWListener.Service(name: "StudyDeck", type: "_studydeck._tcp")
            listener.stateUpdateHandler = { [weak self] state in
                Task { @MainActor in
                    guard let self else { return }
                    switch state {
                    case .ready:
                        self.isRunning = true
                        self.lanIPv4 = Self.primaryLANIPv4()
                        self.lastStatusMessage = "运行中 · \(self.displayURL)"
                    case .failed(let error):
                        self.lastStatusMessage = "启动失败: \(error.localizedDescription)"
                        self.isRunning = false
                    case .cancelled:
                        self.isRunning = false
                    default:
                        break
                    }
                }
            }
            listener.newConnectionHandler = { [weak self] connection in
                Task { @MainActor in
                    self?.handle(connection: connection)
                }
            }
            listener.start(queue: .global(qos: .userInitiated))
            self.listener = listener
            lastStatusMessage = "正在启动…"
        } catch {
            lastStatusMessage = "无法监听端口 \(port): \(error.localizedDescription)"
            isRunning = false
        }
    }

    func stop(byBackground: Bool = false) {
        listener?.cancel()
        listener = nil
        isRunning = false
        stoppedByBackground = byBackground
        lastStatusMessage = byBackground ? "已因进入后台停止 · 回到前台后请重新开启" : "已停止"
    }

    func handleScenePhase(_ phase: ScenePhase) {
        if phase != .active && isRunning {
            stop(byBackground: true)
        }
    }

    // MARK: - Connection handling

    private func handle(connection: NWConnection) {
        connection.start(queue: .global(qos: .userInitiated))
        receiveRequest(on: connection, accumulated: Data())
    }

    private func receiveRequest(on connection: NWConnection, accumulated: Data) {
        connection.receive(minimumIncompleteLength: 1, maximumLength: 65536) { [weak self] data, _, isComplete, error in
            Task { @MainActor in
                guard let self else {
                    connection.cancel()
                    return
                }
                if let error {
                    self.lastStatusMessage = "连接错误: \(error.localizedDescription)"
                    connection.cancel()
                    return
                }
                var buffer = accumulated
                if let data { buffer.append(data) }
                if let headerEnd = buffer.range(of: Data("\r\n\r\n".utf8)),
                   headerEnd.lowerBound >= buffer.startIndex,
                   headerEnd.upperBound <= buffer.endIndex {
                    let headerData = buffer.subdata(in: buffer.startIndex..<headerEnd.lowerBound)
                    let body = buffer.subdata(in: headerEnd.upperBound..<buffer.endIndex)
                    guard let headerText = String(data: headerData, encoding: .utf8) else {
                        self.respond(connection, status: 400, body: "bad request")
                        return
                    }
                    let lines = headerText.split(separator: "\r\n", omittingEmptySubsequences: false)
                    guard let requestLine = lines.first else {
                        self.respond(connection, status: 400, body: "bad request")
                        return
                    }
                    let parts = requestLine.split(separator: " ")
                    guard parts.count >= 2 else {
                        self.respond(connection, status: 400, body: "bad request")
                        return
                    }
                    let method = String(parts[0])
                    let pathQuery = String(parts[1])
                    let path = pathQuery.split(separator: "?").first.map(String.init) ?? pathQuery
                    var headers: [String: String] = [:]
                    for line in lines.dropFirst() {
                        if let idx = line.firstIndex(of: ":") {
                            let key = line[..<idx].trimmingCharacters(in: .whitespaces).lowercased()
                            let value = line[line.index(after: idx)...].trimmingCharacters(in: .whitespaces)
                            headers[key] = value
                        }
                    }
                    let contentLength = Int(headers["content-length"] ?? "0") ?? 0
                    func finish(_ fullBody: Data) {
                        Task { @MainActor in
                            await self.route(
                                method: method,
                                path: path,
                                headers: headers,
                                body: fullBody,
                                connection: connection
                            )
                        }
                    }
                    if body.count >= contentLength {
                        finish(Data(body.prefix(contentLength)))
                    } else {
                        self.receiveBody(
                            on: connection,
                            needed: contentLength,
                            current: body,
                            completion: finish
                        )
                    }
                    return
                }
                if isComplete {
                    connection.cancel()
                    return
                }
                self.receiveRequest(on: connection, accumulated: buffer)
            }
        }
    }

    private func receiveBody(
        on connection: NWConnection,
        needed: Int,
        current: Data,
        completion: @escaping (Data) -> Void
    ) {
        if current.count >= needed {
            completion(Data(current.prefix(needed)))
            return
        }
        connection.receive(minimumIncompleteLength: 1, maximumLength: 1024 * 1024) { [weak self] data, _, isComplete, error in
            var buffer = current
            if let data { buffer.append(data) }
            if error != nil || (isComplete && buffer.count < needed) {
                connection.cancel()
                return
            }
            Task { @MainActor in
                self?.receiveBody(on: connection, needed: needed, current: buffer, completion: completion)
            }
        }
    }

    private func route(
        method: String,
        path: String,
        headers: [String: String],
        body: Data,
        connection: NWConnection
    ) async {
        switch (method, path) {
        case ("GET", "/"):
            let html = Self.uploadPageHTML()
            respond(connection, status: 200, contentType: "text/html; charset=utf-8", body: html)
        case ("GET", "/health"), ("GET", "/status"):
            let json = "{\"ok\":true,\"port\":\(port),\"running\":\(isRunning)}"
            respond(connection, status: 200, contentType: "application/json", body: json)
        case ("POST", "/upload"):
            await handleUpload(headers: headers, body: body, connection: connection)
        default:
            respond(connection, status: 404, body: "not found")
        }
    }

    private func handleUpload(
        headers: [String: String],
        body: Data,
        connection: NWConnection
    ) async {
        guard isRunning else {
            respond(connection, status: 503, body: "server stopped")
            return
        }
        guard let store = libraryStore else {
            respond(connection, status: 500, body: "store unavailable")
            return
        }
        guard let contentType = headers["content-type"],
              let boundary = MultipartParser.boundary(from: contentType) else {
            respond(connection, status: 400, body: "expected multipart")
            return
        }
        let parts = MultipartParser.parse(body: body, boundary: boundary)
        let targetDir = parts.first(where: { $0.name == "targetDir" })?.textValue ?? ""

        guard let filePart = parts.first(where: { $0.name == "file" }),
              let fileData = filePart.data else {
            respond(connection, status: 400, body: "missing file")
            return
        }
        // Flat import only: use basename, ignore any nested relative path from clients.
        let rawName = filePart.filename ?? "upload.bin"
        let fileName = (rawName as NSString).lastPathComponent
        let ext = (fileName as NSString).pathExtension
        let kind = ResourceKind.from(extension: ext)
        if kind == .other {
            recordImport(skippedOther: 1)
            respond(connection, status: 415, body: "unsupported type")
            return
        }
        do {
            let tmp = FileManager.default.temporaryDirectory
                .appendingPathComponent(UUID().uuidString)
                .appendingPathExtension(ext)
            try fileData.write(to: tmp)
            defer { try? FileManager.default.removeItem(at: tmp) }
            _ = try store.importFile(
                from: tmp,
                targetDir: targetDir,
                nestedRelativePath: fileName
            )
            recordImport(imported: 1)
            respond(connection, status: 200, body: "ok")
        } catch {
            recordImport(failed: 1, lastError: error.localizedDescription)
            respond(connection, status: 500, body: error.localizedDescription)
        }
    }

    private func respond(
        _ connection: NWConnection,
        status: Int,
        contentType: String = "text/plain; charset=utf-8",
        body: String
    ) {
        let data = Data(body.utf8)
        respond(connection, status: status, contentType: contentType, body: data)
    }

    private func respond(
        _ connection: NWConnection,
        status: Int,
        contentType: String,
        body: Data
    ) {
        let reason: String
        switch status {
        case 200: reason = "OK"
        case 400: reason = "Bad Request"
        case 404: reason = "Not Found"
        case 415: reason = "Unsupported Media Type"
        case 503: reason = "Service Unavailable"
        default: reason = "Error"
        }
        var response = "HTTP/1.1 \(status) \(reason)\r\n"
        response += "Content-Type: \(contentType)\r\n"
        response += "Content-Length: \(body.count)\r\n"
        response += "Connection: close\r\n"
        response += "Access-Control-Allow-Origin: *\r\n"
        response += "\r\n"
        var payload = Data(response.utf8)
        payload.append(body)
        connection.send(content: payload, completion: .contentProcessed { _ in
            connection.cancel()
        })
    }

    private static func uploadPageHTML() -> String {
        if let url = Bundle.main.url(forResource: "UploadPage", withExtension: "html"),
           let html = try? String(contentsOf: url, encoding: .utf8) {
            return html
        }
        return "<html><body><h1>StudyDeck Upload</h1><p>UploadPage.html missing</p></body></html>"
    }

    static func primaryLANIPv4() -> String? {
        var address: String?
        var ifaddr: UnsafeMutablePointer<ifaddrs>?
        guard getifaddrs(&ifaddr) == 0, let first = ifaddr else { return nil }
        defer { freeifaddrs(ifaddr) }
        var ptr: UnsafeMutablePointer<ifaddrs>? = first
        while let current = ptr {
            defer { ptr = current.pointee.ifa_next }
            let interface = current.pointee
            let family = interface.ifa_addr.pointee.sa_family
            guard family == UInt8(AF_INET) else { continue }
            let name = String(cString: interface.ifa_name)
            guard name.hasPrefix("en") || name.hasPrefix("bridge") || name.hasPrefix("wlan") else { continue }
            var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
            getnameinfo(
                interface.ifa_addr,
                socklen_t(interface.ifa_addr.pointee.sa_len),
                &hostname,
                socklen_t(hostname.count),
                nil,
                0,
                NI_NUMERICHOST
            )
            let ip = String(cString: hostname)
            if ip.hasPrefix("127.") { continue }
            address = ip
            if name.hasPrefix("en") { break }
        }
        return address
    }
}
