// Sources/Core/Auth/Pairing.swift
// T-2.2: QR parsing + pairing exchange

import Foundation

// MARK: - Errors

enum PairingError: Error, Sendable {
    case invalidQR
    case networkError(Error)
    case serverError(Int, String)
    case decodingFailed
}

// MARK: - Wire types

private struct PairRequestBody: Encodable {
    let pairingToken: String
    let deviceName: String
    // deviceToken / environment injected in T-2.4 (Push integration)
}

private struct PairResponseBody: Decodable {
    let bearerToken: String
    let sidecarId: String
}

// MARK: - PairingService

struct PairingService: Sendable {

    // MARK: QR parsing

    /// Parses a `pi-remote://` URL produced by the sidecar `pi-remote pair` command.
    ///
    /// Expected format:
    /// ```
    /// pi-remote://<host>:<port>?pair=<pairingToken>&fp=<sha256-hex>&name=<sidecarName>
    /// ```
    static func parseQR(_ string: String) throws -> (
        host: String,
        port: Int,
        pairingToken: String,
        fingerprint: String,
        name: String
    ) {
        var components = URLComponents(string: string)

        // The custom scheme confuses URLComponents on some inputs — patch it.
        if components == nil || components?.host == nil {
            // Try replacing custom scheme with https so standard parsing works.
            let patched = string.replacingOccurrences(of: "pi-remote://", with: "https://")
            components = URLComponents(string: patched)
        }

        guard
            let comps = components,
            let host = comps.host, !host.isEmpty,
            let port = comps.port
        else {
            throw PairingError.invalidQR
        }

        let items = comps.queryItems ?? []

        func queryValue(_ name: String) throws -> String {
            guard let value = items.first(where: { $0.name == name })?.value,
                  !value.isEmpty
            else { throw PairingError.invalidQR }
            return value
        }

        let pairingToken  = try queryValue("pair")
        // fp may be empty pre-TLS (Phase 1); allowed for dev
        let fingerprint   = items.first(where: { $0.name == "fp" })?.value ?? ""
        let name          = try queryValue("name")

        return (host: host, port: port, pairingToken: pairingToken,
                fingerprint: fingerprint, name: name)
    }

    // MARK: Exchange

    /// Sends `POST /pair` to the sidecar and returns a persisted `SidecarCredential`.
    ///
    /// - Note: TLS pinning via `PinnedTrust` is wired in T-2.5.
    ///         Until then the request uses plain HTTP or default TLS validation.
    func exchange(
        host: String,
        port: Int,
        pairingToken: String,
        fingerprint: String,
        name: String,
        deviceName: String
    ) async throws -> SidecarCredential {

        // Prefer HTTPS; the sidecar uses a self-signed cert that will be
        // pinned in T-2.5. For now plain HTTP is also acceptable during dev.
        guard let url = URL(string: "http://\(host):\(port)/pair") else {
            throw PairingError.invalidQR
        }

        var request = URLRequest(url: url, timeoutInterval: 15)
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.setValue("application/json", forHTTPHeaderField: "Accept")

        let body = PairRequestBody(pairingToken: pairingToken, deviceName: deviceName)
        do {
            request.httpBody = try JSONEncoder().encode(body)
        } catch {
            throw PairingError.decodingFailed
        }

        let data: Data
        let response: URLResponse
        do {
            (data, response) = try await URLSession.shared.data(for: request)
        } catch {
            throw PairingError.networkError(error)
        }

        if let http = response as? HTTPURLResponse, !(200...299).contains(http.statusCode) {
            let body = String(data: data, encoding: .utf8) ?? "<empty>"
            throw PairingError.serverError(http.statusCode, body)
        }

        let decoded: PairResponseBody
        do {
            decoded = try JSONDecoder().decode(PairResponseBody.self, from: data)
        } catch {
            throw PairingError.decodingFailed
        }

        return SidecarCredential(
            sidecarId: decoded.sidecarId,
            host: host,
            port: port,
            bearerToken: decoded.bearerToken,
            tlsFingerprint: fingerprint,
            sidecarName: name,
            pairedAt: Date()
        )
    }
}
