/*
 * This file is part of LibEuFin.
 * Copyright (C) 2024-2025 Taler Systems S.A.

 * LibEuFin is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation; either version 3, or
 * (at your option) any later version.

 * LibEuFin is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General
 * Public License for more details.

 * You should have received a copy of the GNU Affero General Public
 * License along with LibEuFin; see the file COPYING.  If not, see
 * <http://www.gnu.org/licenses/>
 */

package tech.libeufin.ebisync.db

import tech.libeufin.common.*
import tech.libeufin.common.db.*
import java.sql.*
import java.time.Instant
import kotlinx.serialization.Contextual
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encodeToString
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.Json
import kotlinx.serialization.modules.SerializersModule

object InstantSerialize : KSerializer<Instant> {
    override val descriptor: SerialDescriptor =
        PrimitiveSerialDescriptor("Instant", PrimitiveKind.LONG)
    override fun serialize(encoder: Encoder, value: Instant) =
        encoder.encodeLong(value.micros())
    override fun deserialize(decoder: Decoder): Instant =
        decoder.decodeLong().asInstant()
}

val JSON = Json {
    this.serializersModule = SerializersModule {
        contextual(Instant::class) { InstantSerialize }
    }
}

inline fun <reified T> ResultSet.getJson(name: String): T? {
    val value = this.getString(name)
    if (value == null) {
        return value
    }
    return JSON.decodeFromString(value)
}

/** Data access logic for key value */
class KvDAO(val db: Database) {
    /** Get current value for [key] */
    suspend inline fun <reified T> get(key: String): T? = db.serializable(
        "SELECT value FROM kv WHERE key=?"
    ) {
        bind(key)
        oneOrNull {
            it.getJson("value")
        }
    }

    /** Update a TaskStatus timestamp */
    suspend fun updateTaskStatus(key: String, timestamp: Instant, success: Boolean) = db.serializable(
        if (success) {
            "INSERT INTO kv (key, value) VALUES (?, jsonb_build_object('last_successfull', ?, 'last_trial', ?)) ON CONFLICT (key) DO UPDATE SET value=EXCLUDED.value"
        } else {
            "INSERT INTO kv (key, value) VALUES (?, jsonb_build_object('last_trial', ?)) ON CONFLICT (key) DO UPDATE SET value=jsonb_set(EXCLUDED.value, '{last_trial}'::text[], to_jsonb(?))"
        }
    ) {
        bind(key)
        bind(timestamp)
        bind(timestamp)
        executeUpdate()
    }
}