//! Plugin de comunicação com Supabase via REST API
//! Substitui completamente o fetch() do JS + n8n como intermediário.

use reqwest::blocking::Client;
use serde_json::Value;
use std::time::Duration;
use tauri::State;
use thiserror::Error;

#[derive(Error, Debug)]
pub enum SupabaseError {
    #[error("HTTP error: {0}")]
    Http(#[from] reqwest::Error),
    #[error("API error: {0}")]
    Api(String),
    #[error("Lock error")]
    Lock,
}

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

pub struct SupabaseClient {
    http: Client,
    anon_key: String,
    service_role_key: String,
    base_url: String,
}

impl SupabaseClient {
    pub fn new(anon_key: String, service_role_key: String) -> Self {
        let http = Client::builder()
            .timeout(Duration::from_secs(15))
            .build()
            .expect("reqwest client");

        Self {
            http,
            anon_key,
            service_role_key,
            base_url: "https://supabase.ofrangao.com.br".to_string(),
        }
    }

    fn headers(&self, use_service: bool) -> reqwest::header::HeaderMap {
        let key = if use_service { &self.service_role_key } else { &self.anon_key };
        let mut headers = reqwest::header::HeaderMap::new();
        headers.insert(
            reqwest::header::AUTHORIZATION,
            format!("Bearer {}", key).parse().unwrap(),
        );
        headers.insert(
            reqwest::header::HeaderName::from_static("apikey"),
            key.parse().unwrap(),
        );
        headers.insert(
            reqwest::header::CONTENT_TYPE,
            "application/json".parse().unwrap(),
        );
        headers
    }

    /// Salva/atualiza um fechamento na tabela `fechamentos_web`.
    pub fn salvar_fechamento(&self, estado: &serde_json::Value) -> Result<Value, SupabaseError> {
        let uuid = estado
            .get("uuid")
            .and_then(|v| v.as_str())
            .unwrap_or("");

        let payload = serde_json::json!({
            "uuid": uuid,
            "loja": estado["loja"],
            "data": estado["data"],
            "operador": estado["operador"],
            "turno": estado["turno"],
            "saldo_troco": estado["saldo_troco"],
            "saldo_esperado": estado["saldo_esperado"],
            "fechamento": estado["fechamento"],
            "dados": estado,
            "observacoes": "rascunho",
        });

        let _url = format!(
            "{}/rest/v1/fechamentos_web?uuid=eq.{}&select=id",
            self.base_url, uuid
        );

        // Upsert via POST com Prefer: resolution=merge-duplicates
        let resp = self
            .http
            .post(&format!("{}/rest/v1/rpc/upsert_fechamento", self.base_url))
            .headers(self.headers(true))
            .json(&payload)
            .send()?;

        if resp.status().is_success() {
            Ok(serde_json::json!({ "ok": true, "uuid": uuid }))
        } else {
            let body = resp.text().unwrap_or_default();
            // Fallback: tenta POST direto na tabela
            let resp2 = self
                .http
                .post(&format!("{}/rest/v1/fechamentos_web", self.base_url))
                .headers(self.headers(true))
                .header("Prefer", "resolution=merge-duplicates")
                .json(&payload)
                .send()?;
            if resp2.status().is_success() {
                Ok(serde_json::json!({ "ok": true, "uuid": uuid }))
            } else {
                Err(SupabaseError::Api(format!(
                    "HTTP {}: {}",
                    resp2.status().as_u16(),
                    body
                )))
            }
        }
    }

    /// Busca o rascunho mais recente para loja+data.
    pub fn carregar_rascunho(&self, loja: &str, data: &str) -> Result<Option<Value>, SupabaseError> {
        let url = format!(
            "{}/rest/v1/fechamentos_web?loja=eq.{}&data=eq.{}&observacoes=eq.rascunho&order=atualizado_em.desc&limit=1&select=*",
            self.base_url,
            urlencoding::encode(loja),
            urlencoding::encode(data),
        );

        let resp = self
            .http
            .get(&url)
            .headers(self.headers(false))
            .send()?;

        if resp.status().is_success() {
            let arr: Vec<Value> = resp.json()?;
            Ok(arr.into_iter().next())
        } else {
            let status = resp.status();
            let body = resp.text().unwrap_or_default();
            Err(SupabaseError::Api(format!(
                "HTTP {}: {}",
                status.as_u16(),
                body
            )))
        }
    }

    /// Lista fechamentos recentes de uma loja.
    pub fn listar_recentes(&self, loja: &str, limite: i64) -> Result<Vec<Value>, SupabaseError> {
        let url = format!(
            "{}/rest/v1/fechamentos_web?loja=eq.{}&order=data.desc,atualizado_em.desc&limit={}&select=id,uuid,data,operador,turno,saldo_troco,saldo_esperado,fechamento,sync_status,observacoes,atualizado_em",
            self.base_url,
            urlencoding::encode(loja),
            limite,
        );

        let resp = self
            .http
            .get(&url)
            .headers(self.headers(false))
            .send()?;

        if resp.status().is_success() {
            let arr: Vec<Value> = resp.json()?;
            Ok(arr)
        } else {
            let status = resp.status();
            let body = resp.text().unwrap_or_default();
            Err(SupabaseError::Api(format!(
                "HTTP {}: {}",
                status.as_u16(),
                body
            )))
        }
    }

