import Foundation

/// Synor Wallet SDK client for Swift.
///
/// Provides key management, transaction signing, and balance queries
/// for the Synor blockchain.
///
/// Example:
/// ```swift
/// let wallet = SynorWallet(config: WalletConfig(apiKey: "your-api-key"))
///
/// // Create a new wallet
/// let result = try await wallet.createWallet(type: .standard)
/// print("Wallet ID: \(result.wallet.id)")
/// print("Mnemonic: \(result.mnemonic ?? "N/A")")
///
/// // Get balance
/// let balance = try await wallet.getBalance(address: result.wallet.addresses[0].address)
/// print("Balance: \(balance.total)")
/// ```
public class SynorWallet {
    private let config: WalletConfig
    private let session: URLSession
    private let decoder: JSONDecoder
    private let encoder: JSONEncoder

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

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

        self.decoder = JSONDecoder()
        self.encoder = JSONEncoder()
    }

    // MARK: - Wallet Operations

    /// Create a new wallet.
    public func createWallet(type: WalletType = .standard) async throws -> CreateWalletResult {
        let request = CreateWalletRequest(type: type, network: config.network)
        return try await post(path: "/wallets", body: request)
    }

    /// Import a wallet from a mnemonic phrase.
    public func importWallet(mnemonic: String, passphrase: String? = nil) async throws -> Wallet {
        let request = ImportWalletRequest(
            mnemonic: mnemonic,
            passphrase: passphrase,
            network: config.network
        )
        return try await post(path: "/wallets/import", body: request)
    }

    /// Get a wallet by ID.
    public func getWallet(walletId: String) async throws -> Wallet {
        return try await get(path: "/wallets/\(walletId)")
    }

    /// Generate a new address for a wallet.
    public func generateAddress(walletId: String, isChange: Bool = false) async throws -> Address {
        let request = GenerateAddressRequest(isChange: isChange)
        return try await post(path: "/wallets/\(walletId)/addresses", body: request)
    }

    /// Get a stealth address for privacy transactions.
    public func getStealthAddress(walletId: String) async throws -> StealthAddress {
        return try await get(path: "/wallets/\(walletId)/stealth-address")
    }

    // MARK: - Signing Operations

    /// Sign a transaction.
    public func signTransaction(walletId: String, transaction: Transaction) async throws -> SignedTransaction {
        let request = SignTransactionRequest(walletId: walletId, transaction: transaction)
        return try await post(path: "/transactions/sign", body: request)
    }

    /// Sign a message with a wallet address.
    public func signMessage(walletId: String, message: String, addressIndex: Int = 0) async throws -> Signature {
        let request = SignMessageRequest(
            walletId: walletId,
            message: message,
            addressIndex: addressIndex
        )
        return try await post(path: "/messages/sign", body: request)
    }

    /// Verify a message signature.
    public func verifyMessage(message: String, signature: String, address: String) async throws -> Bool {
        let request = VerifyMessageRequest(message: message, signature: signature, address: address)
        let response: VerifyMessageResponse = try await post(path: "/messages/verify", body: request)
        return response.valid
    }

    // MARK: - Balance Operations

    /// Get the balance for an address.
    public func getBalance(address: String) async throws -> Balance {
        return try await get(path: "/addresses/\(address)/balance")
    }

    /// Get UTXOs for an address.
    public func getUTXOs(address: String, minConfirmations: Int = 1) async throws -> [UTXO] {
        return try await get(path: "/addresses/\(address)/utxos?min_confirmations=\(minConfirmations)")
    }

    /// Estimate transaction fee.
    public func estimateFee(priority: Priority = .medium) async throws -> Int64 {
        let response: FeeEstimateResponse = try await get(
            path: "/fees/estimate?priority=\(priority.rawValue)"
        )
        return response.feePerByte
    }

    // MARK: - HTTP Methods

    private func get<T: Decodable>(path: String) async throws -> T {
        return try await executeWithRetry {
            try await self.performRequest(method: "GET", path: path, body: nil as Empty?)
        }
    }

    private func post<T: Decodable, R: Encodable>(path: String, body: R) async throws -> T {
        return try await executeWithRetry {
            try await self.performRequest(method: "POST", path: path, body: body)
        }
    }

    private func performRequest<T: Decodable, R: Encodable>(
        method: String,
        path: String,
        body: R?
    ) async throws -> T {
        guard let url = URL(string: "\(config.endpoint)\(path)") else {
            throw WalletError.invalidResponse
        }

        var request = URLRequest(url: url)
        request.httpMethod = method
        request.setValue("Bearer \(config.apiKey)", forHTTPHeaderField: "Authorization")
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")

        if let body = body {
            do {
                request.httpBody = try encoder.encode(body)
            } catch {
                throw WalletError.encodingError(error)
            }
        }

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

        guard let httpResponse = response as? HTTPURLResponse else {
            throw WalletError.invalidResponse
        }

        if httpResponse.statusCode >= 200 && httpResponse.statusCode < 300 {
            do {
                return try decoder.decode(T.self, from: data)
            } catch {
                throw WalletError.decodingError(error)
            }
        }

        let message = String(data: data, encoding: .utf8) ?? "Unknown error"
        throw WalletError.httpError(statusCode: httpResponse.statusCode, message: message)
    }

    private func executeWithRetry<T>(_ operation: () async throws -> T) async throws -> T {
        var lastError: Error?

        for attempt in 0..<config.retries {
            do {
                return try await operation()
            } catch {
                lastError = error
                if config.debug {
                    print("Attempt \(attempt + 1) failed: \(error)")
                }
                if attempt < config.retries - 1 {
                    try await Task.sleep(nanoseconds: UInt64(1_000_000_000 * (attempt + 1)))
                }
            }
        }

        throw lastError ?? WalletError.invalidResponse
    }
}

// Helper type for empty body
private struct Empty: Encodable {}
