//! Plugin de armazenamento local via SQLite
//! Fonte primária de dados — funciona offline, sync com Supabase quando online.

use rusqlite::{Connection, params};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::sync::Mutex;
use tauri::State;
use thiserror::Error;
use uuid::Uuid;
use chrono::Utc;

#[derive(Error, Debug)]
pub enum StorageError {
    #[error("SQLite error: {0}")]
    Sqlite(#[from] rusqlite::Error),
    #[error("Lock error")]
    Lock,
    #[error("IO error: {0}")]
    Io(#[from] std::io::Error),
    #[error("JSON error: {0}")]
    Json(#[from] serde_json::Error),
}

impl serde::Serialize for StorageError {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        serializer.serialize_str(&self.to_string())
    }
}

// ── Tipos ──────────────────────────────────────────────────────────────────

#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct FechamentoEstado {
    pub id: Option<i64>,
    pub uuid: String,
    pub loja: String,
    pub data: String,
    pub operador: String,
    pub turno: String,
    pub saldo_troco: f64,
    pub saldo_esperado: f64,
    pub fechamento: f64,
    pub tabelas: TableData,
    pub cartoes: CartaoData,
    pub sync_status: SyncStatus,
    pub criado_em: String,
    pub atualizado_em: String,
}

#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct TableData {
    pub sangrias: Vec<TableRow>,
    pub despesas: Vec<TableRow>,
    pub pixcnpj: Vec<TableRow>,
    pub vales: Vec<TableRow>,
    pub areceber: Vec<TableRow>,
    pub cancelamentos: Vec<TableRow>,
}

#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct TableRow {
    pub retirada: Option<f64>,
    pub valor: Option<f64>,
    pub descricao: Option<String>,
    pub motivo: Option<String>,
    pub cliente: Option<String>,
    #[serde(flatten)]
    pub extra: std::collections::HashMap<String, serde_json::Value>,
}

#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct CartaoData {
    pub credito: Vec<TableRow>,
    pub debito: Vec<TableRow>,
    pub alimentacao: Vec<TableRow>,
    pub voucher: Vec<TableRow>,
}

#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Default)]
#[serde(rename_all = "lowercase")]
pub enum SyncStatus {
    #[default]
    Local,      // só local, não sincronizado
    Syncing,    // sync em progresso
    Synced,     // sincronizado com servidor
    Error,      // erro no sync
}

impl std::fmt::Display for SyncStatus {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            SyncStatus::Local => write!(f, "local"),
            SyncStatus::Syncing => write!(f, "syncing"),
            SyncStatus::Synced => write!(f, "synced"),
            SyncStatus::Error => write!(f, "error"),
        }
    }
}

impl From<&str> for SyncStatus {
    fn from(s: &str) -> Self {
        match s {
            "local" => SyncStatus::Local,
            "syncing" => SyncStatus::Syncing,
            "synced" => SyncStatus::Synced,
            "error" => SyncStatus::Error,
            _ => SyncStatus::Local,
        }
    }
}

#[derive(Debug, Serialize, Deserialize)]
pub struct SalvarResult {
    pub id: i64,
    pub uuid: String,
    pub sync_status: String,
}

// ── DB Manager ──────────────────────────────────────────────────────────────

pub struct DbManager {
    conn: Mutex<Connection>,
}

