package io.synor.mining

import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import io.ktor.client.*
import io.ktor.client.engine.cio.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import kotlinx.coroutines.delay
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicReference

/**
 * Synor Mining SDK for Kotlin.
 * Pool connections, block templates, hashrate stats, and GPU management.
 */
class SynorMining(private val config: MiningConfig) : AutoCloseable {

    companion object {
        private val gson: Gson = GsonBuilder().create()
    }

    private val httpClient = HttpClient(CIO) {
        engine { requestTimeout = config.timeoutSecs * 1000 }
    }
    private val closed = AtomicBoolean(false)
    private val activeConnection = AtomicReference<StratumConnection?>(null)

    constructor(apiKey: String) : this(MiningConfig(apiKey))

    // ==================== Pool Operations ====================

    suspend fun connect(pool: PoolConfig): StratumConnection {
        val body = mutableMapOf<String, Any>("url" to pool.url, "user" to pool.user)
        pool.password?.let { body["password"] = it }
        pool.algorithm?.let { body["algorithm"] = it }
        pool.difficulty?.let { body["difficulty"] = it }
        val resp = request("POST", "/pool/connect", body)
        val conn = gson.fromJson(resp, StratumConnection::class.java)
        activeConnection.set(conn)
        return conn
    }

    suspend fun disconnect() {
        if (activeConnection.get() == null) return
        request("POST", "/pool/disconnect")
        activeConnection.set(null)
    }

    suspend fun getConnection(): StratumConnection {
        val resp = request("GET", "/pool/connection")
        val conn = gson.fromJson(resp, StratumConnection::class.java)
        activeConnection.set(conn)
        return conn
    }

    suspend fun getPoolStats(): PoolStats {
        val resp = request("GET", "/pool/stats")
        return gson.fromJson(resp, PoolStats::class.java)
    }

    fun isConnected(): Boolean {
        val conn = activeConnection.get()
        return conn != null && conn.status == ConnectionStatus.connected
    }

    // ==================== Mining Operations ====================

    suspend fun getBlockTemplate(): BlockTemplate {
        val resp = request("GET", "/mining/template")
        return gson.fromJson(resp, BlockTemplate::class.java)
    }

    suspend fun submitWork(work: MinedWork): SubmitResult {
        val body = mapOf(
            "templateId" to work.templateId,
            "nonce" to work.nonce,
            "extraNonce" to work.extraNonce,
            "timestamp" to work.timestamp,
            "hash" to work.hash
        )
        val resp = request("POST", "/mining/submit", body)
        return gson.fromJson(resp, SubmitResult::class.java)
    }

    suspend fun startMining(algorithm: String? = null) {
        val body = if (algorithm != null) mapOf("algorithm" to algorithm) else null
        request("POST", "/mining/start", body)
    }

    suspend fun stopMining() {
        request("POST", "/mining/stop")
    }

    suspend fun isMining(): Boolean {
        val resp = request("GET", "/mining/status")
        val map = gson.fromJson<Map<String, Any>>(resp, object : TypeToken<Map<String, Any>>() {}.type)
        return map["mining"] == true
    }

    // ==================== Stats Operations ====================

    suspend fun getHashrate(): Hashrate {
        val resp = request("GET", "/stats/hashrate")
        return gson.fromJson(resp, Hashrate::class.java)
    }

    suspend fun getStats(): MiningStats {
        val resp = request("GET", "/stats")
        return gson.fromJson(resp, MiningStats::class.java)
    }

    suspend fun getEarnings(period: TimePeriod? = null): Earnings {
        val path = if (period != null) "/stats/earnings?period=${period.name.lowercase()}" else "/stats/earnings"
        val resp = request("GET", path)
        return gson.fromJson(resp, Earnings::class.java)
    }

    suspend fun getShareStats(): ShareStats {
        val resp = request("GET", "/stats/shares")
        return gson.fromJson(resp, ShareStats::class.java)
    }

    // ==================== Device Operations ====================

    suspend fun listDevices(): List<MiningDevice> {
        val resp = request("GET", "/devices")
        val result = gson.fromJson<DevicesResponse>(resp, DevicesResponse::class.java)
        return result.devices ?: emptyList()
    }

    suspend fun getDevice(deviceId: String): MiningDevice {
        val resp = request("GET", "/devices/${deviceId.urlEncode()}")
        return gson.fromJson(resp, MiningDevice::class.java)
    }

