import Foundation

/// Synor Mining SDK client for Swift.
///
/// Provides pool connections, block templates, hashrate stats, and GPU management.
public class SynorMining {
    private let config: MiningConfig
    private let session: URLSession
    private let decoder: JSONDecoder
    private let encoder: JSONEncoder
    private var closed = false
    private var activeConnection: StratumConnection?

    public init(config: MiningConfig) {
        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: - Pool Operations

    /// Connect to a mining pool.
    public func connect(pool: PoolConfig) async throws -> StratumConnection {
        var body: [String: Any] = ["url": pool.url, "user": pool.user]
        if let password = pool.password { body["password"] = password }
        if let algorithm = pool.algorithm { body["algorithm"] = algorithm }
        if let difficulty = pool.difficulty { body["difficulty"] = difficulty }
        let conn: StratumConnection = try await post(path: "/pool/connect", body: body)
        activeConnection = conn
        return conn
    }

    /// Disconnect from the pool.
    public func disconnect() async throws {
        guard activeConnection != nil else { return }
        let _: [String: Any] = try await post(path: "/pool/disconnect", body: [:] as [String: String])
        activeConnection = nil
    }

    /// Get current connection.
    public func getConnection() async throws -> StratumConnection {
        let conn: StratumConnection = try await get(path: "/pool/connection")
        activeConnection = conn
        return conn
    }

    /// Get pool stats.
    public func getPoolStats() async throws -> PoolStats {
        return try await get(path: "/pool/stats")
    }

    /// Check if connected.
    public var isConnected: Bool {
        activeConnection?.status == .connected
    }

    // MARK: - Mining Operations

    /// Get block template.
    public func getBlockTemplate() async throws -> BlockTemplate {
        return try await get(path: "/mining/template")
    }

    /// Submit mined work.
    public func submitWork(_ work: MinedWork) async throws -> SubmitResult {
        let body: [String: Any] = [
            "templateId": work.templateId,
            "nonce": work.nonce,
            "extraNonce": work.extraNonce,
            "timestamp": work.timestamp,
            "hash": work.hash
        ]
        return try await post(path: "/mining/submit", body: body)
    }

    /// Start mining.
    public func startMining(algorithm: String? = nil) async throws {
        var body: [String: Any] = [:]
        if let algorithm = algorithm { body["algorithm"] = algorithm }
        let _: [String: Any] = try await post(path: "/mining/start", body: body)
    }

    /// Stop mining.
    public func stopMining() async throws {
        let _: [String: Any] = try await post(path: "/mining/stop", body: [:] as [String: String])
    }

    /// Check if mining.
    public func isMining() async throws -> Bool {
        let response: [String: Bool] = try await get(path: "/mining/status")
        return response["mining"] ?? false
    }

    // MARK: - Stats Operations

    /// Get hashrate.
    public func getHashrate() async throws -> Hashrate {
        return try await get(path: "/stats/hashrate")
    }

    /// Get mining stats.
    public func getStats() async throws -> MiningStats {
        return try await get(path: "/stats")
    }

    /// Get earnings.
    public func getEarnings(period: TimePeriod? = nil) async throws -> Earnings {
        let path = period != nil ? "/stats/earnings?period=\(period!.rawValue)" : "/stats/earnings"
        return try await get(path: path)
    }

    /// Get share stats.
    public func getShareStats() async throws -> ShareStats {
        return try await get(path: "/stats/shares")
    }

    // MARK: - Device Operations

    /// List devices.
    public func listDevices() async throws -> [MiningDevice] {
        let response: DevicesResponse = try await get(path: "/devices")
        return response.devices ?? []
    }

    /// Get device.
    public func getDevice(deviceId: String) async throws -> MiningDevice {
        return try await get(path: "/devices/\(deviceId.urlEncoded)")
    }

    /// Set device config.
    public func setDeviceConfig(deviceId: String, config: DeviceConfig) async throws -> MiningDevice {
        var body: [String: Any] = ["enabled": config.enabled]
        if let intensity = config.intensity { body["intensity"] = intensity }
        if let powerLimit = config.powerLimit { body["powerLimit"] = powerLimit }
        if let coreClockOffset = config.coreClockOffset { body["coreClockOffset"] = coreClockOffset }
        if let memoryClockOffset = config.memoryClockOffset { body["memoryClockOffset"] = memoryClockOffset }
        if let fanSpeed = config.fanSpeed { body["fanSpeed"] = fanSpeed }
        return try await put(path: "/devices/\(deviceId.urlEncoded)/config", body: body)
    }

    /// Enable device.
    public func enableDevice(deviceId: String) async throws {
        _ = try await setDeviceConfig(deviceId: deviceId, config: DeviceConfig(enabled: true))
    }

    /// Disable device.
    public func disableDevice(deviceId: String) async throws {
        _ = try await setDeviceConfig(deviceId: deviceId, config: DeviceConfig(enabled: false))
    }

    // MARK: - Worker Operations

    /// List workers.
    public func listWorkers() async throws -> [WorkerInfo] {
        let response: WorkersResponse = try await get(path: "/workers")
        return response.workers ?? []
    }

    /// Get worker.
    public func getWorker(workerId: String) async throws -> WorkerInfo {
        return try await get(path: "/workers/\(workerId.urlEncoded)")
    }

    /// Remove worker.
    public func removeWorker(workerId: String) async throws {
        let _: [String: Any] = try await delete(path: "/workers/\(workerId.urlEncoded)")
    }

    // MARK: - Algorithm Operations

    /// List algorithms.
    public func listAlgorithms() async throws -> [MiningAlgorithm] {
        let response: AlgorithmsResponse = try await get(path: "/algorithms")
        return response.algorithms ?? []
    }

    /// Get algorithm.
    public func getAlgorithm(name: String) async throws -> MiningAlgorithm {
        return try await get(path: "/algorithms/\(name.urlEncoded)")
    }

    /// Switch algorithm.
    public func switchAlgorithm(_ algorithm: String) async throws {
        let _: [String: Any] = try await post(path: "/algorithms/switch", body: ["algorithm": algorithm])
    }

    /// Get most profitable algorithm.
    public func getMostProfitable() async throws -> MiningAlgorithm {
        return try await get(path: "/algorithms/profitable")
    }

    // MARK: - Lifecycle

    public func close() {
        closed = true
        activeConnection = nil
    }

    public var isClosed: Bool { closed }

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

    // MARK: - Private Methods

    private func get<T: Decodable>(path: String) async throws -> T {
        return try await request(method: "GET", path: path)
    }

    private func post<T: Decodable>(path: String, body: Any) async throws -> T {
        return try await request(method: "POST", path: path, body: body)
    }

    private func put<T: Decodable>(path: String, body: Any) async throws -> T {
        return try await request(method: "PUT", path: path, body: body)
    }

    private func delete<T: Decodable>(path: String) async throws -> T {
        return try await request(method: "DELETE", path: path)
    }

    private func request<T: Decodable>(method: String, path: String, body: Any? = nil) async throws -> T {
        guard !closed else { throw MiningError(message: "Client has been closed") }

        var lastError: Error?
        for attempt in 0..<config.retries {
            do {
                return try await doRequest(method: method, path: path, body: body)
            } catch {
                lastError = error
                if attempt < config.retries - 1 {
                    try await Task.sleep(nanoseconds: UInt64(pow(2.0, Double(attempt))) * 1_000_000_000)
                }
            }
        }
        throw lastError ?? MiningError(message: "Unknown error")
    }

    private func doRequest<T: Decodable>(method: String, path: String, body: Any?) async throws -> T {
        guard let url = URL(string: config.endpoint + path) else { throw MiningError(message: "Invalid URL") }

        var request = URLRequest(url: url)
        request.httpMethod = method
        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) }

        let (data, response) = try await session.data(for: request)
        guard let httpResponse = response as? HTTPURLResponse else { throw MiningError(message: "Invalid response") }

        if httpResponse.statusCode >= 400 {
            let errorInfo = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
            throw MiningError(
                message: errorInfo?["message"] as? String ?? "HTTP \(httpResponse.statusCode)",
                code: errorInfo?["code"] as? String,
                statusCode: httpResponse.statusCode
            )
        }
        return try decoder.decode(T.self, from: data)
    }
}

extension String {
    fileprivate var urlEncoded: String {
        addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? self
    }
}