impl DbManager {
    pub fn new(app_dir: PathBuf) -> Result<Self, StorageError> {
        std::fs::create_dir_all(&app_dir)?;
        let db_path = app_dir.join("fechamento.db");
        let conn = Connection::open(&db_path)?;

        conn.execute_batch(
            "PRAGMA journal_mode=WAL;
             PRAGMA synchronous=NORMAL;
             PRAGMA foreign_keys=ON;
             PRAGMA busy_timeout=5000;
             CREATE TABLE IF NOT EXISTS fechamentos (
                 id INTEGER PRIMARY KEY AUTOINCREMENT,
                 uuid TEXT NOT NULL UNIQUE,
                 loja TEXT NOT NULL,
                 data TEXT NOT NULL,
                 operador TEXT NOT NULL DEFAULT '',
                 turno TEXT NOT NULL DEFAULT '',
                 saldo_troco REAL NOT NULL DEFAULT 0,
                 saldo_esperado REAL NOT NULL DEFAULT 0,
                 fechamento REAL NOT NULL DEFAULT 0,
                 tabelas_json TEXT NOT NULL DEFAULT '{}',
                 cartoes_json TEXT NOT NULL DEFAULT '{}',
                 sync_status TEXT NOT NULL DEFAULT 'local',
                 criado_em TEXT NOT NULL DEFAULT (datetime('now')),
                 atualizado_em TEXT NOT NULL DEFAULT (datetime('now')),
                 UNIQUE(loja, data, operador, turno)
             );
             CREATE INDEX IF NOT EXISTS idx_fechamentos_lookup
                 ON fechamentos(loja, data, operador, turno);
             CREATE INDEX IF NOT EXISTS idx_fechamentos_data
                 ON fechamentos(data);"
        )?;

        log::info!("SQLite ready: {:?}", db_path);
        Ok(Self { conn: Mutex::new(conn) })
    }

    /// Salva ou atualiza um estado de fechamento. Retorna (id, uuid).
    pub fn salvar(&self, estado: &FechamentoEstado) -> Result<SalvarResult, StorageError> {
        let conn = self.conn.lock().map_err(|_| StorageError::Lock)?;
        let uuid = if estado.uuid.is_empty() {
            Uuid::new_v4().to_string()
        } else {
            estado.uuid.clone()
        };
        let now = Utc::now().format("%Y-%m-%dT%H:%M:%S%.3fZ").to_string();
        let tabelas_json = serde_json::to_string(&estado.tabelas)?;
        let cartoes_json = serde_json::to_string(&estado.cartoes)?;
        let status_str = estado.sync_status.to_string();

        // Upsert
        conn.execute(
            r#"INSERT INTO fechamentos
                 (uuid, loja, data, operador, turno, saldo_troco, saldo_esperado,
                  fechamento, tabelas_json, cartoes_json, sync_status, atualizado_em)
               VALUES (?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12)
               ON CONFLICT(loja, data, operador, turno) DO UPDATE SET
                 uuid=excluded.uuid,
                 saldo_troco=excluded.saldo_troco,
                 saldo_esperado=excluded.saldo_esperado,
                 fechamento=excluded.fechamento,
                 tabelas_json=excluded.tabelas_json,
                 cartoes_json=excluded.cartoes_json,
                 sync_status=excluded.sync_status,
                 atualizado_em=excluded.atualizado_em
               WHERE atualizado_em <= excluded.atualizado_em"#,
            params![
                uuid,
                estado.loja,
                estado.data,
                estado.operador,
                estado.turno,
                estado.saldo_troco,
                estado.saldo_esperado,
                estado.fechamento,
                tabelas_json,
                cartoes_json,
                status_str,
                now,
            ],
        )?;

        let id = conn.query_row(
            "SELECT id FROM fechamentos WHERE uuid = ?1",
            params![uuid],
            |r| r.get(0),
        )?;

        Ok(SalvarResult { id, uuid, sync_status: status_str })
    }

    /// Busca o rascunho mais recente para loja+data (independente de operador/turno).
    pub fn carregar_rascunho(&self, loja: &str, data: &str) -> Result<Option<FechamentoEstado>, StorageError> {
        let conn = self.conn.lock().map_err(|_| StorageError::Lock)?;
        let mut stmt = conn.prepare(
            "SELECT id, uuid, loja, data, operador, turno, saldo_troco, saldo_esperado,
                    fechamento, tabelas_json, cartoes_json, sync_status, criado_em, atualizado_em
               FROM fechamentos
              WHERE loja = ?1 AND data = ?2
              ORDER BY atualizado_em DESC
              LIMIT 1"
        )?;

        let result = stmt.query_row(params![loja, data], |r| {
            let tabelas_json: String = r.get(9)?;
            let cartoes_json: String = r.get(10)?;
            let sync_str: String = r.get(11)?;
            Ok(FechamentoEstado {
                id: Some(r.get(0)?),
                uuid: r.get(1)?,
                loja: r.get(2)?,
                data: r.get(3)?,
                operador: r.get(4)?,
                turno: r.get(5)?,
                saldo_troco: r.get(6)?,
                saldo_esperado: r.get(7)?,
                fechamento: r.get(8)?,
                tabelas: serde_json::from_str(&tabelas_json).unwrap_or_default(),
                cartoes: serde_json::from_str(&cartoes_json).unwrap_or_default(),
                sync_status: SyncStatus::from(sync_str.as_str()),
                criado_em: r.get(12)?,
                atualizado_em: r.get(13)?,
            })
        });

        match result {
            Ok(e) => Ok(Some(e)),
            Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
            Err(e) => Err(StorageError::Sqlite(e)),
        }
    }

