import Foundation

/// Synor Crypto SDK for Swift
///
/// Quantum-resistant cryptographic primitives for the Synor blockchain.
///
/// ```swift
/// let config = CryptoConfig(apiKey: "your-api-key")
/// let crypto = SynorCrypto(config: config)
///
/// // Generate a mnemonic
/// let mnemonic = try await crypto.mnemonic.generate(wordCount: 24)
/// print("Backup words: \(mnemonic.phrase)")
///
/// // Create keypair from mnemonic
/// let keypair = try await crypto.keypairs.fromMnemonic(phrase: mnemonic.phrase, passphrase: "")
/// let address = keypair.address(for: .mainnet)
///
/// // Sign a message
/// let signature = try await crypto.signing.sign(keypair: keypair, message: "Hello!".data(using: .utf8)!)
/// ```
public class SynorCrypto {
    private let config: CryptoConfig
    private let session: URLSession
    private let encoder: JSONEncoder
    private let decoder: JSONDecoder
    private var _closed = false

    public let mnemonic: MnemonicClient
    public let keypairs: KeypairClient
    public let signing: SigningClient
    public let falcon: FalconClient
    public let sphincs: SphincsClient
    public let kdf: KdfClient
    public let hash: HashClient

    public var defaultNetwork: Network { config.defaultNetwork }
    public var isClosed: Bool { _closed }

    public init(config: CryptoConfig) {
        self.config = config

        let sessionConfig = URLSessionConfiguration.default
        sessionConfig.timeoutIntervalForRequest = config.timeout
        self.session = URLSession(configuration: sessionConfig)

        self.encoder = JSONEncoder()
        encoder.keyEncodingStrategy = .convertToSnakeCase

        self.decoder = JSONDecoder()
        decoder.keyDecodingStrategy = .convertFromSnakeCase

        self.mnemonic = MnemonicClient()
        self.keypairs = KeypairClient()
        self.signing = SigningClient()
        self.falcon = FalconClient()
        self.sphincs = SphincsClient()
        self.kdf = KdfClient()
        self.hash = HashClient()

        // Set parent references
        self.mnemonic.crypto = self
        self.keypairs.crypto = self
        self.signing.crypto = self
        self.falcon.crypto = self
        self.sphincs.crypto = self
        self.kdf.crypto = self
        self.hash.crypto = self
    }

    public func healthCheck() async -> Bool {
        do {
            let result: [String: String] = try await get(path: "/health")
            return result["status"] == "healthy"
        } catch {
            return false
        }
    }

    public func getInfo() async throws -> [String: Any] {
        try await get(path: "/info")
    }

    public func close() {
        _closed = true
        session.invalidateAndCancel()
    }

    // MARK: - Internal HTTP Methods

    func get<T: Decodable>(path: String) async throws -> T {
        guard !_closed else {
            throw CryptoError(message: "Client has been closed", code: "CLIENT_CLOSED")
        }

        guard let url = URL(string: config.endpoint + path) else {
            throw CryptoError(message: "Invalid URL", code: "INVALID_URL")
        }

        var request = URLRequest(url: url)
        request.httpMethod = "GET"
        request.setValue("Bearer \(config.apiKey)", forHTTPHeaderField: "Authorization")
        request.setValue("swift/0.1.0", forHTTPHeaderField: "X-SDK-Version")

        let (data, response) = try await session.data(for: request)
        return try handleResponse(data: data, response: response)
    }

    func post<T: Decodable>(path: String, body: [String: Any]? = nil) async throws -> T {
        guard !_closed else {
            throw CryptoError(message: "Client has been closed", code: "CLIENT_CLOSED")
        }

        guard let url = URL(string: config.endpoint + path) else {
            throw CryptoError(message: "Invalid URL", code: "INVALID_URL")
        }

        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("Bearer \(config.apiKey)", forHTTPHeaderField: "Authorization")
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.setValue("swift/0.1.0", forHTTPHeaderField: "X-SDK-Version")

        if let body = body {
            request.httpBody = try JSONSerialization.data(withJSONObject: body)
        } else {
            request.httpBody = "{}".data(using: .utf8)
        }

        let (data, response) = try await session.data(for: request)
        return try handleResponse(data: data, response: response)
    }

