package io.synor.governance

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 Governance SDK for Kotlin.
 * Proposals, voting, DAOs, and vesting operations.
 */
class SynorGovernance(private val config: GovernanceConfig) : AutoCloseable {

    companion object {
        private val gson: Gson = GsonBuilder().create()
        private val FINAL_STATUSES = setOf(
            ProposalStatus.passed, ProposalStatus.rejected, ProposalStatus.executed, ProposalStatus.cancelled
        )
    }

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

    constructor(apiKey: String) : this(GovernanceConfig(apiKey))

    // ==================== Proposal Operations ====================

    suspend fun createProposal(proposal: ProposalDraft): Proposal {
        val body = mutableMapOf<String, Any>(
            "title" to proposal.title,
            "description" to proposal.description
        )
        proposal.discussionUrl?.let { body["discussionUrl"] = it }
        proposal.votingStartTime?.let { body["votingStartTime"] = it }
        proposal.votingEndTime?.let { body["votingEndTime"] = it }
        proposal.actions?.let { body["actions"] = it }
        proposal.daoId?.let { body["daoId"] = it }
        val resp = request("POST", "/proposals", body)
        return gson.fromJson(resp, Proposal::class.java)
    }

    suspend fun getProposal(proposalId: String): Proposal {
        val resp = request("GET", "/proposals/${proposalId.urlEncode()}")
        return gson.fromJson(resp, Proposal::class.java)
    }

    suspend fun listProposals(filter: ProposalFilter? = null): List<Proposal> {
        val params = mutableListOf<String>()
        filter?.status?.let { params.add("status=${it.name.lowercase()}") }
        filter?.proposer?.let { params.add("proposer=${it.urlEncode()}") }
        filter?.daoId?.let { params.add("daoId=${it.urlEncode()}") }
        filter?.limit?.let { params.add("limit=$it") }
        filter?.offset?.let { params.add("offset=$it") }

        val path = if (params.isNotEmpty()) "/proposals?${params.joinToString("&")}" else "/proposals"
        val resp = request("GET", path)
        val result = gson.fromJson<ProposalsResponse>(resp, ProposalsResponse::class.java)
        return result.proposals ?: emptyList()
    }

    suspend fun cancelProposal(proposalId: String): Proposal {
        val resp = request("POST", "/proposals/${proposalId.urlEncode()}/cancel")
        return gson.fromJson(resp, Proposal::class.java)
    }

    suspend fun executeProposal(proposalId: String): Proposal {
        val resp = request("POST", "/proposals/${proposalId.urlEncode()}/execute")
        return gson.fromJson(resp, Proposal::class.java)
    }

    suspend fun waitForProposal(proposalId: String, pollIntervalMs: Long = 60000, maxWaitMs: Long = 604800000): Proposal {
        val deadline = System.currentTimeMillis() + maxWaitMs
        while (System.currentTimeMillis() < deadline) {
            val proposal = getProposal(proposalId)
            if (proposal.status in FINAL_STATUSES) return proposal
            delay(pollIntervalMs)
        }
        throw GovernanceException("Timeout waiting for proposal completion")
    }

    // ==================== Voting Operations ====================

    suspend fun vote(proposalId: String, vote: Vote, weight: String? = null): VoteReceipt {
        val body = mutableMapOf<String, Any>("choice" to vote.choice.name.lowercase())
        vote.reason?.let { body["reason"] = it }
        weight?.let { body["weight"] = it }
        val resp = request("POST", "/proposals/${proposalId.urlEncode()}/vote", body)
        return gson.fromJson(resp, VoteReceipt::class.java)
    }

    suspend fun getVotes(proposalId: String): List<VoteReceipt> {
        val resp = request("GET", "/proposals/${proposalId.urlEncode()}/votes")
        val result = gson.fromJson<VotesResponse>(resp, VotesResponse::class.java)
        return result.votes ?: emptyList()
    }

    suspend fun getMyVote(proposalId: String): VoteReceipt {
        val resp = request("GET", "/proposals/${proposalId.urlEncode()}/votes/me")
        return gson.fromJson(resp, VoteReceipt::class.java)
    }

    suspend fun delegate(delegatee: String, amount: String? = null): DelegationReceipt {
        val body = mutableMapOf<String, Any>("delegatee" to delegatee)
        amount?.let { body["amount"] = it }
        val resp = request("POST", "/voting/delegate", body)
        return gson.fromJson(resp, DelegationReceipt::class.java)
    }

