/**
 * Synor Compiler SDK for Kotlin
 *
 * Smart contract compilation, optimization, and ABI generation.
 */
package io.synor.compiler

import kotlinx.coroutines.*
import kotlinx.serialization.*
import kotlinx.serialization.json.*
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import java.time.Duration
import java.util.*
import java.util.concurrent.atomic.AtomicBoolean

// ============================================================================
// Enumerations
// ============================================================================

@Serializable
enum class OptimizationLevel {
    @SerialName("none") NONE,
    @SerialName("basic") BASIC,
    @SerialName("size") SIZE,
    @SerialName("aggressive") AGGRESSIVE;

    fun toApiString(): String = when (this) {
        NONE -> "none"
        BASIC -> "basic"
        SIZE -> "size"
        AGGRESSIVE -> "aggressive"
    }

    companion object {
        fun fromApiString(value: String): OptimizationLevel = when (value) {
            "none" -> NONE
            "basic" -> BASIC
            "size" -> SIZE
            "aggressive" -> AGGRESSIVE
            else -> SIZE
        }
    }
}

// ============================================================================
// Configuration
// ============================================================================

@Serializable
data class StripOptions(
    val stripDebug: Boolean = true,
    val stripProducers: Boolean = true,
    val stripNames: Boolean = true,
    val stripCustom: Boolean = true,
    val preserveSections: List<String> = emptyList(),
    val stripUnused: Boolean = true
)

data class CompilerConfig(
    val apiKey: String,
    val endpoint: String = "https://compiler.synor.io/v1",
    val timeout: Long = 60000,
    val retries: Int = 3,
    val debug: Boolean = false,
    val optimizationLevel: OptimizationLevel = OptimizationLevel.SIZE,
    val stripOptions: StripOptions? = null,
    val maxContractSize: Int = 256 * 1024,
    val useWasmOpt: Boolean = true,
    val validate: Boolean = true,
    val extractMetadata: Boolean = true,
    val generateAbi: Boolean = true
)

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

@Serializable
data class ValidationError(
    val code: String,
    val message: String,
    val location: String? = null
)

@Serializable
data class ValidationResult(
    val valid: Boolean,
    val errors: List<ValidationError> = emptyList(),
    val warnings: List<String> = emptyList(),
    val exportCount: Int = 0,
    val importCount: Int = 0,
    val functionCount: Int = 0,
    val memoryPages: List<Int?> = listOf(0, null)
)

@Serializable
data class ContractMetadata(
    val name: String? = null,
    val version: String? = null,
    val authors: List<String> = emptyList(),
    val description: String? = null,
    val license: String? = null,
    val repository: String? = null,
    val buildTimestamp: Long? = null,
    val rustVersion: String? = null,
    val sdkVersion: String? = null,
    val custom: Map<String, String> = emptyMap()
)

@Serializable
data class TypeInfo(
    val kind: String,
    val size: Int? = null,
    val element: TypeInfo? = null,
    val inner: TypeInfo? = null,
    val elements: List<TypeInfo>? = null,
    val name: String? = null,
    val fields: List<TypeField>? = null
) {
    val typeName: String get() = when (kind) {
        "u8", "u16", "u32", "u64", "u128", "i8", "i16", "i32", "i64", "i128",
        "bool", "string", "bytes", "address", "hash256" -> kind
        "fixedBytes" -> "bytes${size ?: 0}"
        "array" -> "${element?.typeName ?: ""}[]"
        "fixedArray" -> "${element?.typeName ?: ""}[${size ?: 0}]"
        "option" -> "${inner?.typeName ?: ""}?"
        "tuple" -> "(${elements?.joinToString(", ") { it.typeName } ?: ""})"
        "struct" -> name ?: "struct"
        else -> name ?: "unknown"
    }
}

@Serializable
data class TypeField(
    val name: String,
    val type: TypeInfo
)

@Serializable
data class ParamAbi(
    val name: String,
    val type: TypeInfo,
    val indexed: Boolean = false
)

