import Foundation
import CryptoKit

/// Contact key verification: fingerprints, safety numbers, QR codes.
/// Matches Python: crypto_utils.py compute_fingerprint, compute_safety_number, etc.
enum ContactVerification {

    /// Version byte for fingerprint computation (Signal's NumericFingerprint).
    private static let fingerprintVersion: UInt16 = 0

    /// Number of SHA-512 iterations for fingerprint computation.
    private static let fingerprintIterations = 5200

    // MARK: - Fingerprint

    /// Compute a 32-byte fingerprint for a user's identity key.
    ///
    /// Uses iterated SHA-512 (Signal's NumericFingerprint algorithm).
    /// Seed: version(2B big-endian) + identity_key(32B) + user_id(UTF-8).
    /// Each iteration: SHA-512(previous_hash + identity_key).
    /// Output: first 32 bytes of final hash.
    static func computeFingerprint(userId: String, identityKey: Data, iterations: Int = fingerprintIterations) -> Data {
        let versionBytes = fingerprintVersion.bigEndianData
        var data = versionBytes + identityKey + Data(userId.utf8)
        for _ in 0..<iterations {
            var hasher = SHA512()
            hasher.update(data: data)
            hasher.update(data: identityKey)
            let digest = hasher.finalize()
            data = Data(digest)
        }
        return Data(data.prefix(32))
    }

    /// Format 32-byte fingerprint as 6 groups of 5 zero-padded digits (30 digits).
    ///
    /// Each group: int(bytes[i*5:(i+1)*5], big-endian) % 100000.
    /// Output: two lines of 3 groups each, space-separated.
    static func formatFingerprint(_ fpBytes: Data) -> String {
        var groups: [String] = []
        for i in 0..<6 {
            let start = i * 5
            let end = min(start + 5, fpBytes.count)
            let slice = fpBytes[fpBytes.startIndex + start ..< fpBytes.startIndex + end]
            let num = bigEndianUInt64(slice) % 100000
            groups.append(String(format: "%05d", num))
        }
        return groups[0..<3].joined(separator: " ") + "\n" + groups[3..<6].joined(separator: " ")
    }

    // MARK: - Safety Number

    /// Compute a 60-digit safety number for a pair of users.
    ///
    /// Both users see the same number regardless of who computes it.
    /// Lower user_id's fingerprint comes first (deterministic ordering).
    /// Output: 12 groups of 5 digits, formatted as 3 lines of 4 groups.
    static func computeSafetyNumber(
        myUserId: String, myIdentityKey: Data,
        theirUserId: String, theirIdentityKey: Data
    ) -> String {
        let fpMine = computeFingerprint(userId: myUserId, identityKey: myIdentityKey)
        let fpTheirs = computeFingerprint(userId: theirUserId, identityKey: theirIdentityKey)

        let combined: Data
        if myUserId < theirUserId {
            combined = fpMine + fpTheirs
        } else {
            combined = fpTheirs + fpMine
        }

        // 64 bytes -> 12 groups of 5 digits
        var groups: [String] = []
        for i in 0..<12 {
            let start = i * 5
            let end = min(start + 5, combined.count)
            let slice = combined[combined.startIndex + start ..< combined.startIndex + end]
            let num = bigEndianUInt64(slice) % 100000
            groups.append(String(format: "%05d", num))
        }

        return [
            groups[0..<4].joined(separator: " "),
            groups[4..<8].joined(separator: " "),
            groups[8..<12].joined(separator: " "),
        ].joined(separator: "\n")
    }

    // MARK: - QR Code

    /// Encode user identity for QR code verification.
    ///
    /// Format: version(1B=0x01) + uid_len(1B) + uid(UTF-8) + identity_key(32B).
    static func encodeVerificationQR(userId: String, identityKey: Data) -> Data {
        let uidBytes = Data(userId.utf8)
        var data = Data([0x01, UInt8(uidBytes.count)])
        data.append(uidBytes)
        data.append(identityKey)
        return data
    }

    /// Decode QR code verification payload.
    ///
    /// Returns (userId, identityKey).
    /// Throws on invalid format.
    static func decodeVerificationQR(_ data: Data) throws -> (userId: String, identityKey: Data) {
        guard data.count >= 3 else {
            throw VerificationError.qrDataTooShort
        }
        guard data[data.startIndex] == 0x01 else {
            throw VerificationError.unknownQRVersion(data[data.startIndex])
        }
        let uidLen = Int(data[data.startIndex + 1])
        guard data.count >= 2 + uidLen + 32 else {
            throw VerificationError.qrDataTruncated
        }
        let uidData = data[data.startIndex + 2 ..< data.startIndex + 2 + uidLen]
        guard let userId = String(data: uidData, encoding: .utf8) else {
            throw VerificationError.invalidUTF8
        }
        let identityKey = Data(data[data.startIndex + 2 + uidLen ..< data.startIndex + 2 + uidLen + 32])
        return (userId, identityKey)
    }

    // MARK: - Helpers

    /// Convert up to 8 bytes to UInt64, big-endian.
    private static func bigEndianUInt64(_ data: Data) -> UInt64 {
        var result: UInt64 = 0
        for byte in data {
            result = result << 8 | UInt64(byte)
        }
        return result
    }
}

// MARK: - UInt16 Big-Endian

private extension UInt16 {
    var bigEndianData: Data {
        var value = self.bigEndian
        return Data(bytes: &value, count: 2)
    }
}

// MARK: - Verification Errors

enum VerificationError: Error, LocalizedError {
    case qrDataTooShort
    case unknownQRVersion(UInt8)
    case qrDataTruncated
    case invalidUTF8

    var errorDescription: String? {
        switch self {
        case .qrDataTooShort: return "QR data too short"
        case .unknownQRVersion(let v): return "Unknown QR version: \(v)"
        case .qrDataTruncated: return "QR data truncated"
        case .invalidUTF8: return "Invalid UTF-8 in QR data"
        }
    }
}