    suspend fun setDeviceConfig(deviceId: String, deviceConfig: DeviceConfig): MiningDevice {
        val body = mutableMapOf<String, Any>("enabled" to deviceConfig.enabled)
        deviceConfig.intensity?.let { body["intensity"] = it }
        deviceConfig.powerLimit?.let { body["powerLimit"] = it }
        deviceConfig.coreClockOffset?.let { body["coreClockOffset"] = it }
        deviceConfig.memoryClockOffset?.let { body["memoryClockOffset"] = it }
        deviceConfig.fanSpeed?.let { body["fanSpeed"] = it }
        val resp = request("PUT", "/devices/${deviceId.urlEncode()}/config", body)
        return gson.fromJson(resp, MiningDevice::class.java)
    }

    suspend fun enableDevice(deviceId: String) {
        setDeviceConfig(deviceId, DeviceConfig(enabled = true))
    }

    suspend fun disableDevice(deviceId: String) {
        setDeviceConfig(deviceId, DeviceConfig(enabled = false))
    }

    // ==================== Worker Operations ====================

    suspend fun listWorkers(): List<WorkerInfo> {
        val resp = request("GET", "/workers")
        val result = gson.fromJson<WorkersResponse>(resp, WorkersResponse::class.java)
        return result.workers ?: emptyList()
    }

    suspend fun getWorker(workerId: String): WorkerInfo {
        val resp = request("GET", "/workers/${workerId.urlEncode()}")
        return gson.fromJson(resp, WorkerInfo::class.java)
    }

    suspend fun removeWorker(workerId: String) {
        request("DELETE", "/workers/${workerId.urlEncode()}")
    }

    // ==================== Algorithm Operations ====================

    suspend fun listAlgorithms(): List<MiningAlgorithm> {
        val resp = request("GET", "/algorithms")
        val result = gson.fromJson<AlgorithmsResponse>(resp, AlgorithmsResponse::class.java)
        return result.algorithms ?: emptyList()
    }

    suspend fun getAlgorithm(name: String): MiningAlgorithm {
        val resp = request("GET", "/algorithms/${name.urlEncode()}")
        return gson.fromJson(resp, MiningAlgorithm::class.java)
    }

    suspend fun switchAlgorithm(algorithm: String) {
        request("POST", "/algorithms/switch", mapOf("algorithm" to algorithm))
    }

    suspend fun getMostProfitable(): MiningAlgorithm {
        val resp = request("GET", "/algorithms/profitable")
        return gson.fromJson(resp, MiningAlgorithm::class.java)
    }

    // ==================== Lifecycle ====================

    override fun close() {
        if (closed.compareAndSet(false, true)) {
            activeConnection.set(null)
            httpClient.close()
        }
    }

    fun isClosed(): Boolean = closed.get()

    suspend fun healthCheck(): Boolean = try {
        val resp = request("GET", "/health")
        val map = gson.fromJson<Map<String, Any>>(resp, object : TypeToken<Map<String, Any>>() {}.type)
        map["status"] == "healthy"
    } catch (e: Exception) { false }

    // ==================== Private Methods ====================

    private suspend fun request(method: String, path: String, body: Any? = null): String {
        if (closed.get()) throw MiningException("Client has been closed")

        var lastError: Exception? = null
        repeat(config.retries) { attempt ->
            try {
                return doRequest(method, path, body)
            } catch (e: Exception) {
                lastError = e
                if (attempt < config.retries - 1) delay((1L shl attempt) * 1000)
            }
        }
        throw lastError ?: MiningException("Unknown error")
    }

    private suspend fun doRequest(method: String, path: String, body: Any?): String {
        val url = "${config.endpoint}$path"
        val response: HttpResponse = httpClient.request(url) {
            this.method = HttpMethod.parse(method)
            header("Authorization", "Bearer ${config.apiKey}")
            header("Content-Type", "application/json")
            header("X-SDK-Version", "kotlin/0.1.0")
            body?.let { setBody(gson.toJson(it)) }
        }

        val responseBody = response.bodyAsText()
        if (response.status.value >= 400) {
            val errorMap = try {
                gson.fromJson<Map<String, Any>>(responseBody, object : TypeToken<Map<String, Any>>() {}.type)
            } catch (e: Exception) { emptyMap() }
            throw MiningException(
                errorMap["message"] as? String ?: "HTTP ${response.status.value}",
                errorMap["code"] as? String,
                response.status.value
            )
        }
        return responseBody
    }

    private fun String.urlEncode(): String = java.net.URLEncoder.encode(this, "UTF-8")
}

// ==================== Types ====================

data class MiningConfig(
    val apiKey: String,
    val endpoint: String = "https://mining.synor.io/v1",
    val timeoutSecs: Long = 60,
    val retries: Int = 3,
    val debug: Boolean = false
)