    /// Lista todos os fechamentos de uma loja (para o histórico).
    pub fn listar(&self, loja: &str, limite: i64) -> Result<Vec<FechamentoEstado>, StorageError> {
        let conn = self.conn.lock().map_err(|_| StorageError::Lock)?;
        let mut stmt = conn.prepare(
            "SELECT id, uuid, loja, data, operador, turno, saldo_troco, saldo_esperado,
                    fechamento, tabelas_json, cartoes_json, sync_status, criado_em, atualizado_em
               FROM fechamentos
              WHERE loja = ?1
              ORDER BY data DESC, id DESC
              LIMIT ?2"
        )?;

        let rows = stmt.query_map(params![loja, limite], |r| {
            let tabelas_json: String = r.get(9)?;
            let cartoes_json: String = r.get(10)?;
            let sync_str: String = r.get(11)?;
            Ok(FechamentoEstado {
                id: Some(r.get(0)?),
                uuid: r.get(1)?,
                loja: r.get(2)?,
                data: r.get(3)?,
                operador: r.get(4)?,
                turno: r.get(5)?,
                saldo_troco: r.get(6)?,
                saldo_esperado: r.get(7)?,
                fechamento: r.get(8)?,
                tabelas: serde_json::from_str(&tabelas_json).unwrap_or_default(),
                cartoes: serde_json::from_str(&cartoes_json).unwrap_or_default(),
                sync_status: SyncStatus::from(sync_str.as_str()),
                criado_em: r.get(12)?,
                atualizado_em: r.get(13)?,
            })
        })?;

        rows.collect::<Result<Vec<_>, _>>().map_err(StorageError::Sqlite)
    }

    /// Busca um fechamento específico por ID.
    pub fn buscar_por_id(&self, id: i64) -> Result<Option<FechamentoEstado>, StorageError> {
        let conn = self.conn.lock().map_err(|_| StorageError::Lock)?;
        let mut stmt = conn.prepare(
            "SELECT id, uuid, loja, data, operador, turno, saldo_troco, saldo_esperado,
                    fechamento, tabelas_json, cartoes_json, sync_status, criado_em, atualizado_em
               FROM fechamentos WHERE id = ?1"
        )?;

        let result = stmt.query_row(params![id], |r| {
            let tabelas_json: String = r.get(9)?;
            let cartoes_json: String = r.get(10)?;
            let sync_str: String = r.get(11)?;
            Ok(FechamentoEstado {
                id: Some(r.get(0)?),
                uuid: r.get(1)?,
                loja: r.get(2)?,
                data: r.get(3)?,
                operador: r.get(4)?,
                turno: r.get(5)?,
                saldo_troco: r.get(6)?,
                saldo_esperado: r.get(7)?,
                fechamento: r.get(8)?,
                tabelas: serde_json::from_str(&tabelas_json).unwrap_or_default(),
                cartoes: serde_json::from_str(&cartoes_json).unwrap_or_default(),
                sync_status: SyncStatus::from(sync_str.as_str()),
                criado_em: r.get(12)?,
                atualizado_em: r.get(13)?,
            })
        });

        match result {
            Ok(e) => Ok(Some(e)),
            Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
            Err(e) => Err(StorageError::Sqlite(e)),
        }
    }

