//! Modulo de Chat em Tempo Real
//!
//! Este modulo implementa o sistema de chat entre agentes (dashboard web)
//! e clientes (Raven desktop). Usa Server-Sent Events (SSE) como metodo
//! primario para atualizacoes em tempo real, com fallback para HTTP polling.

use convex::{ConvexClient, FunctionResult, Value};
use futures_util::StreamExt;
use once_cell::sync::Lazy;
use parking_lot::Mutex;
use reqwest::Client;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::fs;
use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
use tauri::async_runtime::JoinHandle;
use tauri::{Emitter, Manager, WebviewWindowBuilder, WebviewUrl};
use tauri_plugin_notification::NotificationExt;

// ============================================================================
// TYPES
// ============================================================================

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ChatSession {
    pub session_id: String,
    pub ticket_id: String,
    pub ticket_ref: u64,
    pub ticket_subject: String,
    pub agent_name: String,
    pub agent_email: Option<String>,
    pub agent_avatar_url: Option<String>,
    pub unread_count: u32,
    pub last_activity_at: i64,
    pub started_at: i64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ChatMessage {
    pub id: String,
    pub body: String,
    pub author_name: String,
    pub author_avatar_url: Option<String>,
    pub is_from_machine: bool,
    pub created_at: i64,
    pub attachments: Vec<ChatAttachment>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ChatAttachment {
    pub storage_id: String,
    pub name: String,
    pub size: Option<u64>,
    #[serde(rename = "type")]
    pub mime_type: Option<String>,
}

#[allow(dead_code)]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ChatPollResponse {
    pub has_active_sessions: bool,
    pub sessions: Vec<ChatSessionSummary>,
    pub total_unread: u32,
}

#[allow(dead_code)]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ChatSessionSummary {
    pub ticket_id: String,
    pub ticket_ref: u64,
    pub unread_count: u32,
    pub last_activity_at: i64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ChatMessagesResponse {
    pub messages: Vec<ChatMessage>,
    pub has_session: bool,
    #[serde(default)]
    pub unread_count: Option<u32>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SendMessageResponse {
    pub message_id: String,
    pub created_at: i64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SessionStartedEvent {
    pub session: ChatSession,
}

// ============================================================================
// PERSISTENCIA DE ESTADO
// ============================================================================

/// Estado persistido do chat para sobreviver a restarts
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct ChatPersistedState {
    last_unread_count: u32,
    sessions: Vec<ChatSession>,
    saved_at: u64, // Unix timestamp em ms
}

const STATE_FILE_NAME: &str = "chat-state.json";
const STATE_MAX_AGE_MS: u64 = 3600_000; // 1 hora - ignorar estados mais antigos

fn get_state_file_path() -> Option<PathBuf> {
    dirs::data_local_dir().map(|p| p.join("Raven").join(STATE_FILE_NAME))
}

fn save_chat_state(last_unread: u32, sessions: &[ChatSession]) {
    let Some(path) = get_state_file_path() else {
        return;
    };

    // Criar diretorio se nao existir
    if let Some(parent) = path.parent() {
        let _ = fs::create_dir_all(parent);
    }

    let now = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map(|d| d.as_millis() as u64)
        .unwrap_or(0);

    let state = ChatPersistedState {
        last_unread_count: last_unread,
        sessions: sessions.to_vec(),
        saved_at: now,
    };

    if let Ok(json) = serde_json::to_string_pretty(&state) {
        let _ = fs::write(&path, json);
        crate::log_info!("[CHAT] Estado persistido: unread={}, sessions={}", last_unread, sessions.len());
    }
}

fn load_chat_state() -> Option<ChatPersistedState> {
    let path = get_state_file_path()?;

    let json = fs::read_to_string(&path).ok()?;
    let state: ChatPersistedState = serde_json::from_str(&json).ok()?;

    // Verificar se estado nao esta muito antigo
    let now = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map(|d| d.as_millis() as u64)
        .unwrap_or(0);

    if now.saturating_sub(state.saved_at) > STATE_MAX_AGE_MS {
        crate::log_info!("[CHAT] Estado persistido ignorado (muito antigo)");
        return None;
    }

    crate::log_info!(
        "[CHAT] Estado restaurado: unread={}, sessions={}",
        state.last_unread_count, state.sessions.len()
    );
    Some(state)
}

// ============================================================================
// HTTP CLIENT
// ============================================================================

static CHAT_CLIENT: Lazy<Client> = Lazy::new(|| {
    Client::builder()
        .user_agent("raven-chat/1.0")
        .timeout(Duration::from_secs(15))
        .use_rustls_tls()
        .build()
        .expect("failed to build chat http client")
});

// ============================================================================
// API FUNCTIONS
// ============================================================================

#[allow(dead_code)]
pub async fn poll_chat_updates(
    base_url: &str,
    token: &str,
    last_checked_at: Option<i64>,
) -> Result<ChatPollResponse, String> {
    let url = format!("{}/api/machines/chat/poll", base_url);

    let mut payload = serde_json::json!({
        "machineToken": token,
    });

    if let Some(ts) = last_checked_at {
        payload["lastCheckedAt"] = serde_json::json!(ts);
    }

    let response = CHAT_CLIENT
        .post(&url)
        .json(&payload)
        .send()
        .await
        .map_err(|e| format!("Falha na requisicao de poll: {e}"))?;

    if !response.status().is_success() {
        let status = response.status();
        let body = response.text().await.unwrap_or_default();
        return Err(format!("Poll falhou: status={}, body={}", status, body));
    }

    response
        .json()
        .await
        .map_err(|e| format!("Falha ao parsear resposta de poll: {e}"))
}

pub async fn fetch_sessions(base_url: &str, token: &str) -> Result<Vec<ChatSession>, String> {
    let url = format!("{}/api/machines/chat/sessions", base_url);

    let payload = serde_json::json!({
        "machineToken": token,
    });

    let response = CHAT_CLIENT
        .post(&url)
        .json(&payload)
        .send()
        .await
        .map_err(|e| format!("Falha na requisicao de sessions: {e}"))?;

    if !response.status().is_success() {
        let status = response.status();
        let body = response.text().await.unwrap_or_default();
        return Err(format!("Sessions falhou: status={}, body={}", status, body));
    }

    #[derive(Deserialize)]
    struct SessionsResponse {
        sessions: Vec<ChatSession>,
    }

    let data: SessionsResponse = response
        .json()
        .await
        .map_err(|e| format!("Falha ao parsear resposta de sessions: {e}"))?;

    Ok(data.sessions)
}

pub async fn fetch_messages(
    base_url: &str,
    token: &str,
    ticket_id: &str,
    since: Option<i64>,
) -> Result<ChatMessagesResponse, String> {
    let url = format!("{}/api/machines/chat/messages", base_url);

    let mut payload = serde_json::json!({
        "machineToken": token,
        "ticketId": ticket_id,
        "action": "list",
        "limit": 200,
    });

    if let Some(ts) = since {
        payload["since"] = serde_json::json!(ts);
    }

    let response = CHAT_CLIENT
        .post(&url)
        .json(&payload)
        .send()
        .await
        .map_err(|e| format!("Falha na requisicao de messages: {e}"))?;

    if !response.status().is_success() {
        let status = response.status();
        let body = response.text().await.unwrap_or_default();
        return Err(format!("Messages falhou: status={}, body={}", status, body));
    }

    response
        .json()
        .await
        .map_err(|e| format!("Falha ao parsear resposta de messages: {e}"))
}

pub async fn send_message(
    base_url: &str,
    token: &str,
    ticket_id: &str,
    body: &str,
    attachments: Option<Vec<AttachmentPayload>>,
) -> Result<SendMessageResponse, String> {
    let url = format!("{}/api/machines/chat/messages", base_url);

    let mut payload = serde_json::json!({
        "machineToken": token,
        "ticketId": ticket_id,
        "action": "send",
        "body": body,
    });

    if let Some(atts) = attachments {
        payload["attachments"] = serde_json::to_value(atts).unwrap_or_default();
    }

    let response = CHAT_CLIENT
        .post(&url)
        .json(&payload)
        .send()
        .await
        .map_err(|e| format!("Falha na requisicao de send: {e}"))?;

    if !response.status().is_success() {
        let status = response.status();
        let body = response.text().await.unwrap_or_default();
        return Err(format!("Send falhou: status={}, body={}", status, body));
    }

    response
        .json()
        .await
        .map_err(|e| format!("Falha ao parsear resposta de send: {e}"))
}

pub async fn mark_messages_read(
    base_url: &str,
    token: &str,
    ticket_id: &str,
    message_ids: &[String],
) -> Result<(), String> {
    let url = format!("{}/api/machines/chat/read", base_url);

    let payload = serde_json::json!({
        "machineToken": token,
        "ticketId": ticket_id,
        "messageIds": message_ids,
    });

    let response = CHAT_CLIENT
        .post(&url)
        .json(&payload)
        .send()
        .await
        .map_err(|e| format!("Falha na requisicao de mark read: {e}"))?;

    if !response.status().is_success() {
        let status = response.status();
        let body = response.text().await.unwrap_or_default();
        return Err(format!("Mark read falhou: status={}, body={}", status, body));
    }

    Ok(())
}

// ============================================================================
// UPLOAD DE ARQUIVOS
// ============================================================================

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AttachmentPayload {
    pub storage_id: String,
    pub name: String,
    pub size: Option<u64>,
    #[serde(rename = "type")]
    pub mime_type: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UploadUrlResponse {
    pub upload_url: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UploadResult {
    pub storage_id: String,
}

// Extensoes permitidas
const ALLOWED_EXTENSIONS: &[&str] = &[
    ".jpg", ".jpeg", ".png", ".gif", ".webp",
    ".pdf", ".txt", ".doc", ".docx", ".xls", ".xlsx",
];

// Tamanho maximo: 10MB
const MAX_FILE_SIZE: u64 = 10 * 1024 * 1024;

pub fn is_allowed_file(file_name: &str, file_size: u64) -> Result<(), String> {
    let ext = file_name
        .to_lowercase()
        .rsplit('.')
        .next()
        .map(|e| format!(".{}", e))
        .unwrap_or_default();

    if !ALLOWED_EXTENSIONS.contains(&ext.as_str()) {
        return Err(format!(
            "Tipo de arquivo não permitido. Permitidos: {}",
            ALLOWED_EXTENSIONS.join(", ")
        ));
    }

    if file_size > MAX_FILE_SIZE {
        return Err(format!(
            "Arquivo muito grande. Máximo: {}MB",
            MAX_FILE_SIZE / 1024 / 1024
        ));
    }

    Ok(())
}

pub fn get_mime_type(file_name: &str) -> String {
    let lower = file_name.to_lowercase();
    let ext = lower.rsplit('.').next().unwrap_or("");

    match ext {
        "jpg" | "jpeg" => "image/jpeg",
        "png" => "image/png",
        "gif" => "image/gif",
        "webp" => "image/webp",
        "pdf" => "application/pdf",
        "txt" => "text/plain",
        "doc" => "application/msword",
        "docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
        "xls" => "application/vnd.ms-excel",
        "xlsx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
        _ => "application/octet-stream",
    }
    .to_string()
}

pub async fn generate_upload_url(
    base_url: &str,
    token: &str,
    file_name: &str,
    file_type: &str,
    file_size: u64,
) -> Result<String, String> {
    let url = format!("{}/api/machines/chat/upload", base_url);

    let payload = serde_json::json!({
        "machineToken": token,
        "fileName": file_name,
        "fileType": file_type,
        "fileSize": file_size,
    });

    let response = CHAT_CLIENT
        .post(&url)
        .json(&payload)
        .send()
        .await
        .map_err(|e| format!("Falha na requisicao de upload URL: {e}"))?;

    if !response.status().is_success() {
        let status = response.status();
        let body = response.text().await.unwrap_or_default();
        return Err(format!("Upload URL falhou: status={}, body={}", status, body));
    }

    let data: UploadUrlResponse = response
        .json()
        .await
        .map_err(|e| format!("Falha ao parsear resposta de upload URL: {e}"))?;

    Ok(data.upload_url)
}

pub async fn upload_file(
    upload_url: &str,
    file_data: Vec<u8>,
    content_type: &str,
) -> Result<String, String> {
    let response = CHAT_CLIENT
        .post(upload_url)
        .header("Content-Type", content_type)
        .body(file_data)
        .send()
        .await
        .map_err(|e| format!("Falha no upload: {e}"))?;

    if !response.status().is_success() {
        let status = response.status();
        let body = response.text().await.unwrap_or_default();
        return Err(format!("Upload falhou: status={}, body={}", status, body));
    }

    let data: UploadResult = response
        .json()
        .await
        .map_err(|e| format!("Falha ao parsear resposta de upload: {e}"))?;

    Ok(data.storage_id)
}

// ============================================================================
// CHAT RUNTIME
// ============================================================================

struct ChatRealtimeHandle {
    stop_flag: Arc<AtomicBool>,
    join_handle: JoinHandle<()>,
}

impl ChatRealtimeHandle {
    fn stop(self) {
        self.stop_flag.store(true, Ordering::Relaxed);
        self.join_handle.abort();
    }
}

#[derive(Default, Clone)]
pub struct ChatRuntime {
    inner: Arc<Mutex<Option<ChatRealtimeHandle>>>,
    last_sessions: Arc<Mutex<Vec<ChatSession>>>,
    last_unread_count: Arc<Mutex<u32>>,
    is_connected: Arc<AtomicBool>,
}

impl ChatRuntime {
    pub fn new() -> Self {
        // Tentar restaurar estado persistido
        let (sessions, unread) = match load_chat_state() {
            Some(state) => (state.sessions, state.last_unread_count),
            None => (Vec::new(), 0),
        };

        Self {
            inner: Arc::new(Mutex::new(None)),
            last_sessions: Arc::new(Mutex::new(sessions)),
            last_unread_count: Arc::new(Mutex::new(unread)),
            is_connected: Arc::new(AtomicBool::new(false)),
        }
    }

    /// Retorna true se conexao WS Convex esta ativa
    pub fn is_using_sse(&self) -> bool {
        self.is_connected.load(Ordering::Relaxed)
    }

    /// Inicia o sistema de atualizacoes de chat via WebSocket do Convex
    pub fn start_polling(
        &self,
        base_url: String,
        convex_url: String,
        token: String,
        app: tauri::AppHandle,
    ) -> Result<(), String> {
        let sanitized_base = base_url.trim().trim_end_matches('/').to_string();
        if sanitized_base.is_empty() {
            return Err("URL base invalida".to_string());
        }
        let sanitized_convex = convex_url.trim().trim_end_matches('/').to_string();
        if sanitized_convex.is_empty() {
            return Err("URL do Convex inválida".to_string());
        }

        // Para polling/SSE existente
        {
            let mut guard = self.inner.lock();
            if let Some(handle) = guard.take() {
                handle.stop();
            }
        }

        let stop_flag = Arc::new(AtomicBool::new(false));
        let stop_clone = stop_flag.clone();
        let base_clone = sanitized_base.clone();
        let convex_clone = sanitized_convex.clone();
        let token_clone = token.clone();
        let last_sessions = self.last_sessions.clone();
        let last_unread_count = self.last_unread_count.clone();
        let is_connected = self.is_connected.clone();

        let join_handle = tauri::async_runtime::spawn(async move {
            crate::log_info!("[CHAT DEBUG] Iniciando sistema de chat");
            crate::log_info!("[CHAT DEBUG] Convex URL: {}", convex_clone);
            crate::log_info!("[CHAT DEBUG] API Base URL: {}", base_clone);

            let mut backoff_ms: u64 = 1_000;
            let max_backoff_ms: u64 = 30_000;
            let poll_interval = Duration::from_secs(5);
            let mut last_poll = Instant::now() - poll_interval;

            loop {
                if stop_clone.load(Ordering::Relaxed) {
                    break;
                }

                crate::log_info!("[CHAT DEBUG] Tentando conectar ao Convex...");
                let client_result = ConvexClient::new(&convex_clone).await;
                let mut client = match client_result {
                    Ok(c) => {
                        crate::log_info!("[CHAT DEBUG] Cliente Convex criado com sucesso");
                        c
                    }
                    Err(err) => {
                        is_connected.store(false, Ordering::Relaxed);
                        crate::log_warn!("[CHAT DEBUG] FALHA ao criar cliente Convex: {err:?}");

                        if last_poll.elapsed() >= poll_interval {
                            poll_and_process_chat_update(
                                &base_clone,
                                &token_clone,
                                &app,
                                &last_sessions,
                                &last_unread_count,
                            )
                            .await;
                            last_poll = Instant::now();
                        }

                        tokio::time::sleep(Duration::from_millis(backoff_ms)).await;
                        backoff_ms = backoff_ms.saturating_mul(2).min(max_backoff_ms);
                        continue;
                    }
                };

                let mut args = BTreeMap::new();
                args.insert("machineToken".to_string(), token_clone.clone().into());

                crate::log_info!("[CHAT DEBUG] Assinando liveChat:checkMachineUpdates...");
                let subscribe_result = client.subscribe("liveChat:checkMachineUpdates", args).await;
                let mut subscription = match subscribe_result {
                    Ok(sub) => {
                        is_connected.store(true, Ordering::Relaxed);
                        backoff_ms = 1_000;
                        crate::log_info!("[CHAT DEBUG] CONECTADO ao Convex WebSocket com sucesso!");
                        sub
                    }
                    Err(err) => {
                        is_connected.store(false, Ordering::Relaxed);
                        crate::log_warn!("[CHAT DEBUG] FALHA ao assinar checkMachineUpdates: {err:?}");

                        if last_poll.elapsed() >= poll_interval {
                            poll_and_process_chat_update(
                                &base_clone,
                                &token_clone,
                                &app,
                                &last_sessions,
                                &last_unread_count,
                            )
                            .await;
                            last_poll = Instant::now();
                        }

                        tokio::time::sleep(Duration::from_millis(backoff_ms)).await;
                        backoff_ms = backoff_ms.saturating_mul(2).min(max_backoff_ms);
                        continue;
                    }
                };

                crate::log_info!("[CHAT DEBUG] Entrando no loop de escuta WebSocket...");
                let mut update_count: u64 = 0;
                while let Some(next) = subscription.next().await {
                    update_count += 1;
                    if stop_clone.load(Ordering::Relaxed) {
                        crate::log_info!("[CHAT DEBUG] Stop flag detectado, saindo do loop");
                        break;
                    }
                    match next {
                        FunctionResult::Value(Value::Object(obj)) => {
                            let has_active = obj
                                .get("hasActiveSessions")
                                .and_then(|v| match v {
                                    Value::Boolean(b) => Some(*b),
                                    _ => None,
                                })
                                .unwrap_or(false);
                            let total_unread = obj
                                .get("totalUnread")
                                .and_then(|v| match v {
                                    Value::Int64(i) => Some(*i as u32),
                                    Value::Float64(f) => Some(*f as u32),
                                    _ => None,
                                })
                                .unwrap_or(0);

                            crate::log_info!(
                                "[CHAT DEBUG] UPDATE #{} recebido via WebSocket: hasActive={}, totalUnread={}",
                                update_count, has_active, total_unread
                            );

                            process_chat_update(
                                &base_clone,
                                &token_clone,
                                &app,
                                &last_sessions,
                                &last_unread_count,
                                has_active,
                                total_unread,
                            )
                            .await;
                        }
                        FunctionResult::ConvexError(err) => {
                            crate::log_warn!("[CHAT DEBUG] Convex error em checkMachineUpdates: {err:?}");
                        }
                        FunctionResult::ErrorMessage(msg) => {
                            crate::log_warn!("[CHAT DEBUG] Erro em checkMachineUpdates: {msg}");
                        }
                        FunctionResult::Value(other) => {
                            crate::log_warn!("[CHAT DEBUG] Payload inesperado em checkMachineUpdates: {other:?}");
                        }
                    }
                }

                is_connected.store(false, Ordering::Relaxed);

                if stop_clone.load(Ordering::Relaxed) {
                    crate::log_info!("[CHAT DEBUG] Stop flag detectado apos loop");
                    break;
                }

                crate::log_warn!("[CHAT DEBUG] WebSocket DESCONECTADO! Aplicando fallback e tentando reconectar...");
                if last_poll.elapsed() >= poll_interval {
                    poll_and_process_chat_update(
                        &base_clone,
                        &token_clone,
                        &app,
                        &last_sessions,
                        &last_unread_count,
                    )
                    .await;
                    last_poll = Instant::now();
                }

                tokio::time::sleep(Duration::from_millis(backoff_ms)).await;
                backoff_ms = backoff_ms.saturating_mul(2).min(max_backoff_ms);
            }

            is_connected.store(false, Ordering::Relaxed);
            crate::log_info!("Chat encerrado (realtime finalizado)");
        });

        let mut guard = self.inner.lock();
        *guard = Some(ChatRealtimeHandle {
            stop_flag,
            join_handle,
        });

        Ok(())
    }

    pub fn stop(&self) {
        let mut guard = self.inner.lock();
        if let Some(handle) = guard.take() {
            handle.stop();
        }
        self.is_connected.store(false, Ordering::Relaxed);
    }

    pub fn get_sessions(&self) -> Vec<ChatSession> {
        self.last_sessions.lock().clone()
    }
}

// ============================================================================
// SHARED UPDATE PROCESSING
// ============================================================================

async fn poll_and_process_chat_update(
    base_url: &str,
    token: &str,
    app: &tauri::AppHandle,
    last_sessions: &Arc<Mutex<Vec<ChatSession>>>,
    last_unread_count: &Arc<Mutex<u32>>,
) {
    crate::log_info!("[CHAT DEBUG] Executando fallback HTTP polling...");
    match poll_chat_updates(base_url, token, None).await {
        Ok(result) => {
            crate::log_info!(
                "[CHAT DEBUG] Polling OK: hasActive={}, totalUnread={}",
                result.has_active_sessions, result.total_unread
            );
            process_chat_update(
                base_url,
                token,
                app,
                last_sessions,
                last_unread_count,
                result.has_active_sessions,
                result.total_unread,
            )
            .await;
        }
        Err(err) => {
            crate::log_warn!("[CHAT DEBUG] Fallback poll FALHOU: {err}");
        }
    }
}

async fn process_chat_update(
    base_url: &str,
    token: &str,
    app: &tauri::AppHandle,
    last_sessions: &Arc<Mutex<Vec<ChatSession>>>,
    last_unread_count: &Arc<Mutex<u32>>,
    has_active_sessions: bool,
    total_unread: u32,
) {
    crate::log_info!(
        "[CHAT DEBUG] process_chat_update: hasActive={}, totalUnread={}",
        has_active_sessions, total_unread
    );

    // Buscar sessoes completas para ter dados corretos
    let mut current_sessions = if has_active_sessions {
        let sessions = fetch_sessions(base_url, token).await.unwrap_or_default();
        crate::log_info!("[CHAT DEBUG] Buscou {} sessoes ativas", sessions.len());
        sessions
    } else {
        crate::log_info!("[CHAT DEBUG] Sem sessoes ativas");
        Vec::new()
    };

    // Ordenar por ultima atividade (mais recente primeiro) para consistencia em UI/tray.
    if current_sessions.len() > 1 {
        current_sessions.sort_by(|a, b| {
            b.last_activity_at
                .cmp(&a.last_activity_at)
                .then_with(|| b.started_at.cmp(&a.started_at))
        });
    }

    // Verificar sessoes anteriores
    let prev_sessions: Vec<ChatSession> = last_sessions.lock().clone();
    let prev_session_ids: Vec<String> = prev_sessions.iter().map(|s| s.session_id.clone()).collect();
    let current_session_ids: Vec<String> = current_sessions.iter().map(|s| s.session_id.clone()).collect();

    // Detectar novas sessoes
    for session in &current_sessions {
        if !prev_session_ids.contains(&session.session_id) {
            crate::log_info!(
                "Nova sessao de chat: ticket={}, session={}",
                session.ticket_id,
                session.session_id
            );
            let _ = app.emit(
                "raven://chat/session-started",
                SessionStartedEvent {
                    session: session.clone(),
                },
            );

            // NÃO abre janela aqui - só quando o agente enviar a primeira mensagem
            // O chat aparecerá minimizado com badge quando houver novas mensagens

            crate::log_info!(
                "Sessão de chat iniciada pelo agente {}. Aguardando primeira mensagem.",
                session.agent_name
            );
        }
    }

    // Detectar sessoes encerradas
    for prev_session in &prev_sessions {
        if !current_session_ids.contains(&prev_session.session_id) {
            crate::log_info!(
                "Sessao de chat encerrada: ticket={}, session={}",
                prev_session.ticket_id,
                prev_session.session_id
            );
            let _ = app.emit(
                "raven://chat/session-ended",
                serde_json::json!({
                    "sessionId": prev_session.session_id,
                    "ticketId": prev_session.ticket_id
                }),
            );
        }
    }

    // =========================================================================
    // DETECCAO ROBUSTA DE NOVAS MENSAGENS
    // Usa DUAS estrategias: timestamp E contador (belt and suspenders)
    // =========================================================================

    let prev_unread = *last_unread_count.lock();

    // Estrategia 1: Detectar por lastActivityAt de cada sessao
    // Se alguma sessao teve atividade mais recente E tem mensagens nao lidas -> nova mensagem
    let mut detected_by_activity = false;
    let mut activity_details = String::new();

    for session in &current_sessions {
        let prev_activity = prev_sessions
            .iter()
            .find(|s| s.session_id == session.session_id)
            .map(|s| s.last_activity_at)
            .unwrap_or(0);

        // Se lastActivityAt aumentou E ha mensagens nao lidas -> nova mensagem do agente
        if session.last_activity_at > prev_activity && session.unread_count > 0 {
            detected_by_activity = true;
            activity_details = format!(
                "sessao={} activity: {} -> {} unread={}",
                session.ticket_id, prev_activity, session.last_activity_at, session.unread_count
            );
            break;
        }
    }

    // Estrategia 2: Fallback por contador total (metodo original)
    let detected_by_count = total_unread > prev_unread;

    // Nova mensagem se QUALQUER estrategia detectar
    let new_messages = detected_by_activity || detected_by_count;

    // Log detalhado para diagnostico
    crate::log_info!(
        "[CHAT] Deteccao: by_activity={} by_count={} (prev={} curr={}) resultado={}",
        detected_by_activity, detected_by_count, prev_unread, total_unread, new_messages
    );
    if detected_by_activity {
        crate::log_info!("[CHAT] Detectado por atividade: {}", activity_details);
    }

    // Atualizar caches APOS deteccao (importante: manter ordem)
    *last_sessions.lock() = current_sessions.clone();
    *last_unread_count.lock() = total_unread;

    // Persistir estado para sobreviver a restarts
    save_chat_state(total_unread, &current_sessions);

    // Sempre emitir unread-update
    let _ = app.emit(
        "raven://chat/unread-update",
        serde_json::json!({
            "totalUnread": total_unread,
            "sessions": current_sessions
        }),
    );

    // Notificar novas mensagens - mostrar chat minimizado com badge
    if new_messages && total_unread > 0 {
        let new_count = if total_unread > prev_unread {
            total_unread - prev_unread
        } else {
            1 // Se detectou por activity mas contador nao mudou, assumir 1 nova
        };

        crate::log_info!(
            "[CHAT] NOVAS MENSAGENS! count={}, total={}, metodo={}",
            new_count, total_unread,
            if detected_by_activity { "activity" } else { "count" }
        );

        let _ = app.emit(
            "raven://chat/new-message",
            serde_json::json!({
                "totalUnread": total_unread,
                "newCount": new_count,
                "sessions": current_sessions
            }),
        );

        // Escolher qual sessao/ticket deve ser mostrado quando ha multiplas sessoes.
        // Preferencia: maior incremento de unread (delta) e, em empate, ultima atividade mais recente.
        let mut best_session: Option<&ChatSession> = None;
        let mut best_delta: u32 = 0;

        for session in &current_sessions {
            let prev_unread_for_ticket = prev_sessions
                .iter()
                .find(|s| s.ticket_id == session.ticket_id)
                .map(|s| s.unread_count)
                .unwrap_or(0);
            let delta = session.unread_count.saturating_sub(prev_unread_for_ticket);

            let is_better = if delta > best_delta {
                true
            } else if delta == best_delta {
                match best_session {
                    Some(best) => session.last_activity_at > best.last_activity_at,
                    None => true,
                }
            } else {
                false
            };

            if is_better {
                best_delta = delta;
                best_session = Some(session);
            }
        }

        // Se ha multiplas sessoes ativas, usar o hub; senao, abrir janela do chat individual.
        //
        // Importante (UX): em multiplas sessoes, NAO fecha a janela ativa quando chega mensagem em outra conversa.
        // O hub + badge/notificacao sinalizam novas mensagens e o usuario decide quando alternar.
        if current_sessions.len() > 1 {
            let _ = open_hub_window(app);
        } else {
            // Uma sessao - nao precisa de hub
            let _ = close_hub_window(app);

            // Fallback: se nao conseguimos detectar delta, pega a sessao com mais unread e mais recente.
            let session_to_show = if best_delta > 0 {
                best_session
            } else {
                current_sessions.iter().max_by(|a, b| {
                    a.unread_count
                        .cmp(&b.unread_count)
                        .then_with(|| a.last_activity_at.cmp(&b.last_activity_at))
                })
            };

            // Mostrar janela de chat (sempre minimizada/nao intrusiva)
            if let Some(session) = session_to_show {
                let _ = open_chat_window_internal(app, &session.ticket_id, session.ticket_ref, true);
            }
        }

        // Notificacao nativa
        let notification_title = "Nova mensagem de suporte";
        let notification_body = if new_count == 1 {
            "Você recebeu 1 nova mensagem no chat".to_string()
        } else {
            format!("Você recebeu {} novas mensagens no chat", new_count)
        };
        let _ = app
            .notification()
            .builder()
            .title(notification_title)
            .body(&notification_body)
            .show();
    } else {
        // Log para debug quando NAO ha novas mensagens
        if total_unread == 0 {
            crate::log_info!("[CHAT DEBUG] Sem mensagens nao lidas (total=0)");
        } else if !new_messages {
            crate::log_info!(
                "[CHAT DEBUG] Sem novas mensagens (prev={} >= total={})",
                prev_unread, total_unread
            );
        }
    }
}

// ============================================================================
// WINDOW MANAGEMENT
// ============================================================================

// Serializa operacoes de janela para evitar race/deadlock no Windows (winit/WebView2).
static WINDOW_OP_LOCK: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));

fn resolve_chat_window_position(
    app: &tauri::AppHandle,
    window: Option<&tauri::WebviewWindow>,
    width: f64,
    height: f64,
) -> (f64, f64) {
    let margin = 20.0;
    let taskbar_height = 50.0;

    let monitor = window
        .and_then(|w| w.current_monitor().ok().flatten())
        .or_else(|| {
            app.get_webview_window("main")
                .and_then(|w| w.current_monitor().ok().flatten())
        })
        .or_else(|| app.available_monitors().ok().and_then(|monitors| monitors.into_iter().next()));

    let Some(monitor) = monitor else {
        return (100.0, 100.0);
    };

    let size = monitor.size();
    let pos = monitor.position();
    let scale = monitor.scale_factor();

    // Converter coordenadas do monitor para coordenadas logicas (multi-monitor pode ter origem negativa).
    let monitor_x = pos.x as f64 / scale;
    let monitor_y = pos.y as f64 / scale;
    let monitor_width = size.width as f64 / scale;
    let monitor_height = size.height as f64 / scale;

    let max_x = monitor_x + monitor_width - width - margin;
    let max_y = monitor_y + monitor_height - height - margin - taskbar_height;

    let x = if max_x.is_finite() { max_x.max(monitor_x) } else { 100.0 };
    let y = if max_y.is_finite() { max_y.max(monitor_y) } else { 100.0 };

    (x, y)
}

fn open_chat_window_internal(app: &tauri::AppHandle, ticket_id: &str, ticket_ref: u64, start_minimized: bool) -> Result<(), String> {
    let _guard = WINDOW_OP_LOCK.lock();
    open_chat_window_with_state(app, ticket_id, ticket_ref, start_minimized)
}

/// Abre janela de chat com estado inicial de minimizacao configuravel
fn open_chat_window_with_state(app: &tauri::AppHandle, ticket_id: &str, ticket_ref: u64, start_minimized: bool) -> Result<(), String> {
    let label = format!("chat-{}", ticket_id);
    crate::log_info!(
        "[WINDOW] open_chat_window: label={} ticket_ref={} start_minimized={}",
        label,
        ticket_ref,
        start_minimized
    );

    // Verificar se ja existe
    if let Some(window) = app.get_webview_window(&label) {
        let _ = window.set_ignore_cursor_events(false);
        crate::log_info!("[WINDOW] {}: window existe -> show()", label);
        window.show().map_err(|e| e.to_string())?;
        let _ = window.unminimize();
        if !start_minimized {
            crate::log_info!("[WINDOW] {}: window existe -> set_focus()", label);
            window.set_focus().map_err(|e| e.to_string())?;
        }
        // Expandir a janela se estiver minimizada (quando clicado na lista)
        if !start_minimized {
            crate::log_info!("[WINDOW] {}: window existe -> set_chat_minimized(false)", label);
            let _ = set_chat_minimized_unlocked(app, ticket_id, false);
        }
        crate::log_info!("[WINDOW] {}: open_chat_window OK (reuso)", label);
        return Ok(());
    }

    // Dimensoes baseadas no estado inicial
    let (width, height) = if start_minimized {
        (240.0, 52.0) // Tamanho minimizado (chip com badge)
    } else {
        (380.0, 520.0) // Tamanho expandido
    };

    // Posicionar no canto inferior direito (acima da barra de tarefas).
    let (x, y) = resolve_chat_window_position(app, None, width, height);

    // Usar query param ao inves de path para compatibilidade com SPA
    let url_path = format!("index.html?view=chat&ticketId={}&ticketRef={}", ticket_id, ticket_ref);

    crate::log_info!(
        "[WINDOW] {}: build() inicio size={}x{} pos=({},{}) url={}",
        label,
        width,
        height,
        x,
        y,
        url_path
    );

    let window = WebviewWindowBuilder::new(
        app,
        &label,
        WebviewUrl::App(url_path.into()),
    )
    .title("Chat de Suporte")
    .inner_size(width, height) // Abre ja no tamanho correto
    .min_inner_size(240.0, 52.0) // Tamanho minimo para modo minimizado com badge
    .position(x, y)
    .decorations(false) // Sem decoracoes nativas - usa header customizado
    .transparent(true) // Permite fundo transparente
    .shadow(false) // Desabilitar sombra para transparencia funcionar corretamente
    .resizable(false) // Desabilitar redimensionamento manual
    // Mantem o chat acessivel mesmo ao trocar de janela/app (skip_taskbar=true).
    .always_on_top(true)
    .skip_taskbar(true)
    .focused(!start_minimized)
    .visible(true)
    .build()
    .map_err(|e| e.to_string())?;

    crate::log_info!("[WINDOW] {}: build() OK", label);

    // IMPORTANTE: Garantir que a janela receba eventos de cursor (evita click-through)
    let _ = window.set_ignore_cursor_events(false);

    crate::log_info!("[WINDOW] {}: pos-build set_chat_minimized({}) inicio", label, start_minimized);
    // Reaplica layout/posicao logo apos criar a janela.
    // Isso evita que a primeira abertura apareca no canto superior esquerdo em alguns ambientes.
    let _ = set_chat_minimized_unlocked(app, ticket_id, start_minimized);
    crate::log_info!("[WINDOW] {}: pos-build set_chat_minimized({}) fim", label, start_minimized);

    crate::log_info!("Janela de chat aberta (minimizada={}): {}", start_minimized, label);
    Ok(())
}

pub fn open_chat_window(app: &tauri::AppHandle, ticket_id: &str, ticket_ref: u64) -> Result<(), String> {
    // Quando chamado explicitamente (ex: clique no hub), abre expandida
    open_chat_window_internal(app, ticket_id, ticket_ref, false)
}

pub fn close_chat_window(app: &tauri::AppHandle, ticket_id: &str) -> Result<(), String> {
    let _guard = WINDOW_OP_LOCK.lock();
    let label = format!("chat-{}", ticket_id);
    if let Some(window) = app.get_webview_window(&label) {
        window.close().map_err(|e| e.to_string())?;
    }
    Ok(())
}

pub fn minimize_chat_window(app: &tauri::AppHandle, ticket_id: &str) -> Result<(), String> {
    let _guard = WINDOW_OP_LOCK.lock();
    let label = format!("chat-{}", ticket_id);
    if let Some(window) = app.get_webview_window(&label) {
        window.hide().map_err(|e| e.to_string())?;
    }
    Ok(())
}

/// Redimensiona a janela de chat para modo minimizado (chip) ou expandido
fn set_chat_minimized_unlocked(app: &tauri::AppHandle, ticket_id: &str, minimized: bool) -> Result<(), String> {
    let label = format!("chat-{}", ticket_id);
    let window = app.get_webview_window(&label).ok_or("Janela não encontrada")?;

    // Tamanhos - chip minimizado com margem extra para badge (absolute -top-1 -right-1)
    let (width, height) = if minimized {
        (240.0, 52.0) // Tamanho com folga para "Ticket #XXX" e badge
    } else {
        (380.0, 520.0) // Tamanho expandido
    };

    // Calcular posicao no canto inferior direito do monitor atual (com fallback seguro).
    let (x, y) = resolve_chat_window_position(app, Some(&window), width, height);

    // Aplicar novo tamanho e posicao
    crate::log_info!("[WINDOW] {}: set_chat_minimized({}) set_size inicio", label, minimized);
    window.set_size(tauri::LogicalSize::new(width, height)).map_err(|e| e.to_string())?;
    crate::log_info!("[WINDOW] {}: set_chat_minimized({}) set_size OK", label, minimized);
    crate::log_info!("[WINDOW] {}: set_chat_minimized({}) set_position inicio", label, minimized);
    window.set_position(tauri::LogicalPosition::new(x, y)).map_err(|e| e.to_string())?;
    crate::log_info!("[WINDOW] {}: set_chat_minimized({}) set_position OK", label, minimized);

    crate::log_info!("Chat {} -> minimized={}", ticket_id, minimized);
    Ok(())
}

pub fn set_chat_minimized(app: &tauri::AppHandle, ticket_id: &str, minimized: bool) -> Result<(), String> {
    let _guard = WINDOW_OP_LOCK.lock();
    set_chat_minimized_unlocked(app, ticket_id, minimized)
}

// ============================================================================
// HUB WINDOW MANAGEMENT (Lista de todas as sessoes)
// ============================================================================

const HUB_WINDOW_LABEL: &str = "chat-hub";

pub fn open_hub_window(app: &tauri::AppHandle) -> Result<(), String> {
    let _guard = WINDOW_OP_LOCK.lock();
    open_hub_window_with_state(app, true) // Por padrao abre minimizada
}

fn open_hub_window_with_state(app: &tauri::AppHandle, start_minimized: bool) -> Result<(), String> {
    // Verificar se ja existe
    if let Some(window) = app.get_webview_window(HUB_WINDOW_LABEL) {
        let _ = window.set_ignore_cursor_events(false);
        window.show().map_err(|e| e.to_string())?;
        let _ = window.unminimize();
        if !start_minimized {
            window.set_focus().map_err(|e| e.to_string())?;
        }
        return Ok(());
    }

    // Dimensoes baseadas no estado inicial
    let (width, height) = if start_minimized {
        (200.0, 52.0) // Tamanho minimizado (chip)
    } else {
        (400.0, 520.0) // Tamanho expandido (igual ao web)
    };

    // Posicionar no canto inferior direito
    let (x, y) = resolve_chat_window_position(app, None, width, height);

    // URL para modo hub
    let url_path = "index.html?view=chat&hub=true";

    WebviewWindowBuilder::new(
        app,
        HUB_WINDOW_LABEL,
        WebviewUrl::App(url_path.into()),
    )
    .title("Chats de Suporte")
    .inner_size(width, height)
    .min_inner_size(200.0, 52.0)
    .position(x, y)
    .decorations(false)
    .transparent(true)
    .shadow(false)
    .resizable(false) // Desabilitar redimensionamento manual
    // Mantem o hub acessivel mesmo ao trocar de janela/app (skip_taskbar=true).
    .always_on_top(true)
    .skip_taskbar(true)
    .focused(!start_minimized)
    .visible(true)
    .build()
    .map_err(|e| e.to_string())?;

    // IMPORTANTE: Garantir que a janela receba eventos de cursor (evita click-through)
    if let Some(hub) = app.get_webview_window(HUB_WINDOW_LABEL) {
        let _ = hub.set_ignore_cursor_events(false);
        if !start_minimized {
            let _ = hub.set_focus();
        }
    }

    // REMOVIDO TEMPORARIAMENTE: set_hub_minimized logo apos build pode causar
    // "resize em cima do resize" no timing errado do WebView2
    // let _ = set_hub_minimized(app, start_minimized);

    crate::log_info!("Hub window aberta (minimizada={})", start_minimized);
    Ok(())
}

pub fn close_hub_window(app: &tauri::AppHandle) -> Result<(), String> {
    let _guard = WINDOW_OP_LOCK.lock();
    if let Some(window) = app.get_webview_window(HUB_WINDOW_LABEL) {
        window.close().map_err(|e| e.to_string())?;
    }
    Ok(())
}

pub fn set_hub_minimized(app: &tauri::AppHandle, minimized: bool) -> Result<(), String> {
    let _guard = WINDOW_OP_LOCK.lock();
    let window = app.get_webview_window(HUB_WINDOW_LABEL).ok_or("Hub window não encontrada")?;

    let (width, height) = if minimized {
        (200.0, 52.0) // Chip minimizado
    } else {
        (400.0, 520.0) // Lista expandida (igual ao web)
    };

    let (x, y) = resolve_chat_window_position(app, Some(&window), width, height);

    // IGUAL AO CHAT: primeiro size, depois position (ordem importa para hit-test no Windows)
    window.set_size(tauri::LogicalSize::new(width, height)).map_err(|e| e.to_string())?;
    window.set_position(tauri::LogicalPosition::new(x, y)).map_err(|e| e.to_string())?;

    // Foco apenas quando expandir (evita roubar foco ao minimizar apos abrir um chat).
    if !minimized {
        let _ = window.set_focus();
    }

    crate::log_info!("Hub -> minimized={}, size={}x{}, pos=({},{})", minimized, width, height, x, y);
    Ok(())
}
