package io.synor.hosting

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 Hosting SDK for Kotlin.
 * Decentralized web hosting with domain management, DNS, and deployments.
 */
class SynorHosting(private val config: HostingConfig) : 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(HostingConfig(apiKey))

    // ==================== Domain Operations ====================

    suspend fun checkAvailability(name: String): DomainAvailability {
        val resp = request("GET", "/domains/check/${name.urlEncode()}")
        return gson.fromJson(resp, DomainAvailability::class.java)
    }

    suspend fun registerDomain(name: String, options: RegisterDomainOptions? = null): Domain {
        val body = mutableMapOf<String, Any>("name" to name)
        options?.years?.let { body["years"] = it }
        options?.autoRenew?.let { body["autoRenew"] = it }
        val resp = request("POST", "/domains", body)
        return gson.fromJson(resp, Domain::class.java)
    }

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

    suspend fun listDomains(): List<Domain> {
        val resp = request("GET", "/domains")
        val result = gson.fromJson<DomainsResponse>(resp, DomainsResponse::class.java)
        return result.domains ?: emptyList()
    }

    suspend fun updateDomainRecord(name: String, record: DomainRecord): Domain {
        val resp = request("PUT", "/domains/${name.urlEncode()}/record", record)
        return gson.fromJson(resp, Domain::class.java)
    }

    suspend fun resolveDomain(name: String): DomainRecord {
        val resp = request("GET", "/domains/${name.urlEncode()}/resolve")
        return gson.fromJson(resp, DomainRecord::class.java)
    }

    suspend fun renewDomain(name: String, years: Int): Domain {
        val resp = request("POST", "/domains/${name.urlEncode()}/renew", mapOf("years" to years))
        return gson.fromJson(resp, Domain::class.java)
    }

    // ==================== DNS Operations ====================

    suspend fun getDnsZone(domain: String): DnsZone {
        val resp = request("GET", "/dns/${domain.urlEncode()}")
        return gson.fromJson(resp, DnsZone::class.java)
    }

    suspend fun setDnsRecords(domain: String, records: List<DnsRecord>): DnsZone {
        val resp = request("PUT", "/dns/${domain.urlEncode()}", mapOf("records" to records))
        return gson.fromJson(resp, DnsZone::class.java)
    }

    suspend fun addDnsRecord(domain: String, record: DnsRecord): DnsZone {
        val resp = request("POST", "/dns/${domain.urlEncode()}/records", record)
        return gson.fromJson(resp, DnsZone::class.java)
    }

    suspend fun deleteDnsRecord(domain: String, recordType: String, name: String): DnsZone {
        val path = "/dns/${domain.urlEncode()}/records/$recordType/${name.urlEncode()}"
        val resp = request("DELETE", path)
        return gson.fromJson(resp, DnsZone::class.java)
    }

    // ==================== Deployment Operations ====================

    suspend fun deploy(cid: String, options: DeployOptions? = null): Deployment {
        val body = mutableMapOf<String, Any>("cid" to cid)
        options?.domain?.let { body["domain"] = it }
        options?.subdomain?.let { body["subdomain"] = it }
        options?.spa?.let { body["spa"] = it }
        options?.cleanUrls?.let { body["cleanUrls"] = it }
        val resp = request("POST", "/deployments", body)
        return gson.fromJson(resp, Deployment::class.java)
    }

    suspend fun getDeployment(id: String): Deployment {
        val resp = request("GET", "/deployments/${id.urlEncode()}")
        return gson.fromJson(resp, Deployment::class.java)
    }

    suspend fun listDeployments(domain: String? = null): List<Deployment> {
        val path = domain?.let { "/deployments?domain=${it.urlEncode()}" } ?: "/deployments"
        val resp = request("GET", path)
        val result = gson.fromJson<DeploymentsResponse>(resp, DeploymentsResponse::class.java)
        return result.deployments ?: emptyList()
    }

    suspend fun rollback(domain: String, deploymentId: String): Deployment {
        val resp = request("POST", "/deployments/${deploymentId.urlEncode()}/rollback", mapOf("domain" to domain))
        return gson.fromJson(resp, Deployment::class.java)
    }

    suspend fun deleteDeployment(id: String) {
        request("DELETE", "/deployments/${id.urlEncode()}")
    }

    // ==================== SSL Operations ====================

    suspend fun provisionSsl(domain: String, options: ProvisionSslOptions? = null): Certificate {
        val resp = request("POST", "/ssl/${domain.urlEncode()}", options)
        return gson.fromJson(resp, Certificate::class.java)
    }

    suspend fun getCertificate(domain: String): Certificate {
        val resp = request("GET", "/ssl/${domain.urlEncode()}")
        return gson.fromJson(resp, Certificate::class.java)
    }

    suspend fun renewCertificate(domain: String): Certificate {
        val resp = request("POST", "/ssl/${domain.urlEncode()}/renew")
        return gson.fromJson(resp, Certificate::class.java)
    }

    suspend fun deleteCertificate(domain: String) {
        request("DELETE", "/ssl/${domain.urlEncode()}")
    }

    // ==================== Site Configuration ====================

    suspend fun getSiteConfig(domain: String): SiteConfig {
        val resp = request("GET", "/sites/${domain.urlEncode()}/config")
        return gson.fromJson(resp, SiteConfig::class.java)
    }

    suspend fun updateSiteConfig(domain: String, config: Map<String, Any>): SiteConfig {
        val resp = request("PATCH", "/sites/${domain.urlEncode()}/config", config)
        return gson.fromJson(resp, SiteConfig::class.java)
    }

    suspend fun purgeCache(domain: String, paths: List<String>? = null): Long {
        val body = paths?.let { mapOf("paths" to it) }
        val resp = request("DELETE", "/sites/${domain.urlEncode()}/cache", body)
        val map = gson.fromJson<Map<String, Any>>(resp, object : TypeToken<Map<String, Any>>() {}.type)
        return (map["purged"] as Number).toLong()
    }

    // ==================== Analytics ====================

    suspend fun getAnalytics(domain: String, options: AnalyticsOptions? = null): AnalyticsData {
        val params = mutableListOf<String>()
        options?.period?.let { params.add("period=${it.urlEncode()}") }
        options?.start?.let { params.add("start=${it.urlEncode()}") }
        options?.end?.let { params.add("end=${it.urlEncode()}") }
        val path = if (params.isNotEmpty()) {
            "/sites/${domain.urlEncode()}/analytics?${params.joinToString("&")}"
        } else {
            "/sites/${domain.urlEncode()}/analytics"
        }
        val resp = request("GET", path)
        return gson.fromJson(resp, AnalyticsData::class.java)
    }

    // ==================== 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 HostingException("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 ?: HostingException("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 HostingException(
                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 HostingConfig(
    val apiKey: String,
    val endpoint: String = "https://hosting.synor.io/v1",
    val timeoutSecs: Long = 60,
    val retries: Int = 3,
    val debug: Boolean = false
)