@Serializable
data class FunctionAbi(
    val name: String,
    val selector: String,
    val inputs: List<ParamAbi> = emptyList(),
    val outputs: List<ParamAbi> = emptyList(),
    val view: Boolean = false,
    val payable: Boolean = false,
    val doc: String? = null
)

@Serializable
data class EventAbi(
    val name: String,
    val topic: String,
    val params: List<ParamAbi> = emptyList(),
    val doc: String? = null
)

@Serializable
data class ErrorAbi(
    val name: String,
    val selector: String,
    val params: List<ParamAbi> = emptyList(),
    val doc: String? = null
)

@Serializable
data class ContractAbi(
    val name: String,
    val version: String,
    val functions: List<FunctionAbi> = emptyList(),
    val events: List<EventAbi> = emptyList(),
    val errors: List<ErrorAbi> = emptyList()
) {
    fun findFunction(name: String): FunctionAbi? = functions.find { it.name == name }
    fun findBySelector(selector: String): FunctionAbi? = functions.find { it.selector == selector }
}

@Serializable
data class CompilationResult(
    val contractId: String,
    val code: String,
    val codeHash: String,
    val originalSize: Int,
    val optimizedSize: Int,
    val sizeReduction: Double,
    val metadata: ContractMetadata? = null,
    val abi: ContractAbi? = null,
    val estimatedDeployGas: Long = 0,
    val validation: ValidationResult? = null,
    val warnings: List<String> = emptyList()
) {
    val sizeStats: String get() =
        "Original: $originalSize bytes, Optimized: $optimizedSize bytes, Reduction: ${"%.1f".format(sizeReduction)}%"

    fun decodeCode(): ByteArray = Base64.getDecoder().decode(code)
}

@Serializable
data class SizeBreakdown(
    val code: Int = 0,
    val data: Int = 0,
    val types: Int = 0,
    val functions: Int = 0,
    val memory: Int = 0,
    val table: Int = 0,
    val exports: Int = 0,
    val imports: Int = 0,
    val custom: Int = 0,
    val total: Int = 0
)

@Serializable
data class FunctionAnalysis(
    val name: String,
    val size: Int,
    val instructionCount: Int,
    val localCount: Int,
    val exported: Boolean,
    val estimatedGas: Long
)

@Serializable
data class ImportAnalysis(
    val module: String,
    val name: String,
    val kind: String,
    val signature: String? = null
)

@Serializable
data class SecurityIssue(
    val severity: String,
    val type: String,
    val description: String,
    val location: String? = null
)

@Serializable
data class SecurityAnalysis(
    val score: Int,
    val issues: List<SecurityIssue> = emptyList(),
    val recommendations: List<String> = emptyList()
)

@Serializable
data class GasAnalysis(
    val deploymentGas: Long,
    val functionGas: Map<String, Long> = emptyMap(),
    val memoryInitGas: Long = 0,
    val dataSectionGas: Long = 0
)

@Serializable
data class ContractAnalysis(
    val sizeBreakdown: SizeBreakdown,
    val functions: List<FunctionAnalysis> = emptyList(),
    val imports: List<ImportAnalysis> = emptyList(),
    val security: SecurityAnalysis? = null,
    val gasAnalysis: GasAnalysis? = null
)

class CompilerException(
    message: String,
    val code: String? = null,
    val httpStatus: Int? = null
) : RuntimeException(message)

// ============================================================================
// Client
// ============================================================================

class SynorCompiler(private val config: CompilerConfig) : AutoCloseable {
    private val httpClient = HttpClient.newBuilder()
        .connectTimeout(Duration.ofMillis(config.timeout))
        .build()
    private val json = Json { ignoreUnknownKeys = true; isLenient = true }
    private val closed = AtomicBoolean(false)

    val contracts = ContractsClient(this)
    val abi = AbiClient(this)
    val analysis = AnalysisClient(this)
    val validation = ValidationClient(this)

    val defaultOptimizationLevel: OptimizationLevel get() = config.optimizationLevel

    suspend fun healthCheck(): Boolean = try {
        val result = get<Map<String, String>>("/health")
        result["status"] == "healthy"
    } catch (e: Exception) {
        false
    }

