use anyhow::{Context, Result};
use serde::{de::DeserializeOwned, Serialize};
use sqlx::{Row, PgPool};

/// Fetches a configuration JSON from the `system_config` table
/// and deserializes it into the specified generic type `T`.
/// If the key does not exist, it returns a default `T`.
pub async fn get_config<T>(pool: &PgPool, key: &str) -> Result<T>
where
    T: DeserializeOwned + Default,
{
    let row_opt = sqlx::query("SELECT config_value FROM system_config WHERE config_key = $1")
        .bind(key)
        .fetch_optional(pool)
        .await
        .context("Failed to fetch from system_config")?;

    if let Some(row) = row_opt {
        // Postgres JSON/JSONB maps to serde_json::Value
        let value: serde_json::Value = row
            .try_get("config_value")
            .context("Missing column 'config_value'")?;
        let config: T = serde_json::from_value(value)
            .context("Failed to deserialize config_value from database")?;
        Ok(config)
    } else {
        Ok(T::default())
    }
}

/// Serializes the provided configuration struct `T` into JSON
/// and upserts it into the `system_config` table for the given key.
pub async fn update_config<T>(pool: &PgPool, key: &str, config: &T) -> Result<T>
where
    T: Serialize + DeserializeOwned,
{
    let json_value = serde_json::to_value(config)
        .context("Failed to serialize config to JSON value")?;

    let row = sqlx::query(
        r#"
        INSERT INTO system_config (config_key, config_value, updated_at)
        VALUES ($1, $2, NOW())
        ON CONFLICT (config_key) DO UPDATE SET
            config_value = EXCLUDED.config_value,
            updated_at = NOW()
        RETURNING config_value
        "#,
    )
    .bind(key)
    .bind(json_value)
    .fetch_one(pool)
    .await
    .context("Failed to upsert into system_config")?;

    let value: serde_json::Value = row
        .try_get("config_value")
        .context("Missing returned column 'config_value'")?;
    let result: T = serde_json::from_value(value)
        .context("Failed to deserialize updated config_value from database")?;

    Ok(result)
}