    /// Atualiza sync_status de um registro.
    pub fn atualizar_sync(&self, uuid: &str, status: SyncStatus) -> Result<(), StorageError> {
        let conn = self.conn.lock().map_err(|_| StorageError::Lock)?;
        let now = Utc::now().format("%Y-%m-%dT%H:%M:%S%.3fZ").to_string();
        conn.execute(
            "UPDATE fechamentos SET sync_status = ?1, atualizado_em = ?2 WHERE uuid = ?3",
            params![status.to_string(), now, uuid],
        )?;
        Ok(())
    }

    /// Remove um registro (para debugging).
    pub fn remover(&self, uuid: &str) -> Result<bool, StorageError> {
        let conn = self.conn.lock().map_err(|_| StorageError::Lock)?;
        let n = conn.execute("DELETE FROM fechamentos WHERE uuid = ?1", params![uuid])?;
        Ok(n > 0)
    }

    /// Retorna quantos registros estão pendentes de sync.
    pub fn pendentes_sync(&self) -> Result<i64, StorageError> {
        let conn = self.conn.lock().map_err(|_| StorageError::Lock)?;
        let count: i64 = conn.query_row(
            "SELECT COUNT(*) FROM fechamentos WHERE sync_status = 'local' OR sync_status = 'error'",
            [],
            |r| r.get(0),
        )?;
        Ok(count)
    }
}

// ── Tauri Commands ─────────────────────────────────────────────────────────

// ── Tauri Commands ─────────────────────────────────────────────────────────

