use chrono::Utc;
use serde_json::Value;
use surrealdb::{engine::any::Any, Surreal};

use crate::{
    error::ControllerError,
    types::InFlightOp,
};

pub struct Persistence {
    db: Surreal<Any>,
    workspace: String,
}

const TABLE: &str = "ops_in_flight";
const STATE_TABLE: &str = "workspace_state";

impl Persistence {
    pub async fn new(db: Surreal<Any>, workspace: String) -> Result<Self, ControllerError> {
        db.use_ns("ops")
            .use_db("controller")
            .await
            .map_err(|e| ControllerError::Db(e.to_string()))?;
        Ok(Self { db, workspace })
    }

    /// Insert a new in-flight op with state=Pending.
    /// Returns `Err(DuplicateJti)` if the jti already exists.
    pub async fn insert_pending(
        &self,
        jti: &str,
        op_type: &str,
        target: &str,
    ) -> Result<(), ControllerError> {
        let jti_owned = jti.to_string();
        let existing = self.get(jti).await?;
        if let Some(row) = existing {
            return Err(ControllerError::DuplicateJti(format!(
                "{jti} (state={})",
                row.state.as_str()
            )));
        }

        let now = Utc::now();
        let _: Option<Value> = self
            .db
            .create((TABLE, jti_owned.as_str()))
            .content(serde_json::json!({
                "jti":        jti_owned,
                "op_type":    op_type,
                "target":     target,
                "state":      "pending",
                "created_at": now,
                "updated_at": now,
                "error":      null,
            }))
            .await
            .map_err(|e| ControllerError::Db(e.to_string()))?;

        Ok(())
    }

    pub async fn mark_applied(&self, jti: &str) -> Result<(), ControllerError> {
        let jti_owned = jti.to_string();
        let _: Option<Value> = self
            .db
            .update((TABLE, jti_owned.as_str()))
            .merge(serde_json::json!({
                "state":      "applied",
                "updated_at": Utc::now(),
            }))
            .await
            .map_err(|e| ControllerError::Db(format!("mark_applied {jti}: {e}")))?;
        Ok(())
    }

    pub async fn mark_rejected(&self, jti: &str, reason: &str) -> Result<(), ControllerError> {
        let jti_owned = jti.to_string();
        let reason_owned = reason.to_string();
        let _: Option<Value> = self
            .db
            .update((TABLE, jti_owned.as_str()))
            .merge(serde_json::json!({
                "state":      "rejected",
                "error":      reason_owned,
                "updated_at": Utc::now(),
            }))
            .await
            .map_err(|e| ControllerError::Db(format!("mark_rejected {jti}: {e}")))?;
        Ok(())
    }

    pub async fn get(&self, jti: &str) -> Result<Option<InFlightOp>, ControllerError> {
        let mut resp = self
            .db
            .query("SELECT * FROM type::table($tb) WHERE jti = $jti LIMIT 1")
            .bind(("tb", TABLE.to_string()))
            .bind(("jti", jti.to_string()))
            .await
            .map_err(|e| ControllerError::Db(e.to_string()))?;

        let rows: Vec<serde_json::Value> = resp
            .take(0)
            .map_err(|e| ControllerError::Db(e.to_string()))?;
        let row = rows
            .into_iter()
            .next()
            .and_then(|v| serde_json::from_value(v).ok());
        Ok(row)
    }

    /// Return all rows with state=Pending for recovery on startup.
    pub async fn list_pending(&self) -> Result<Vec<InFlightOp>, ControllerError> {
        let mut resp = self
            .db
            .query("SELECT * FROM type::table($tb) WHERE state = 'pending'")
            .bind(("tb", TABLE.to_string()))
            .await
            .map_err(|e| ControllerError::Db(e.to_string()))?;

        let raw: Vec<serde_json::Value> = resp
            .take(0)
            .map_err(|e| ControllerError::Db(e.to_string()))?;
        Ok(raw.into_iter().filter_map(|v| serde_json::from_value(v).ok()).collect())
    }

    /// Get the current state version for this workspace. Returns "v0" if none persisted.
    pub async fn current_version(&self) -> Result<String, ControllerError> {
        let ws = self.workspace.clone();
        let mut resp = self
            .db
            .query("SELECT version FROM type::table($tb) WHERE workspace = $ws LIMIT 1")
            .bind(("tb", STATE_TABLE.to_string()))
            .bind(("ws", ws))
            .await
            .map_err(|e| ControllerError::Db(e.to_string()))?;

        let rows: Vec<serde_json::Value> = resp
            .take(0)
            .map_err(|e| ControllerError::Db(e.to_string()))?;

        let version = rows
            .into_iter()
            .next()
            .and_then(|v| v.get("version").and_then(|s| s.as_str()).map(str::to_owned))
            .unwrap_or_else(|| "v0".to_string());

        Ok(version)
    }

    /// Increment the workspace state version after a successful apply.
    pub async fn increment_version(&self) -> Result<String, ControllerError> {
        let current = self.current_version().await?;
        let n: u64 = current.trim_start_matches('v').parse().unwrap_or(0);
        let next = format!("v{}", n + 1);
        let ws = self.workspace.clone();

        let _: Option<Value> = self
            .db
            .upsert((STATE_TABLE, ws.as_str()))
            .content(serde_json::json!({
                "workspace":  ws,
                "version":    next,
                "updated_at": Utc::now(),
            }))
            .await
            .map_err(|e| ControllerError::Db(format!("increment_version: {e}")))?;

        Ok(next)
    }
}
