package io.synor.economics

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

/**
 * Synor Economics SDK for Kotlin.
 * Pricing, billing, staking, and discount operations.
 */
class SynorEconomics(private val config: EconomicsConfig) : AutoCloseable {

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

    private val httpClient = HttpClient(CIO) {
        engine { requestTimeout = config.timeoutSecs * 1000 }
    }
    private val closed = AtomicBoolean(false)

    constructor(apiKey: String) : this(EconomicsConfig(apiKey))

    // ==================== Pricing Operations ====================

    suspend fun getPrice(service: ServiceType, usage: UsageMetrics): Price {
        val body = mapOf("service" to service.name.lowercase(), "usage" to usage)
        val resp = request("POST", "/pricing/calculate", body)
        return gson.fromJson(resp, Price::class.java)
    }

    suspend fun estimateCost(plan: UsagePlan): CostEstimate {
        val body = mapOf(
            "service" to plan.service.name.lowercase(),
            "estimatedUsage" to plan.estimatedUsage,
            "period" to plan.period.name.lowercase()
        )
        val resp = request("POST", "/pricing/estimate", body)
        return gson.fromJson(resp, CostEstimate::class.java)
    }

    suspend fun getPricingTiers(service: ServiceType): List<PricingTier> {
        val resp = request("GET", "/pricing/tiers/${service.name.lowercase()}")
        val result = gson.fromJson<TiersResponse>(resp, TiersResponse::class.java)
        return result.tiers ?: emptyList()
    }

    // ==================== Billing Operations ====================

    suspend fun getUsage(period: BillingPeriod? = null): Usage {
        val path = if (period != null) "/billing/usage?period=${period.name.lowercase()}" else "/billing/usage"
        val resp = request("GET", path)
        return gson.fromJson(resp, Usage::class.java)
    }

    suspend fun getUsageByDateRange(startDate: Long, endDate: Long): Usage {
        val resp = request("GET", "/billing/usage?startDate=$startDate&endDate=$endDate")
        return gson.fromJson(resp, Usage::class.java)
    }

    suspend fun getInvoices(): List<Invoice> {
        val resp = request("GET", "/billing/invoices")
        val result = gson.fromJson<InvoicesResponse>(resp, InvoicesResponse::class.java)
        return result.invoices ?: emptyList()
    }

    suspend fun getInvoice(invoiceId: String): Invoice {
        val resp = request("GET", "/billing/invoices/${invoiceId.urlEncode()}")
        return gson.fromJson(resp, Invoice::class.java)
    }

    suspend fun downloadInvoice(invoiceId: String): ByteArray {
        val resp = request("GET", "/billing/invoices/${invoiceId.urlEncode()}/pdf")
        val map = gson.fromJson<Map<String, Any>>(resp, object : TypeToken<Map<String, Any>>() {}.type)
        return java.util.Base64.getDecoder().decode(map["data"] as String)
    }

    suspend fun getBalance(): AccountBalance {
        val resp = request("GET", "/billing/balance")
        return gson.fromJson(resp, AccountBalance::class.java)
    }

    suspend fun addCredits(amount: String, paymentMethod: String) {
        request("POST", "/billing/credits", mapOf("amount" to amount, "paymentMethod" to paymentMethod))
    }

    // ==================== Staking Operations ====================

    suspend fun stake(amount: String, durationDays: Int? = null): StakeReceipt {
        val body = mutableMapOf<String, Any>("amount" to amount)
        durationDays?.let { body["durationDays"] = it }
        val resp = request("POST", "/staking/stake", body)
        return gson.fromJson(resp, StakeReceipt::class.java)
    }

    suspend fun unstake(stakeId: String): UnstakeReceipt {
        val resp = request("POST", "/staking/unstake/${stakeId.urlEncode()}")
        return gson.fromJson(resp, UnstakeReceipt::class.java)
    }

    suspend fun getStakes(): List<StakeInfo> {
        val resp = request("GET", "/staking/stakes")
        val result = gson.fromJson<StakesResponse>(resp, StakesResponse::class.java)
        return result.stakes ?: emptyList()
    }

    suspend fun getStake(stakeId: String): StakeInfo {
        val resp = request("GET", "/staking/stakes/${stakeId.urlEncode()}")
        return gson.fromJson(resp, StakeInfo::class.java)
    }

    suspend fun getStakingRewards(): Rewards {
        val resp = request("GET", "/staking/rewards")
        return gson.fromJson(resp, Rewards::class.java)
    }

    suspend fun claimRewards(): String {
        val resp = request("POST", "/staking/rewards/claim")
        val map = gson.fromJson<Map<String, Any>>(resp, object : TypeToken<Map<String, Any>>() {}.type)
        return map["txHash"] as String
    }

    suspend fun getCurrentApy(): Double {
        val resp = request("GET", "/staking/apy")
        val map = gson.fromJson<Map<String, Any>>(resp, object : TypeToken<Map<String, Any>>() {}.type)
        return (map["apy"] as Number).toDouble()
    }

    // ==================== Discount Operations ====================

    suspend fun applyDiscount(code: String): AppliedDiscount {
        val resp = request("POST", "/discounts/apply", mapOf("code" to code))
        return gson.fromJson(resp, AppliedDiscount::class.java)
    }