/// Salva fechamento com estrutura flexível ( campos simples do frontend JS )
#[tauri::command]
pub fn salvar_fechamento(
    db: State<'_, DbManager>,
    estado: serde_json::Value,
) -> Result<SalvarResult, StorageError> {
    // Extrai campos simples do JSON flexível
    let uuid = estado.get("uuid").and_then(|v| v.as_str()).unwrap_or("").to_string();
    let loja = estado.get("loja").and_then(|v| v.as_str()).unwrap_or("").to_string();
    let data = estado.get("data").and_then(|v| v.as_str()).unwrap_or("").to_string();
    let operador = estado.get("operador").and_then(|v| v.as_str()).unwrap_or("").to_string();
    let turno = estado.get("turno").and_then(|v| v.as_str()).unwrap_or("").to_string();
    let saldo_troco = estado.get("saldo_troco").and_then(|v| v.as_f64()).unwrap_or(0.0);
    let fechamento = estado.get("fechamento").and_then(|v| v.as_f64()).unwrap_or(0.0);
    let saldo_esperado = saldo_troco + fechamento;
    // Constrói TableData a partir de todas as tabelas do frontend
    let despesas: Vec<serde_json::Value> = estado
        .get("despesas")
        .and_then(|v| v.as_array())
        .map(|arr| arr.to_vec())
        .unwrap_or_default();
    let sangrias: Vec<serde_json::Value> = estado
        .get("sangrias")
        .and_then(|v| v.as_array())
        .map(|arr| arr.to_vec())
        .unwrap_or_default();
    let pixcnpj: Vec<serde_json::Value> = estado
        .get("pixcnpj")
        .and_then(|v| v.as_array())
        .map(|arr| arr.to_vec())
        .unwrap_or_default();
    let vales: Vec<serde_json::Value> = estado
        .get("vales")
        .and_then(|v| v.as_array())
        .map(|arr| arr.to_vec())
        .unwrap_or_default();
    let receber: Vec<serde_json::Value> = estado
        .get("receber")
        .and_then(|v| v.as_array())
        .map(|arr| arr.to_vec())
        .unwrap_or_default();
    let cancelamentos: Vec<serde_json::Value> = estado
        .get("cancelamentos")
        .and_then(|v| v.as_array())
        .map(|arr| arr.to_vec())
        .unwrap_or_default();
    let cartoes_json = estado.get("cartoes");

    let to_table_row = |v: &serde_json::Value, _tipo: &str| -> TableRow {
        TableRow {
            descricao: v.get("desc").or_else(|| v.get("nome")).and_then(|s| s.as_str()).map(String::from),
            retirada: v.get("retirada").and_then(|n| n.as_f64()),
            valor: v.get("valor").and_then(|n| n.as_f64()),
            motivo: v.get("motivo").and_then(|s| s.as_str()).map(String::from),
            cliente: v.get("nome").and_then(|s| s.as_str()).map(String::from),
            // hora, gerente, obs, numero vão para extra via flatten
            extra: std::collections::HashMap::new(),
            ..Default::default()
        }
    };

    let tabelas = TableData {
        despesas: despesas.iter().map(|v| to_table_row(v, "despesas")).collect(),
        sangrias: sangrias.iter().map(|v| to_table_row(v, "sangrias")).collect(),
        pixcnpj: pixcnpj.iter().map(|v| to_table_row(v, "pixcnpj")).collect(),
        vales: vales.iter().map(|v| to_table_row(v, "vales")).collect(),
        areceber: receber.iter().map(|v| to_table_row(v, "receber")).collect(),
        cancelamentos: cancelamentos.iter().map(|v| to_table_row(v, "cancelamentos")).collect(),
    };

    let cartoes = CartaoData {
        credito: cartoes_json
            .and_then(|c| c.get("credito"))
            .and_then(|v| v.as_array())
            .map(|arr| {
                arr.iter()
                    .map(|v| TableRow { valor: v.as_f64(), ..Default::default() })
                    .collect()
            })
            .unwrap_or_default(),
        debito: cartoes_json
            .and_then(|c| c.get("debito"))
            .and_then(|v| v.as_array())
            .map(|arr| {
                arr.iter()
                    .map(|v| TableRow { valor: v.as_f64(), ..Default::default() })
                    .collect()
            })
            .unwrap_or_default(),
        alimentacao: cartoes_json
            .and_then(|c| c.get("alimentacao"))
            .and_then(|v| v.as_array())
            .map(|arr| {
                arr.iter()
                    .map(|v| TableRow { valor: v.as_f64(), ..Default::default() })
                    .collect()
            })
            .unwrap_or_default(),
        voucher: cartoes_json
            .and_then(|c| c.get("pix"))
            .and_then(|v| v.as_array())
            .map(|arr| {
                arr.iter()
                    .map(|v| TableRow { valor: v.as_f64(), ..Default::default() })
                    .collect()
            })
            .unwrap_or_default(),
    };

    let estado_rust = FechamentoEstado {
        id: estado.get("id_local").and_then(|v| v.as_i64()),
        uuid,
        loja,
        data,
        operador,
        turno,
        saldo_troco,
        fechamento,
        saldo_esperado,
        tabelas,
        cartoes,
        sync_status: SyncStatus::Local,
        ..Default::default()
    };

    db.salvar(&estado_rust)
}

/// Carrega rascunho e devolve estrutura simples para o frontend
#[tauri::command]
pub fn carregar_rascunho(
    db: State<'_, DbManager>,
    loja: String,
    data: String,
) -> Result<Option<serde_json::Value>, StorageError> {
    match db.carregar_rascunho(&loja, &data)? {
        Some(e) => {
            // Simplifica para formato que o frontend espera
            let total_sangrias: f64 = e.tabelas.sangrias.iter().filter_map(|r| r.valor).sum();
            let total_despesas: f64 = e.tabelas.despesas.iter().filter_map(|r| r.valor).sum();
            let total_receber: f64 = e.tabelas.areceber.iter().filter_map(|r| r.valor).sum();
            let despesa_rows: Vec<_> = e.tabelas.despesas.iter().filter_map(|r| {
                r.descricao.as_ref().map(|d| serde_json::json!({"desc": d, "valor": r.valor}))
            }).collect();
            let receber_rows: Vec<_> = e.tabelas.areceber.iter().filter_map(|r| {
                r.descricao.as_ref().map(|d| serde_json::json!({"desc": d, "valor": r.valor}))
            }).collect();

            Ok(Some(serde_json::json!({
                "id_local": e.id,
                "uuid": e.uuid,
                "loja": e.loja,
                "data": e.data,
                "operador": e.operador,
                "turno": e.turno,
                "saldo_troco": e.saldo_troco,
                "fechamento": e.fechamento,
                "saldo_esperado": e.saldo_esperado,
                "diferenca": e.fechamento - e.saldo_esperado,
                "despesas": despesa_rows,
                "total_sangrias": total_sangrias,
                "total_despesas": total_despesas,
                "vales": 0.0,
                "cartoes": {
                    "credito": e.cartoes.credito.iter().find_map(|r| r.valor).unwrap_or(0.0),
                    "debito": e.cartoes.debito.iter().find_map(|r| r.valor).unwrap_or(0.0),
                    "alimentacao": e.cartoes.alimentacao.iter().find_map(|r| r.valor).unwrap_or(0.0),
                    "voucher": e.cartoes.voucher.iter().find_map(|r| r.valor).unwrap_or(0.0),
                    "cancelamentos": 0.0,
                },
                "receber": receber_rows,
                "observacoes": "",
                "sync_status": "local",
            })))
        }
        None => Ok(None),
    }
}