    /// Busca um fechamento por ID.
    pub fn buscar_por_id(&self, id: i64) -> Result<Option<Value>, SupabaseError> {
        let url = format!(
            "{}/rest/v1/fechamentos_web?id=eq.{}&select=*",
            self.base_url, id
        );

        let resp = self
            .http
            .get(&url)
            .headers(self.headers(false))
            .send()?;

        if resp.status().is_success() {
            let arr: Vec<Value> = resp.json()?;
            Ok(arr.into_iter().next())
        } else {
            let status = resp.status();
            let body = resp.text().unwrap_or_default();
            Err(SupabaseError::Api(format!(
                "HTTP {}: {}",
                status.as_u16(),
                body
            )))
        }
    }

    /// Salva operadores/gerentes/despesas customizadas.
    pub fn salvar_listas(&self, loja: &str, listas: &serde_json::Value) -> Result<Value, SupabaseError> {
        let payload = serde_json::json!({
            "loja": loja,
            "operadores": listas.get("operadores"),
            "gerentes": listas.get("gerentes"),
            "despesas_custom": listas.get("despesas_custom"),
            "clientes": listas.get("clientes"),
        });

        let _url = format!(
            "{}/rest/v1/listas_personalizadas?loja=eq.{}&select=id",
            self.base_url,
            urlencoding::encode(loja),
        );

        let resp = self
            .http
            .post(&format!("{}/rest/v1/listas_personalizadas", self.base_url))
            .headers(self.headers(true))
            .header("Prefer", "resolution=merge-duplicates")
            .json(&payload)
            .send()?;

        if resp.status().is_success() {
            Ok(serde_json::json!({ "ok": true }))
        } else {
            let status = resp.status();
            let body = resp.text().unwrap_or_default();
            Err(SupabaseError::Api(format!(
                "HTTP {}: {}",
                status.as_u16(),
                body
            )))
        }
    }

    /// Carrega listas personalizadas do servidor.
    pub fn carregar_listas(&self, loja: &str) -> Result<Option<Value>, SupabaseError> {
        let url = format!(
            "{}/rest/v1/listas_personalizadas?loja=eq.{}&limit=1&select=*",
            self.base_url,
            urlencoding::encode(loja),
        );

        let resp = self
            .http
            .get(&url)
            .headers(self.headers(false))
            .send()?;

        if resp.status().is_success() {
            let arr: Vec<Value> = resp.json()?;
            Ok(arr.into_iter().next())
        } else {
            let status = resp.status();
            let body = resp.text().unwrap_or_default();
            Err(SupabaseError::Api(format!(
                "HTTP {}: {}",
                status.as_u16(),
                body
            )))
        }
    }

    /// Verifica se está online.
    pub fn health_check(&self) -> bool {
        let url = format!("{}/rest/v1/?limit=0", self.base_url);
        self.http
            .get(&url)
            .headers(self.headers(false))
            .send()
            .map(|r| r.status().is_success())
            .unwrap_or(false)
    }
}

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

#[tauri::command]
pub fn sb_salvar_fechamento(
    sb: State<'_, SupabaseClient>,
    estado: serde_json::Value,
) -> Result<serde_json::Value, SupabaseError> {
    sb.salvar_fechamento(&estado)
}

#[tauri::command]
pub fn sb_carregar_rascunho(
    sb: State<'_, SupabaseClient>,
    loja: String,
    data: String,
) -> Result<Option<serde_json::Value>, SupabaseError> {
    sb.carregar_rascunho(&loja, &data)
}

#[tauri::command]
pub fn sb_listar_recentes(
    sb: State<'_, SupabaseClient>,
    loja: String,
    limite: Option<i64>,
) -> Result<Vec<serde_json::Value>, SupabaseError> {
    sb.listar_recentes(&loja, limite.unwrap_or(30))
}

#[tauri::command]
pub fn sb_buscar_por_id(
    sb: State<'_, SupabaseClient>,
    id: i64,
) -> Result<Option<serde_json::Value>, SupabaseError> {
    sb.buscar_por_id(id)
}

#[tauri::command]
pub fn sb_salvar_listas(
    sb: State<'_, SupabaseClient>,
    loja: String,
    listas: serde_json::Value,
) -> Result<serde_json::Value, SupabaseError> {
    sb.salvar_listas(&loja, &listas)
}

#[tauri::command]
pub fn sb_carregar_listas(
    sb: State<'_, SupabaseClient>,
    loja: String,
) -> Result<Option<serde_json::Value>, SupabaseError> {
    sb.carregar_listas(&loja)
}

#[tauri::command]
pub fn sb_online(sb: State<'_, SupabaseClient>) -> bool {
    sb.health_check()
}

pub fn init(anon_key: String, service_role_key: String) -> SupabaseClient {
    SupabaseClient::new(anon_key, service_role_key)
}

// urlencoding helper (we'll add it as a dep)
mod urlencoding {
    pub fn encode(s: &str) -> String {
        let mut out = String::new();
        for b in s.bytes() {
            match b {
                b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => {
                    out.push(b as char);
                }
                _ => {
                    out.push_str(&format!("%{:02X}", b));
                }
            }
        }
        out
    }
}
