package io.synor.database

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 Database SDK for Kotlin.
 * Multi-model database with Key-Value, Document, Vector, and Time Series stores.
 */
class SynorDatabase(private val config: DatabaseConfig) : AutoCloseable {

    companion object {
        private const val DEFAULT_ENDPOINT = "https://db.synor.io/v1"
        private val gson: Gson = GsonBuilder().create()
    }

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

    val kv = KeyValueStore(this)
    val documents = DocumentStore(this)
    val vectors = VectorStore(this)
    val timeseries = TimeSeriesStore(this)

    constructor(apiKey: String) : this(DatabaseConfig(apiKey))

    // ==================== Key-Value Store ====================

    class KeyValueStore internal constructor(private val db: SynorDatabase) {

        suspend fun get(key: String): Any? {
            val resp = db.request("GET", "/kv/${key.urlEncode()}")
            val map = gson.fromJson<Map<String, Any>>(resp, object : TypeToken<Map<String, Any>>() {}.type)
            return map["value"]
        }

        suspend fun set(key: String, value: Any, ttl: Int? = null) {
            val body = mutableMapOf<String, Any>("key" to key, "value" to value)
            ttl?.let { body["ttl"] = it }
            db.request("PUT", "/kv/${key.urlEncode()}", body)
        }

        suspend fun delete(key: String) {
            db.request("DELETE", "/kv/${key.urlEncode()}")
        }

        suspend fun list(prefix: String): List<KeyValue> {
            val resp = db.request("GET", "/kv?prefix=${prefix.urlEncode()}")
            val result = gson.fromJson<ListResponse<KeyValue>>(resp, object : TypeToken<ListResponse<KeyValue>>() {}.type)
            return result.items ?: emptyList()
        }
    }

    // ==================== Document Store ====================

    class DocumentStore internal constructor(private val db: SynorDatabase) {

        suspend fun create(collection: String, document: Map<String, Any>): String {
            val resp = db.request("POST", "/collections/${collection.urlEncode()}/documents", document)
            val map = gson.fromJson<Map<String, Any>>(resp, object : TypeToken<Map<String, Any>>() {}.type)
            return map["id"] as String
        }

        suspend fun get(collection: String, id: String): Document {
            val resp = db.request("GET", "/collections/${collection.urlEncode()}/documents/${id.urlEncode()}")
            return gson.fromJson(resp, Document::class.java)
        }

        suspend fun update(collection: String, id: String, update: Map<String, Any>) {
            db.request("PATCH", "/collections/${collection.urlEncode()}/documents/${id.urlEncode()}", update)
        }

        suspend fun delete(collection: String, id: String) {
            db.request("DELETE", "/collections/${collection.urlEncode()}/documents/${id.urlEncode()}")
        }

        suspend fun query(collection: String, query: Query): List<Document> {
            val resp = db.request("POST", "/collections/${collection.urlEncode()}/query", query)
            val result = gson.fromJson<DocumentListResponse>(resp, DocumentListResponse::class.java)
            return result.documents ?: emptyList()
        }
    }

    // ==================== Vector Store ====================

    class VectorStore internal constructor(private val db: SynorDatabase) {

        suspend fun upsert(collection: String, vectors: List<VectorEntry>) {
            db.request("POST", "/vectors/${collection.urlEncode()}/upsert", mapOf("vectors" to vectors))
        }

        suspend fun search(collection: String, vector: DoubleArray, k: Int): List<SearchResult> {
            val resp = db.request("POST", "/vectors/${collection.urlEncode()}/search", mapOf("vector" to vector, "k" to k))
            val result = gson.fromJson<SearchListResponse>(resp, SearchListResponse::class.java)
            return result.results ?: emptyList()
        }

        suspend fun delete(collection: String, ids: List<String>) {
            db.request("DELETE", "/vectors/${collection.urlEncode()}", mapOf("ids" to ids))
        }
    }

    // ==================== Time Series Store ====================

    class TimeSeriesStore internal constructor(private val db: SynorDatabase) {

        suspend fun write(series: String, points: List<DataPoint>) {
            db.request("POST", "/timeseries/${series.urlEncode()}/write", mapOf("points" to points))
        }

        suspend fun query(series: String, range: TimeRange, aggregation: Aggregation? = null): List<DataPoint> {
            val body = mutableMapOf<String, Any>("range" to range)
            aggregation?.let { body["aggregation"] = it }
            val resp = db.request("POST", "/timeseries/${series.urlEncode()}/query", body)
            val result = gson.fromJson<DataPointListResponse>(resp, DataPointListResponse::class.java)
            return result.points ?: emptyList()
        }
    }

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

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

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

    suspend fun healthCheck(): Boolean {
        return 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 ====================

    internal suspend fun request(method: String, path: String, body: Any? = null): String {
        if (closed.get()) throw DatabaseException("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 ?: DatabaseException("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() }

            val message = errorMap["message"] as? String ?: "HTTP ${response.status.value}"
            throw DatabaseException(message, errorMap["code"] as? String, response.status.value)
        }

        return responseBody
    }

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

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

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

data class KeyValue(
    val key: String,
    val value: Any?,
    val ttl: Int? = null,
    val createdAt: Long? = null,
    val updatedAt: Long? = null
)

data class Document(
    val id: String,
    val collection: String,
    val data: Map<String, Any>,
    val createdAt: Long,
    val updatedAt: Long
)

data class Query(
    val filter: Map<String, Any>? = null,
    val sort: Map<String, Int>? = null,
    val limit: Int? = null,
    val offset: Int? = null,
    val projection: List<String>? = null
)

data class VectorEntry(
    val id: String,
    val vector: DoubleArray,
    val metadata: Map<String, Any>? = null
)

data class SearchResult(
    val id: String,
    val score: Double,
    val vector: DoubleArray? = null,
    val metadata: Map<String, Any>? = null
)

data class DataPoint(
    val timestamp: Long,
    val value: Double,
    val tags: Map<String, String>? = null
)

data class TimeRange(
    val start: Long,
    val end: Long
)

data class Aggregation(
    val function: String,
    val interval: String
)

// Response types
internal data class ListResponse<T>(val items: List<T>?)
internal data class DocumentListResponse(val documents: List<Document>?)
internal data class SearchListResponse(val results: List<SearchResult>?)
internal data class DataPointListResponse(val points: List<DataPoint>?)

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