    suspend fun getAvailableDiscounts(): List<Discount> {
        val resp = request("GET", "/discounts/available")
        val result = gson.fromJson<DiscountsResponse>(resp, DiscountsResponse::class.java)
        return result.discounts ?: emptyList()
    }

    suspend fun validateDiscount(code: String): Discount {
        val resp = request("GET", "/discounts/validate/${code.urlEncode()}")
        return gson.fromJson(resp, Discount::class.java)
    }

    suspend fun getAppliedDiscounts(): List<AppliedDiscount> {
        val resp = request("GET", "/discounts/applied")
        val result = gson.fromJson<AppliedDiscountsResponse>(resp, AppliedDiscountsResponse::class.java)
        return result.discounts ?: emptyList()
    }

    suspend fun removeDiscount(discountId: String) {
        request("DELETE", "/discounts/${discountId.urlEncode()}")
    }

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

    override fun close() {
        closed.set(true)
        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 EconomicsException("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 ?: EconomicsException("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 EconomicsException(
                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 EconomicsConfig(
    val apiKey: String,
    val endpoint: String = "https://economics.synor.io/v1",
    val timeoutSecs: Long = 60,
    val retries: Int = 3,
    val debug: Boolean = false
)

enum class ServiceType { compute, storage, database, hosting, bridge, mining, rpc }
enum class BillingPeriod { daily, weekly, monthly, yearly }
enum class StakeStatus { active, unstaking, unlocked, slashed }
enum class DiscountType { percentage, fixed, volume }

data class UsageMetrics(
    val computeHours: Double = 0.0,
    val storageGb: Double = 0.0,
    val requests: Long = 0,
    val bandwidthGb: Double = 0.0,
    val gpuHours: Double = 0.0
)

data class PricingTier(
    val name: String,
    val upTo: Double,
    val pricePerUnit: Double,
    val unit: String
)

data class Price(
    val service: ServiceType,
    val amount: String,
    val currency: String,
    val amountUsd: String,
    val breakdown: List<PricingTier>? = null,
    val discount: String? = null,
    val finalAmount: String
)

data class UsagePlan(
    val service: ServiceType,
    val estimatedUsage: UsageMetrics,
    val period: BillingPeriod
)

data class CostBreakdown(
    val category: String,
    val amount: String,
    val percentage: Double
)

data class CostEstimate(
    val service: ServiceType,
    val period: BillingPeriod,
    val estimatedCost: String,
    val estimatedCostUsd: String,
    val currency: String,
    val confidenceLevel: String,
    val breakdown: List<CostBreakdown>? = null,
    val savingsFromStaking: String? = null
)

data class UsageRecord(
    val service: ServiceType,
    val resource: String,
    val quantity: Double,
    val unit: String,
    val timestamp: Long,
    val cost: String
)

data class Usage(
    val period: BillingPeriod,
    val startDate: Long,
    val endDate: Long,
    val records: List<UsageRecord>,
    val totalCost: String,
    val currency: String
)

data class InvoiceLineItem(
    val description: String,
    val quantity: Double,
    val unit: String,
    val unitPrice: String,
    val amount: String
)

data class Invoice(
    val id: String,
    val number: String,
    val periodStart: Long,
    val periodEnd: Long,
    val subtotal: String,
    val tax: String,
    val discount: String,
    val total: String,
    val currency: String,
    val status: String,
    val dueDate: Long? = null,
    val paidAt: Long? = null,
    val lineItems: List<InvoiceLineItem>,
    val pdfUrl: String? = null
)

data class AccountBalance(
    val available: String,
    val pending: String,
    val reserved: String,
    val staked: String,
    val currency: String,
    val lastUpdated: Long
)

data class StakeReceipt(
    val id: String,
    val txHash: String,
    val amount: String,
    val startDate: Long,
    val endDate: Long,
    val apy: Double,
    val status: StakeStatus
)

data class StakeInfo(
    val id: String,
    val amount: String,
    val status: StakeStatus,
    val startDate: Long,
    val endDate: Long,
    val apy: Double,
    val earnedRewards: String,
    val pendingRewards: String,
    val unlockDate: Long? = null,
    val discountPercent: Int = 0
)

data class UnstakeReceipt(
    val id: String,
    val txHash: String,
    val amount: String,
    val initiatedAt: Long,
    val availableAt: Long,
    val penalty: String? = null
)

data class RewardRecord(
    val date: Long,
    val amount: String,
    val type: String,
    val txHash: String? = null
)

data class Rewards(
    val totalEarned: String,
    val pendingClaim: String,
    val lastClaimDate: String? = null,
    val currentApy: Double,
    val history: List<RewardRecord>
)

data class Discount(
    val id: String,
    val code: String,
    val type: DiscountType,
    val value: String,
    val validFrom: Long? = null,
    val validUntil: Long? = null,
    val usageLimit: Int? = null,
    val usageCount: Int = 0,
    val applicableServices: List<ServiceType>? = null,
    val minimumSpend: String? = null,
    val active: Boolean = true
)

data class AppliedDiscount(
    val discount: Discount,
    val savedAmount: String,
    val appliedAt: Long
)

internal data class TiersResponse(val tiers: List<PricingTier>?)
internal data class InvoicesResponse(val invoices: List<Invoice>?)
internal data class StakesResponse(val stakes: List<StakeInfo>?)
internal data class DiscountsResponse(val discounts: List<Discount>?)
internal data class AppliedDiscountsResponse(val discounts: List<AppliedDiscount>?)

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