    private func handleResponse<T: Decodable>(data: Data, response: URLResponse) throws -> T {
        guard let httpResponse = response as? HTTPURLResponse else {
            throw CryptoError(message: "Invalid response", code: "INVALID_RESPONSE")
        }

        if httpResponse.statusCode >= 400 {
            if let errorDict = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
                let message = errorDict["message"] as? String ?? "HTTP \(httpResponse.statusCode)"
                let code = errorDict["code"] as? String
                throw CryptoError(message: message, code: code)
            }
            throw CryptoError(message: "HTTP \(httpResponse.statusCode)", code: nil)
        }

        return try decoder.decode(T.self, from: data)
    }

    // MARK: - Sub-Clients

    public class MnemonicClient {
        weak var crypto: SynorCrypto?

        public func generate(wordCount: Int = 24) async throws -> Mnemonic {
            try await crypto!.post(path: "/mnemonic/generate", body: ["word_count": wordCount])
        }

        public func fromPhrase(phrase: String) async throws -> Mnemonic {
            try await crypto!.post(path: "/mnemonic/from-phrase", body: ["phrase": phrase])
        }

        public func validate(phrase: String) async throws -> MnemonicValidation {
            try await crypto!.post(path: "/mnemonic/validate", body: ["phrase": phrase])
        }

        public func toSeed(phrase: String, passphrase: String = "") async throws -> Data {
            struct SeedResponse: Decodable { let seed: String }
            let result: SeedResponse = try await crypto!.post(
                path: "/mnemonic/to-seed",
                body: ["phrase": phrase, "passphrase": passphrase]
            )
            return Data(base64Encoded: result.seed) ?? Data()
        }

        public func suggestWords(partial: String, limit: Int = 5) async throws -> [String] {
            struct SuggestResponse: Decodable { let suggestions: [String] }
            let result: SuggestResponse = try await crypto!.post(
                path: "/mnemonic/suggest",
                body: ["partial": partial, "limit": limit]
            )
            return result.suggestions
        }
    }

    public class KeypairClient {
        weak var crypto: SynorCrypto?

        public func generate() async throws -> HybridKeypair {
            try await crypto!.post(path: "/keypair/generate")
        }

        public func fromMnemonic(phrase: String, passphrase: String = "") async throws -> HybridKeypair {
            try await crypto!.post(
                path: "/keypair/from-mnemonic",
                body: ["phrase": phrase, "passphrase": passphrase]
            )
        }

        public func fromSeed(seed: Data) async throws -> HybridKeypair {
            try await crypto!.post(
                path: "/keypair/from-seed",
                body: ["seed": seed.base64EncodedString()]
            )
        }

        public func getAddress(publicKey: HybridPublicKey, network: Network) async throws -> Address {
            try await crypto!.post(
                path: "/keypair/address",
                body: ["public_key": publicKey.toDict(), "network": network.rawValue]
            )
        }
    }

    public class SigningClient {
        weak var crypto: SynorCrypto?

        public func sign(keypair: HybridKeypair, message: Data) async throws -> HybridSignature {
            try await crypto!.post(path: "/sign/hybrid", body: [
                "secret_key": keypair.secretKey.toDict(),
                "message": message.base64EncodedString()
            ])
        }

        public func verify(publicKey: HybridPublicKey, message: Data, signature: HybridSignature) async throws -> Bool {
            struct VerifyResponse: Decodable { let valid: Bool }
            let result: VerifyResponse = try await crypto!.post(path: "/sign/verify", body: [
                "public_key": publicKey.toDict(),
                "message": message.base64EncodedString(),
                "signature": signature.toDict()
            ])
            return result.valid
        }

        public func signEd25519(secretKey: Data, message: Data) async throws -> Data {
            struct SignResponse: Decodable { let signature: String }
            let result: SignResponse = try await crypto!.post(path: "/sign/ed25519", body: [
                "secret_key": secretKey.base64EncodedString(),
                "message": message.base64EncodedString()
            ])
            return Data(base64Encoded: result.signature) ?? Data()
        }
    }

    public class FalconClient {
        weak var crypto: SynorCrypto?

        public func generate(variant: FalconVariant = .falcon512) async throws -> FalconKeypair {
            try await crypto!.post(path: "/falcon/generate", body: ["variant": variant.rawValue])
        }

        public func sign(keypair: FalconKeypair, message: Data) async throws -> FalconSignature {
            try await crypto!.post(path: "/falcon/sign", body: [
                "variant": keypair.variantEnum.rawValue,
                "secret_key": keypair.secretKey.keyBytes.base64EncodedString(),
                "message": message.base64EncodedString()
            ])
        }

        public func verify(publicKey: Data, message: Data, signature: FalconSignature) async throws -> Bool {
            struct VerifyResponse: Decodable { let valid: Bool }
            let result: VerifyResponse = try await crypto!.post(path: "/falcon/verify", body: [
                "variant": signature.variantEnum.rawValue,
                "public_key": publicKey.base64EncodedString(),
                "message": message.base64EncodedString(),
                "signature": signature.signatureBytes.base64EncodedString()
            ])
            return result.valid
        }
    }

    public class SphincsClient {
        weak var crypto: SynorCrypto?

        public func generate(variant: SphincsVariant = .shake128s) async throws -> SphincsKeypair {
            try await crypto!.post(path: "/sphincs/generate", body: ["variant": variant.rawValue])
        }

        public func sign(keypair: SphincsKeypair, message: Data) async throws -> SphincsSignature {
            try await crypto!.post(path: "/sphincs/sign", body: [
                "variant": keypair.variantEnum.rawValue,
                "secret_key": keypair.secretKey.keyBytes.base64EncodedString(),
                "message": message.base64EncodedString()
            ])
        }

        public func verify(publicKey: Data, message: Data, signature: SphincsSignature) async throws -> Bool {
            struct VerifyResponse: Decodable { let valid: Bool }
            let result: VerifyResponse = try await crypto!.post(path: "/sphincs/verify", body: [
                "variant": signature.variantEnum.rawValue,
                "public_key": publicKey.base64EncodedString(),
                "message": message.base64EncodedString(),
                "signature": signature.signatureBytes.base64EncodedString()
            ])
            return result.valid
        }
    }

    public class KdfClient {
        weak var crypto: SynorCrypto?

        public func deriveKey(seed: Data, config: DerivationConfig = DerivationConfig()) async throws -> Data {
            var body: [String: Any] = [
                "seed": seed.base64EncodedString(),
                "output_length": config.outputLength
            ]
            if let salt = config.salt {
                body["salt"] = salt.base64EncodedString()
            }
            if let info = config.info {
                body["info"] = info.base64EncodedString()
            }

            struct KeyResponse: Decodable { let key: String }
            let result: KeyResponse = try await crypto!.post(path: "/kdf/hkdf", body: body)
            return Data(base64Encoded: result.key) ?? Data()
        }

        public func deriveFromPassword(password: Data, config: PasswordDerivationConfig) async throws -> Data {
            struct KeyResponse: Decodable { let key: String }
            let result: KeyResponse = try await crypto!.post(path: "/kdf/pbkdf2", body: [
                "password": password.base64EncodedString(),
                "salt": config.salt.base64EncodedString(),
                "iterations": config.iterations,
                "output_length": config.outputLength
            ])
            return Data(base64Encoded: result.key) ?? Data()
        }
    }

    public class HashClient {
        weak var crypto: SynorCrypto?

        public func sha3_256(data: Data) async throws -> Hash256 {
            try await crypto!.post(path: "/hash/sha3-256", body: ["data": data.base64EncodedString()])
        }

        public func blake3(data: Data) async throws -> Hash256 {
            try await crypto!.post(path: "/hash/blake3", body: ["data": data.base64EncodedString()])
        }

        public func keccak256(data: Data) async throws -> Hash256 {
            try await crypto!.post(path: "/hash/keccak256", body: ["data": data.base64EncodedString()])
        }
    }
}
