import Foundation

/// QR verification scaffolding: schema, signing, and basic challenge/response helpers.
final class VerificationService {
    static let shared = VerificationService()

    // Injected running transport (do NOT create new BLEService). Noise
    // identity operations go through the transport's narrow noise* wrappers
    // so the raw NoiseEncryptionService is never exposed.
    private var transport: Transport?
    func configure(with transport: Transport) { self.transport = transport }

    /// Encapsulates the data encoded into a verification QR
    struct VerificationQR: Codable {
        let v: Int
        let noiseKeyHex: String
        let signKeyHex: String
        let npub: String?
        let nickname: String
        let ts: Int64
        let nonceB64: String
        var sigHex: String

        static let context = "bitchat-verify-v1"

        /// Canonical bytes used for signature (deterministic ordering)
        func canonicalBytes() -> Data {
            var out = Data()
            func appendField(_ s: String) {
                let d = s.data(using: .utf8) ?? Data()
                out.append(UInt8(min(d.count, 255)))
                out.append(d.prefix(255))
            }
            appendField(Self.context)
            appendField(String(v))
            appendField(noiseKeyHex.lowercased())
            appendField(signKeyHex.lowercased())
            appendField(npub ?? "")
            appendField(nickname)
            appendField(String(ts))
            appendField(nonceB64)
            return out
        }

        func toURLString() -> String {
            var comps = URLComponents()
            comps.scheme = "bitchat"
            comps.host = "verify"
            comps.queryItems = [
                URLQueryItem(name: "v", value: String(v)),
                URLQueryItem(name: "noise", value: noiseKeyHex),
                URLQueryItem(name: "sign", value: signKeyHex),
                URLQueryItem(name: "nick", value: nickname),
                URLQueryItem(name: "ts", value: String(ts)),
                URLQueryItem(name: "nonce", value: nonceB64),
                URLQueryItem(name: "sig", value: sigHex)
            ] + (npub != nil ? [URLQueryItem(name: "npub", value: npub)] : [])
            return comps.string ?? ""
        }

        static func fromURL(_ url: URL) -> VerificationQR? {
            guard url.scheme == "bitchat", url.host == "verify",
                  let items = URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems else { return nil }
            func val(_ name: String) -> String? { items.first(where: { $0.name == name })?.value }
            guard let vStr = val("v"), let v = Int(vStr),
                  let noise = val("noise"), let sign = val("sign"),
                  let nick = val("nick"), let tsStr = val("ts"), let ts = Int64(tsStr),
                  let nonce = val("nonce"), let sig = val("sig") else { return nil }
            return VerificationQR(v: v, noiseKeyHex: noise, signKeyHex: sign, npub: val("npub"), nickname: nick, ts: ts, nonceB64: nonce, sigHex: sig)
        }
    }

    // MARK: - Public API

    /// Build a signed QR string for the current identity
    func buildMyQRString(nickname: String, npub: String?) -> String? {
        // Simple short-lived cache to speed up sheet opening
        struct Cache { static var last: (nick: String, npub: String?, builtAt: Date, value: String)? }
        if let c = Cache.last, c.nick == nickname, c.npub == npub, Date().timeIntervalSince(c.builtAt) < 60 {
            return c.value
        }
        guard let transport = transport else { return nil }
        let noiseKey = transport.noiseStaticPublicKeyData().hexEncodedString()
        let signKey = transport.noiseSigningPublicKeyData().hexEncodedString()
        let ts = Int64(Date().timeIntervalSince1970)
        var nonce = Data(count: 16)
        _ = nonce.withUnsafeMutableBytes { SecRandomCopyBytes(kSecRandomDefault, 16, $0.baseAddress!) }
        let nonceB64 = nonce.base64EncodedString().replacingOccurrences(of: "+", with: "-").replacingOccurrences(of: "/", with: "_").replacingOccurrences(of: "=", with: "")
        let payload = VerificationQR(v: 1, noiseKeyHex: noiseKey, signKeyHex: signKey, npub: npub, nickname: nickname, ts: ts, nonceB64: nonceB64, sigHex: "")
        let msg = payload.canonicalBytes()
        guard let sig = transport.noiseSignData(msg) else { return nil }
        let signed = VerificationQR(v: payload.v,
                                    noiseKeyHex: payload.noiseKeyHex,
                                    signKeyHex: payload.signKeyHex,
                                    npub: payload.npub,
                                    nickname: payload.nickname,
                                    ts: payload.ts,
                                    nonceB64: payload.nonceB64,
                                    sigHex: sig.hexEncodedString())
        let out = signed.toURLString()
        Cache.last = (nickname, npub, Date(), out)
        return out
    }