enum class DomainStatus { pending, active, expired, suspended }
enum class DeploymentStatus { pending, building, deploying, active, failed, inactive }
enum class CertificateStatus { pending, issued, expired, revoked }
enum class DnsRecordType { A, AAAA, CNAME, TXT, MX, NS, SRV, CAA }

data class Domain(
    val name: String,
    val status: DomainStatus,
    val owner: String,
    val registeredAt: Long,
    val expiresAt: Long,
    val autoRenew: Boolean,
    val records: DomainRecord? = null
)

data class DomainRecord(
    val cid: String? = null,
    val ipv4: List<String>? = null,
    val ipv6: List<String>? = null,
    val cname: String? = null,
    val txt: List<String>? = null,
    val metadata: Map<String, String>? = null
)

data class DomainAvailability(
    val name: String,
    val available: Boolean,
    val price: Double? = null,
    val premium: Boolean = false
)

data class RegisterDomainOptions(
    val years: Int? = null,
    val autoRenew: Boolean? = null
)

data class DnsRecord(
    val type: DnsRecordType,
    val name: String,
    val value: String,
    val ttl: Int = 3600,
    val priority: Int? = null
)

data class DnsZone(
    val domain: String,
    val records: List<DnsRecord>,
    val updatedAt: Long
)

data class Deployment(
    val id: String,
    val domain: String,
    val cid: String,
    val status: DeploymentStatus,
    val url: String,
    val createdAt: Long,
    val updatedAt: Long,
    val buildLogs: String? = null,
    val errorMessage: String? = null
)

data class DeployOptions(
    val domain: String? = null,
    val subdomain: String? = null,
    val spa: Boolean? = null,
    val cleanUrls: Boolean? = null,
    val trailingSlash: Boolean? = null
)

data class Certificate(
    val domain: String,
    val status: CertificateStatus,
    val autoRenew: Boolean,
    val issuer: String,
    val issuedAt: Long? = null,
    val expiresAt: Long? = null,
    val fingerprint: String? = null
)

data class ProvisionSslOptions(
    val includeWww: Boolean? = null,
    val autoRenew: Boolean? = null
)

data class SiteConfig(
    val domain: String,
    val cid: String? = null,
    val headers: Map<String, String>? = null,
    val redirects: List<RedirectRule>? = null,
    val errorPages: Map<String, String>? = null,
    val spa: Boolean = false,
    val cleanUrls: Boolean = true,
    val trailingSlash: Boolean = false
)

data class RedirectRule(
    val source: String,
    val destination: String,
    val statusCode: Int = 301,
    val permanent: Boolean = true
)

data class AnalyticsData(
    val domain: String,
    val period: String,
    val pageViews: Long,
    val uniqueVisitors: Long,
    val bandwidth: Long,
    val topPages: List<PageView>,
    val topReferrers: List<Referrer>,
    val topCountries: List<Country>
)

data class PageView(val path: String, val views: Long)
data class Referrer(val referrer: String, val count: Long)
data class Country(val country: String, val count: Long)

data class AnalyticsOptions(
    val period: String? = null,
    val start: String? = null,
    val end: String? = null
)

internal data class DomainsResponse(val domains: List<Domain>?)
internal data class DeploymentsResponse(val deployments: List<Deployment>?)

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