import Foundation
import CryptoKit

// MARK: - Data ↔ Hex

extension Data {
    /// Convert data to lowercase hex string
    var hexString: String {
        map { String(format: "%02x", $0) }.joined()
    }

    /// Initialize Data from a hex string
    init?(hexString: String) {
        let hex = hexString.lowercased()
        guard hex.count % 2 == 0 else { return nil }
        var data = Data(capacity: hex.count / 2)
        var index = hex.startIndex
        while index < hex.endIndex {
            let nextIndex = hex.index(index, offsetBy: 2)
            guard let byte = UInt8(hex[index..<nextIndex], radix: 16) else { return nil }
            data.append(byte)
            index = nextIndex
        }
        self = data
    }

    /// Generate random bytes
    static func randomBytes(_ count: Int) -> Data {
        var data = Data(count: count)
        data.withUnsafeMutableBytes { ptr in
            _ = SecRandomCopyBytes(kSecRandomDefault, count, ptr.baseAddress!)
        }
        return data
    }
}

// MARK: - Data ↔ Base64 (Protocol Wire Format)

extension Data {
    /// Encode to standard base64 string (matching Python's base64.b64encode)
    func base64EncodedString() -> String {
        self.base64EncodedString(options: [])
    }

    /// Decode from base64 string
    static func fromBase64(_ string: String) throws -> Data {
        // Try standard base64 first, then URL-safe
        if let data = Data(base64Encoded: string, options: .ignoreUnknownCharacters) {
            return data
        }
        throw CryptoError.invalidBase64
    }
}

// MARK: - UInt32 Big-Endian

extension UInt32 {
    var bigEndianData: Data {
        var value = self.bigEndian
        return Data(bytes: &value, count: 4)
    }
}

// MARK: - CryptoKit Key → Data

extension Curve25519.KeyAgreement.PublicKey {
    nonisolated var rawData: Data {
        Data(rawRepresentation)
    }
}

extension Curve25519.KeyAgreement.PrivateKey {
    nonisolated var rawData: Data {
        Data(rawRepresentation)
    }
}

extension Curve25519.Signing.PublicKey {
    nonisolated var rawData: Data {
        Data(rawRepresentation)
    }
}

extension Curve25519.Signing.PrivateKey {
    nonisolated var rawData: Data {
        Data(rawRepresentation)
    }
}

// MARK: - String helpers

extension String {
    /// Trim whitespace and newlines
    var trimmed: String {
        trimmingCharacters(in: .whitespacesAndNewlines)
    }
}

// MARK: - Date Parsing (server sends ISO8601 with or without timezone)

enum DateParsing {
    private static let iso8601WithTZ: ISO8601DateFormatter = {
        let f = ISO8601DateFormatter()
        f.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
        return f
    }()

    private static let iso8601Basic: ISO8601DateFormatter = {
        let f = ISO8601DateFormatter()
        f.formatOptions = [.withInternetDateTime]
        return f
    }()

    private static let noTZ: DateFormatter = {
        let f = DateFormatter()
        f.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
        f.timeZone = TimeZone(identifier: "UTC")
        f.locale = Locale(identifier: "en_US_POSIX")
        return f
    }()

    /// Parse ISO8601 date string — handles with/without timezone, with/without fractional seconds
    static func parse(_ string: String) -> Date? {
        iso8601WithTZ.date(from: string)
            ?? iso8601Basic.date(from: string)
            ?? noTZ.date(from: string)
    }

    /// Format Date to ISO8601 string (for after_ts / since_ts parameters)
    static func format(_ date: Date) -> String {
        iso8601WithTZ.string(from: date)
    }
}

// MARK: - Dictionary merge helper

extension Dictionary where Key == String, Value == Any {
    nonisolated func string(for key: String) -> String? {
        self[key] as? String
    }

    nonisolated func int(for key: String) -> Int? {
        if let i = self[key] as? Int { return i }
        if let s = self[key] as? String, let i = Int(s) { return i }
        return nil
    }

    nonisolated func dict(for key: String) -> [String: Any]? {
        self[key] as? [String: Any]
    }

    nonisolated func array(for key: String) -> [[String: Any]]? {
        self[key] as? [[String: Any]]
    }

    nonisolated func data(for key: String) -> Data? {
        if let hex = self[key] as? String {
            return Data(hexString: hex)
        }
        return nil
    }

    nonisolated func bool(for key: String) -> Bool? {
        if let b = self[key] as? Bool { return b }
        if let i = self[key] as? Int { return i != 0 }
        return nil
    }
}