    /// Verify a scanned QR and return the parsed payload if valid (signature + freshness checks)
    func verifyScannedQR(_ urlString: String, maxAge: TimeInterval = TransportConfig.verificationQRMaxAgeSeconds) -> VerificationQR? {
        guard let url = URL(string: urlString), let qr = VerificationQR.fromURL(url) else { return nil }
        // Freshness
        let now = Date().timeIntervalSince1970
        if now - Double(qr.ts) > maxAge { return nil }
        // Verify signature using embedded ed25519 signKey
        guard let sig = Data(hexString: qr.sigHex), let signKey = Data(hexString: qr.signKeyHex) else { return nil }
        guard let transport = transport else { return nil }
        let ok = transport.noiseVerifySignature(sig, for: qr.canonicalBytes(), publicKey: signKey)
        return ok ? qr : nil
    }

    // MARK: - Noise payloads (scaffold only)

    func buildVerifyChallenge(noiseKeyHex: String, nonceA: Data) -> Data {
        // TLV: [0x01 len noiseKeyHex ascii] [0x02 len nonceA]
        var tlv = Data()
        let n0: [UInt8] = [0x01, UInt8(min(noiseKeyHex.count, 255))]
        tlv.append(contentsOf: n0)
        tlv.append(noiseKeyHex.data(using: .utf8)!.prefix(255))
        tlv.append(0x02)
        tlv.append(UInt8(min(nonceA.count, 255)))
        tlv.append(nonceA.prefix(255))
        return NoisePayload(type: .verifyChallenge, data: tlv).encode()
    }

    func buildVerifyResponse(noiseKeyHex: String, nonceA: Data) -> Data? {
        // Sign context: verify-response | noiseKeyHex | nonceA
        var msg = Data("bitchat-verify-resp-v1".utf8)
        let nk = noiseKeyHex.data(using: .utf8) ?? Data()
        msg.append(UInt8(min(nk.count, 255))); msg.append(nk.prefix(255))
        msg.append(nonceA)
        guard let transport = transport, let sig = transport.noiseSignData(msg) else { return nil }
        var tlv = Data()
        tlv.append(0x01); tlv.append(UInt8(min(nk.count, 255))); tlv.append(nk.prefix(255))
        tlv.append(0x02); tlv.append(UInt8(min(nonceA.count, 255))); tlv.append(nonceA.prefix(255))
        tlv.append(0x03); tlv.append(UInt8(min(sig.count, 255))); tlv.append(sig.prefix(255))
        return NoisePayload(type: .verifyResponse, data: tlv).encode()
    }

    func parseVerifyChallenge(_ data: Data) -> (noiseKeyHex: String, nonceA: Data)? {
        var idx = 0
        func take(_ n: Int) -> Data? {
            guard idx + n <= data.count else { return nil }
            let d = data[idx..<(idx+n)]
            idx += n
            return Data(d)
        }
        // Expect type already stripped; we receive only TLV here
        // TLV 0x01 noiseKeyHex
        guard let t1 = take(1), t1[0] == 0x01, let l1 = take(1), let s1 = take(Int(l1[0])),
              let noiseStr = String(data: s1, encoding: .utf8) else { return nil }
        // TLV 0x02 nonceA
        guard let t2 = take(1), t2[0] == 0x02, let l2 = take(1), let nA = take(Int(l2[0])) else { return nil }
        return (noiseStr, nA)
    }

    func parseVerifyResponse(_ data: Data) -> (noiseKeyHex: String, nonceA: Data, signature: Data)? {
        var idx = 0
        func take(_ n: Int) -> Data? {
            guard idx + n <= data.count else { return nil }
            let d = data[idx..<(idx+n)]
            idx += n
            return Data(d)
        }
        guard let t1 = take(1), t1[0] == 0x01, let l1 = take(1), let s1 = take(Int(l1[0])),
              let noiseStr = String(data: s1, encoding: .utf8) else { return nil }
        guard let t2 = take(1), t2[0] == 0x02, let l2 = take(1), let nA = take(Int(l2[0])) else { return nil }
        guard let t3 = take(1), t3[0] == 0x03, let l3 = take(1), let sig = take(Int(l3[0])) else { return nil }
        return (noiseStr, nA, sig)
    }

    func verifyResponseSignature(noiseKeyHex: String, nonceA: Data, signature: Data, signerPublicKeyHex: String) -> Bool {
        var msg = Data("bitchat-verify-resp-v1".utf8)
        let nk = noiseKeyHex.data(using: .utf8) ?? Data()
        msg.append(UInt8(min(nk.count, 255))); msg.append(nk.prefix(255))
        msg.append(nonceA)
        guard let transport = transport, let pub = Data(hexString: signerPublicKeyHex) else { return false }
        return transport.noiseVerifySignature(signature, for: msg, publicKey: pub)
    }
}