enum class DeviceType { cpu, gpu_nvidia, gpu_amd, asic }
enum class DeviceStatus { idle, mining, error, offline }
enum class ConnectionStatus { disconnected, connecting, connected, reconnecting }
enum class TimePeriod { hour, day, week, month, all }
enum class SubmitResultStatus { accepted, rejected, stale }

data class PoolConfig(
    val url: String,
    val user: String,
    val password: String? = null,
    val algorithm: String? = null,
    val difficulty: Double? = null
)

data class StratumConnection(
    val id: String,
    val pool: String,
    val status: ConnectionStatus,
    val algorithm: String,
    val difficulty: Double,
    val connectedAt: Long,
    val acceptedShares: Int,
    val rejectedShares: Int,
    val staleShares: Int,
    val lastShareAt: Long? = null
)

data class PoolStats(
    val url: String,
    val workers: Int,
    val hashrate: Double,
    val difficulty: Double,
    val lastBlock: Long,
    val blocksFound24h: Int,
    val luck: Double
)

data class TemplateTransaction(
    val txid: String,
    val data: String,
    val fee: String,
    val weight: Int
)

data class BlockTemplate(
    val id: String,
    val previousBlockHash: String,
    val merkleRoot: String,
    val timestamp: Long,
    val bits: String,
    val height: Long,
    val coinbaseValue: String,
    val transactions: List<TemplateTransaction>,
    val target: String,
    val algorithm: String,
    val extraNonce: String
)

data class MinedWork(
    val templateId: String,
    val nonce: String,
    val extraNonce: String,
    val timestamp: Long,
    val hash: String
)

data class ShareInfo(
    val hash: String,
    val difficulty: Double,
    val timestamp: Long,
    val accepted: Boolean
)

data class SubmitResult(
    val status: SubmitResultStatus,
    val share: ShareInfo,
    val blockFound: Boolean,
    val reason: String? = null,
    val blockHash: String? = null,
    val reward: String? = null
)

data class Hashrate(
    val current: Double,
    val average1h: Double,
    val average24h: Double,
    val peak: Double,
    val unit: String
)

data class ShareStats(
    val accepted: Int,
    val rejected: Int,
    val stale: Int,
    val total: Int,
    val acceptRate: Double
)

data class DeviceTemperature(
    val current: Double,
    val max: Double,
    val throttling: Boolean
)

data class EarningsSnapshot(
    val today: String,
    val yesterday: String,
    val thisWeek: String,
    val thisMonth: String,
    val total: String,
    val currency: String
)

data class MiningStats(
    val hashrate: Hashrate,
    val shares: ShareStats,
    val uptime: Long,
    val efficiency: Double,
    val earnings: EarningsSnapshot,
    val powerConsumption: Double? = null,
    val temperature: DeviceTemperature? = null
)

data class EarningsBreakdown(
    val date: Long,
    val amount: String,
    val blocks: Int,
    val shares: Int,
    val hashrate: Double
)

data class Earnings(
    val period: TimePeriod,
    val startDate: Long,
    val endDate: Long,
    val amount: String,
    val blocks: Int,
    val shares: Int,
    val averageHashrate: Double,
    val currency: String,
    val breakdown: List<EarningsBreakdown>
)

data class MiningDevice(
    val id: String,
    val name: String,
    val type: DeviceType,
    val status: DeviceStatus,
    val hashrate: Double,
    val temperature: Double,
    val fanSpeed: Double,
    val powerDraw: Double,
    val memoryUsed: Long,
    val memoryTotal: Long,
    val driver: String? = null,
    val firmware: String? = null
)

data class DeviceConfig(
    val enabled: Boolean,
    val intensity: Int? = null,
    val powerLimit: Int? = null,
    val coreClockOffset: Int? = null,
    val memoryClockOffset: Int? = null,
    val fanSpeed: Int? = null
)

data class WorkerInfo(
    val id: String,
    val name: String,
    val status: ConnectionStatus,
    val hashrate: Hashrate,
    val shares: ShareStats,
    val devices: List<MiningDevice>,
    val lastSeen: Long,
    val uptime: Long
)

data class MiningAlgorithm(
    val name: String,
    val displayName: String,
    val hashUnit: String,
    val profitability: String,
    val difficulty: Double,
    val blockReward: String,
    val blockTime: Int
)

internal data class DevicesResponse(val devices: List<MiningDevice>?)
internal data class WorkersResponse(val workers: List<WorkerInfo>?)
internal data class AlgorithmsResponse(val algorithms: List<MiningAlgorithm>?)

class MiningException(
    message: String,
    val code: String? = null,
    val statusCode: Int = 0
) : RuntimeException(message)