    suspend fun getInfo(): Map<String, Any> = get("/info")

    override fun close() {
        closed.set(true)
    }

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

    suspend fun compile(
        wasm: ByteArray,
        optimizationLevel: OptimizationLevel? = null,
        stripOptions: StripOptions? = null,
        useWasmOpt: Boolean? = null,
        validate: Boolean? = null,
        extractMetadata: Boolean? = null,
        generateAbi: Boolean? = null
    ): CompilationResult {
        val wasmBase64 = Base64.getEncoder().encodeToString(wasm)
        val body = buildJsonObject {
            put("wasm", wasmBase64)
            put("optimization_level", (optimizationLevel ?: config.optimizationLevel).toApiString())
            put("use_wasm_opt", useWasmOpt ?: config.useWasmOpt)
            put("validate", validate ?: config.validate)
            put("extract_metadata", extractMetadata ?: config.extractMetadata)
            put("generate_abi", generateAbi ?: config.generateAbi)
            stripOptions?.let {
                put("strip_options", json.encodeToJsonElement(it))
            }
        }
        return post("/compile", body)
    }

    internal suspend inline fun <reified T> get(path: String): T {
        checkClosed()
        val request = HttpRequest.newBuilder()
            .uri(URI.create("${config.endpoint}$path"))
            .header("Authorization", "Bearer ${config.apiKey}")
            .header("Content-Type", "application/json")
            .header("X-SDK-Version", "kotlin/0.1.0")
            .GET()
            .timeout(Duration.ofMillis(config.timeout))
            .build()

        return withContext(Dispatchers.IO) {
            val response = httpClient.send(request, HttpResponse.BodyHandlers.ofString())
            handleResponse(response)
        }
    }

    internal suspend inline fun <reified T> post(path: String, body: JsonElement): T {
        checkClosed()
        val request = HttpRequest.newBuilder()
            .uri(URI.create("${config.endpoint}$path"))
            .header("Authorization", "Bearer ${config.apiKey}")
            .header("Content-Type", "application/json")
            .header("X-SDK-Version", "kotlin/0.1.0")
            .POST(HttpRequest.BodyPublishers.ofString(body.toString()))
            .timeout(Duration.ofMillis(config.timeout))
            .build()

        return withContext(Dispatchers.IO) {
            val response = httpClient.send(request, HttpResponse.BodyHandlers.ofString())
            handleResponse(response)
        }
    }

    private inline fun <reified T> handleResponse(response: HttpResponse<String>): T {
        if (response.statusCode() >= 400) {
            val error = try {
                json.decodeFromString<Map<String, String>>(response.body())
            } catch (e: Exception) {
                mapOf("message" to if (response.body().isEmpty()) "HTTP ${response.statusCode()}" else response.body())
            }
            throw CompilerException(
                error["message"] ?: "HTTP ${response.statusCode()}",
                error["code"],
                response.statusCode()
            )
        }
        return json.decodeFromString(response.body())
    }

    private fun checkClosed() {
        if (closed.get()) {
            throw CompilerException("Client has been closed", "CLIENT_CLOSED")
        }
    }
}

class ContractsClient(private val compiler: SynorCompiler) {
    suspend fun compile(wasm: ByteArray, vararg opts: Pair<String, Any?>): CompilationResult =
        compiler.compile(wasm)

    suspend fun compileDev(wasm: ByteArray): CompilationResult =
        compiler.compile(wasm, OptimizationLevel.NONE, null, false, true)

    suspend fun compileProduction(wasm: ByteArray): CompilationResult =
        compiler.compile(wasm, OptimizationLevel.AGGRESSIVE, null, true, true, true, true)

    suspend fun get(contractId: String): CompilationResult =
        compiler.get("/contracts/$contractId")

    suspend fun list(limit: Int? = null, offset: Int? = null): List<CompilationResult> {
        val params = mutableListOf<String>()
        limit?.let { params.add("limit=$it") }
        offset?.let { params.add("offset=$it") }
        val query = if (params.isNotEmpty()) "?${params.joinToString("&")}" else ""
        val result: Map<String, List<CompilationResult>> = compiler.get("/contracts$query")
        return result["contracts"] ?: emptyList()
    }