    suspend fun undelegate(delegatee: String): DelegationReceipt {
        val resp = request("POST", "/voting/undelegate", mapOf("delegatee" to delegatee))
        return gson.fromJson(resp, DelegationReceipt::class.java)
    }

    suspend fun getVotingPower(address: String): VotingPower {
        val resp = request("GET", "/voting/power/${address.urlEncode()}")
        return gson.fromJson(resp, VotingPower::class.java)
    }

    suspend fun getDelegations(address: String): List<DelegationReceipt> {
        val resp = request("GET", "/voting/delegations/${address.urlEncode()}")
        val result = gson.fromJson<DelegationsResponse>(resp, DelegationsResponse::class.java)
        return result.delegations ?: emptyList()
    }

    // ==================== DAO Operations ====================

    suspend fun createDao(daoConfig: DaoConfig): Dao {
        val body = mutableMapOf<String, Any>(
            "name" to daoConfig.name,
            "description" to daoConfig.description,
            "type" to daoConfig.type.name.lowercase(),
            "votingPeriodDays" to daoConfig.votingPeriodDays,
            "timelockDays" to daoConfig.timelockDays
        )
        daoConfig.tokenAddress?.let { body["tokenAddress"] = it }
        daoConfig.quorumPercent?.let { body["quorumPercent"] = it }
        daoConfig.proposalThreshold?.let { body["proposalThreshold"] = it }
        daoConfig.multisigMembers?.let { body["multisigMembers"] = it }
        daoConfig.multisigThreshold?.let { body["multisigThreshold"] = it }
        val resp = request("POST", "/daos", body)
        return gson.fromJson(resp, Dao::class.java)
    }

    suspend fun getDao(daoId: String): Dao {
        val resp = request("GET", "/daos/${daoId.urlEncode()}")
        return gson.fromJson(resp, Dao::class.java)
    }

    suspend fun listDaos(limit: Int? = null, offset: Int? = null): List<Dao> {
        val params = mutableListOf<String>()
        limit?.let { params.add("limit=$it") }
        offset?.let { params.add("offset=$it") }
        val path = if (params.isNotEmpty()) "/daos?${params.joinToString("&")}" else "/daos"
        val resp = request("GET", path)
        val result = gson.fromJson<DaosResponse>(resp, DaosResponse::class.java)
        return result.daos ?: emptyList()
    }

    suspend fun getDaoTreasury(daoId: String): DaoTreasury {
        val resp = request("GET", "/daos/${daoId.urlEncode()}/treasury")
        return gson.fromJson(resp, DaoTreasury::class.java)
    }

    suspend fun getDaoMembers(daoId: String): List<String> {
        val resp = request("GET", "/daos/${daoId.urlEncode()}/members")
        val result = gson.fromJson<MembersResponse>(resp, MembersResponse::class.java)
        return result.members ?: emptyList()
    }

    // ==================== Vesting Operations ====================

    suspend fun createVestingSchedule(schedule: VestingSchedule): VestingContract {
        val body = mapOf(
            "beneficiary" to schedule.beneficiary,
            "totalAmount" to schedule.totalAmount,
            "startTime" to schedule.startTime,
            "cliffDuration" to schedule.cliffDuration,
            "vestingDuration" to schedule.vestingDuration,
            "revocable" to schedule.revocable
        )
        val resp = request("POST", "/vesting", body)
        return gson.fromJson(resp, VestingContract::class.java)
    }

    suspend fun getVestingContract(contractId: String): VestingContract {
        val resp = request("GET", "/vesting/${contractId.urlEncode()}")
        return gson.fromJson(resp, VestingContract::class.java)
    }

    suspend fun listVestingContracts(beneficiary: String? = null): List<VestingContract> {
        val path = if (beneficiary != null) "/vesting?beneficiary=${beneficiary.urlEncode()}" else "/vesting"
        val resp = request("GET", path)
        val result = gson.fromJson<VestingContractsResponse>(resp, VestingContractsResponse::class.java)
        return result.contracts ?: emptyList()
    }

    suspend fun claimVested(contractId: String): ClaimReceipt {
        val resp = request("POST", "/vesting/${contractId.urlEncode()}/claim")
        return gson.fromJson(resp, ClaimReceipt::class.java)
    }

    suspend fun revokeVesting(contractId: String): VestingContract {
        val resp = request("POST", "/vesting/${contractId.urlEncode()}/revoke")
        return gson.fromJson(resp, VestingContract::class.java)
    }