#[tauri::command]
pub fn listar_fechamentos(
    db: State<'_, DbManager>,
    loja: String,
    limite: Option<i64>,
) -> Result<Vec<serde_json::Value>, StorageError> {
    let rows = db.listar(&loja, limite.unwrap_or(50))?;
    Ok(rows.into_iter().map(|e| serde_json::json!({
        "id": e.id,
        "uuid": e.uuid,
        "data": e.data,
        "operador": e.operador,
        "turno": e.turno,
        "saldo_esperado": e.saldo_esperado,
        "sync_status": format!("{:?}", e.sync_status).to_lowercase(),
    })).collect())
}

#[tauri::command]
pub fn buscar_fechamento_por_id(
    db: State<'_, DbManager>,
    id: i64,
) -> Result<Option<serde_json::Value>, StorageError> {
    match db.buscar_por_id(id)? {
        Some(e) => Ok(Some(serde_json::json!({
            "id_local": e.id,
            "uuid": e.uuid,
            "loja": e.loja,
            "data": e.data,
            "operador": e.operador,
            "turno": e.turno,
            "saldo_troco": e.saldo_troco,
            "fechamento": e.fechamento,
            "saldo_esperado": e.saldo_esperado,
            "diferenca": e.fechamento - e.saldo_esperado,
            "despesas": e.tabelas.despesas.iter().filter_map(|r| r.descricao.as_ref().map(|d| serde_json::json!({"desc": d, "valor": r.valor}))).collect::<Vec<_>>(),
            "total_sangrias": e.tabelas.sangrias.iter().filter_map(|r| r.valor).sum::<f64>(),
            "total_despesas": e.tabelas.despesas.iter().filter_map(|r| r.valor).sum::<f64>(),
            "vales": 0.0,
            "cartoes": {
                "credito": e.cartoes.credito.iter().find_map(|r| r.valor).unwrap_or(0.0),
                "debito": e.cartoes.debito.iter().find_map(|r| r.valor).unwrap_or(0.0),
                "alimentacao": e.cartoes.alimentacao.iter().find_map(|r| r.valor).unwrap_or(0.0),
                "voucher": e.cartoes.voucher.iter().find_map(|r| r.valor).unwrap_or(0.0),
                "cancelamentos": 0.0,
            },
            "receber": e.tabelas.areceber.iter().filter_map(|r| r.descricao.as_ref().map(|d| serde_json::json!({"desc": d, "valor": r.valor}))).collect::<Vec<_>>(),
            "observacoes": "",
            "sync_status": format!("{:?}", e.sync_status).to_lowercase(),
        }))),
        None => Ok(None),
    }
}

#[tauri::command]
pub fn pendentes_sync(db: State<'_, DbManager>) -> Result<i64, StorageError> {
    db.pendentes_sync()
}

pub fn init(app_dir: PathBuf) -> Result<DbManager, StorageError> {
    DbManager::new(app_dir)
}