    suspend fun getCode(contractId: String): ByteArray {
        val result: Map<String, String> = compiler.get("/contracts/$contractId/code")
        return Base64.getDecoder().decode(result["code"] ?: "")
    }
}

class AbiClient(private val compiler: SynorCompiler) {
    suspend fun extract(wasm: ByteArray): ContractAbi {
        val wasmBase64 = Base64.getEncoder().encodeToString(wasm)
        return compiler.post("/abi/extract", buildJsonObject { put("wasm", wasmBase64) })
    }

    suspend fun get(contractId: String): ContractAbi =
        compiler.get("/contracts/$contractId/abi")

    suspend fun encodeCall(functionAbi: FunctionAbi, args: List<Any>): String {
        val body = buildJsonObject {
            put("function", Json.encodeToJsonElement(functionAbi))
            put("args", Json.encodeToJsonElement(args))
        }
        val result: Map<String, String> = compiler.post("/abi/encode", body)
        return result["data"] ?: ""
    }
}

class AnalysisClient(private val compiler: SynorCompiler) {
    suspend fun analyze(wasm: ByteArray): ContractAnalysis {
        val wasmBase64 = Base64.getEncoder().encodeToString(wasm)
        return compiler.post("/analysis", buildJsonObject { put("wasm", wasmBase64) })
    }

    suspend fun get(contractId: String): ContractAnalysis =
        compiler.get("/contracts/$contractId/analysis")

    suspend fun extractMetadata(wasm: ByteArray): ContractMetadata {
        val wasmBase64 = Base64.getEncoder().encodeToString(wasm)
        return compiler.post("/analysis/metadata", buildJsonObject { put("wasm", wasmBase64) })
    }

    suspend fun estimateDeployGas(wasm: ByteArray): Long {
        val wasmBase64 = Base64.getEncoder().encodeToString(wasm)
        val result: Map<String, Long> = compiler.post("/analysis/estimate-gas", buildJsonObject { put("wasm", wasmBase64) })
        return result["gas"] ?: 0
    }

    suspend fun securityScan(wasm: ByteArray): SecurityAnalysis {
        val wasmBase64 = Base64.getEncoder().encodeToString(wasm)
        val result: Map<String, SecurityAnalysis> = compiler.post("/analysis/security", buildJsonObject { put("wasm", wasmBase64) })
        return result["security"] ?: SecurityAnalysis(0)
    }
}

class ValidationClient(private val compiler: SynorCompiler) {
    suspend fun validate(wasm: ByteArray): ValidationResult {
        val wasmBase64 = Base64.getEncoder().encodeToString(wasm)
        return compiler.post("/validate", buildJsonObject { put("wasm", wasmBase64) })
    }

    suspend fun isValid(wasm: ByteArray): Boolean = validate(wasm).valid

    suspend fun getErrors(wasm: ByteArray): List<String> =
        validate(wasm).errors.map { it.message }

    suspend fun validateExports(wasm: ByteArray, requiredExports: List<String>): Boolean {
        val wasmBase64 = Base64.getEncoder().encodeToString(wasm)
        val body = buildJsonObject {
            put("wasm", wasmBase64)
            put("required_exports", Json.encodeToJsonElement(requiredExports))
        }
        val result: Map<String, Boolean> = compiler.post("/validate/exports", body)
        return result["valid"] ?: false
    }

    suspend fun validateMemory(wasm: ByteArray, maxPages: Int): Boolean {
        val wasmBase64 = Base64.getEncoder().encodeToString(wasm)
        val body = buildJsonObject {
            put("wasm", wasmBase64)
            put("max_pages", maxPages)
        }
        val result: Map<String, Boolean> = compiler.post("/validate/memory", body)
        return result["valid"] ?: false
    }
}

// Constants
const val MAX_CONTRACT_SIZE = 256 * 1024
const val MAX_MEMORY_PAGES = 16