    suspend fun getReleasableAmount(contractId: String): String {
        val resp = request("GET", "/vesting/${contractId.urlEncode()}/releasable")
        val map = gson.fromJson<Map<String, Any>>(resp, object : TypeToken<Map<String, Any>>() {}.type)
        return map["amount"] as String
    }

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

enum class ProposalStatus { draft, active, passed, rejected, executed, cancelled }
enum class VoteChoice { yes, no, abstain }
enum class DaoType { token, multisig, hybrid }
enum class VestingStatus { pending, active, paused, completed, revoked }

data class ProposalAction(
    val target: String,
    val method: String,
    val data: String,
    val value: String? = null
)

data class ProposalDraft(
    val title: String,
    val description: String,
    val discussionUrl: String? = null,
    val votingStartTime: Long? = null,
    val votingEndTime: Long? = null,
    val actions: List<ProposalAction>? = null,
    val daoId: String? = null
)

data class VoteBreakdown(
    val yes: String,
    val no: String,
    val abstain: String,
    val quorum: String,
    val quorumPercent: Double,
    val quorumReached: Boolean
)

data class Proposal(
    val id: String,
    val title: String,
    val description: String,
    val proposer: String,
    val status: ProposalStatus,
    val createdAt: Long,
    val votingStartTime: Long,
    val votingEndTime: Long,
    val votes: VoteBreakdown,
    val discussionUrl: String? = null,
    val actions: List<ProposalAction>? = null,
    val daoId: String? = null,
    val snapshotBlock: String? = null,
    val executedAt: Long? = null,
    val executedTxHash: String? = null
)

data class ProposalFilter(
    val status: ProposalStatus? = null,
    val proposer: String? = null,
    val daoId: String? = null,
    val limit: Int? = null,
    val offset: Int? = null
)

data class Vote(val choice: VoteChoice, val reason: String? = null)

data class VoteReceipt(
    val id: String,
    val proposalId: String,
    val voter: String,
    val choice: VoteChoice,
    val weight: String,
    val timestamp: Long,
    val txHash: String,
    val reason: String? = null
)

data class VotingPower(
    val address: String,
    val balance: String,
    val delegatedTo: String? = null,
    val delegatedFrom: String,
    val totalPower: String,
    val blockNumber: Long
)

data class DelegationReceipt(
    val id: String,
    val delegator: String,
    val delegatee: String,
    val amount: String,
    val timestamp: Long,
    val txHash: String
)

data class DaoConfig(
    val name: String,
    val description: String,
    val type: DaoType,
    val tokenAddress: String? = null,
    val quorumPercent: String? = null,
    val votingPeriodDays: Int = 7,
    val timelockDays: Int = 2,
    val proposalThreshold: Int? = null,
    val multisigMembers: List<String>? = null,
    val multisigThreshold: Int? = null
)

data class TreasuryAsset(
    val address: String,
    val symbol: String,
    val balance: String,
    val valueUsd: String
)

data class DaoTreasury(
    val address: String,
    val balance: String,
    val currency: String,
    val assets: List<TreasuryAsset>
)

data class Dao(
    val id: String,
    val name: String,
    val description: String,
    val type: DaoType,
    val tokenAddress: String? = null,
    val governorAddress: String,
    val timelockAddress: String,
    val treasury: DaoTreasury,
    val quorumPercent: String,
    val votingPeriodDays: Int,
    val timelockDays: Int,
    val proposalCount: Int,
    val memberCount: Int,
    val createdAt: Long
)

data class VestingSchedule(
    val beneficiary: String,
    val totalAmount: String,
    val startTime: Long,
    val cliffDuration: Long,
    val vestingDuration: Long,
    val revocable: Boolean = false
)

data class VestingContract(
    val id: String,
    val contractAddress: String,
    val beneficiary: String,
    val totalAmount: String,
    val releasedAmount: String,
    val releasableAmount: String,
    val startTime: Long,
    val cliffEnd: Long,
    val vestingEnd: Long,
    val status: VestingStatus,
    val createdAt: Long,
    val txHash: String
)

data class ClaimReceipt(
    val vestingContractId: String,
    val amount: String,
    val txHash: String,
    val timestamp: Long,
    val remainingAmount: String
)

internal data class ProposalsResponse(val proposals: List<Proposal>?)
internal data class VotesResponse(val votes: List<VoteReceipt>?)
internal data class DelegationsResponse(val delegations: List<DelegationReceipt>?)
internal data class DaosResponse(val daos: List<Dao>?)
internal data class MembersResponse(val members: List<String>?)
internal data class VestingContractsResponse(val contracts: List<VestingContract>?)

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