//! Cliente IPC para comunicacao com o Raven Service
//!
//! Este modulo permite que o app Tauri se comunique com o Raven Service
//! via Named Pipes para executar operacoes privilegiadas.

#![allow(dead_code)]

use serde::{Deserialize, Serialize};
use std::io::{BufRead, BufReader, Write};
use std::time::Duration;
use thiserror::Error;

const PIPE_NAME: &str = r"\\.\pipe\RavenService";

#[derive(Debug, Error)]
pub enum ServiceClientError {
    #[error("Servico nao disponivel: {0}")]
    ServiceUnavailable(String),

    #[error("Erro de comunicacao: {0}")]
    CommunicationError(String),

    #[error("Erro de serializacao: {0}")]
    SerializationError(#[from] serde_json::Error),

    #[error("Erro do servico: {message} (code: {code})")]
    ServiceError { code: i32, message: String },

    #[error("Timeout aguardando resposta")]
    Timeout,
}

#[derive(Debug, Serialize)]
struct Request {
    id: String,
    method: String,
    params: serde_json::Value,
}

#[derive(Debug, Deserialize)]
struct Response {
    id: String,
    result: Option<serde_json::Value>,
    error: Option<ErrorResponse>,
}

#[derive(Debug, Deserialize)]
struct ErrorResponse {
    code: i32,
    message: String,
}

// =============================================================================
// Tipos de Resultado
// =============================================================================

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UsbPolicyResult {
    pub success: bool,
    pub policy: String,
    pub error: Option<String>,
    pub applied_at: Option<i64>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RustdeskResult {
    pub id: String,
    pub password: String,
    pub installed_version: Option<String>,
    pub updated: bool,
    pub last_provisioned_at: i64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RustdeskStatus {
    pub installed: bool,
    pub running: bool,
    pub id: Option<String>,
    pub version: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct HealthCheckResult {
    pub status: String,
    pub service: String,
    pub version: String,
    pub timestamp: i64,
}

// =============================================================================
// Cliente
// =============================================================================

/// Verifica se o servico esta disponivel
pub fn is_service_available() -> bool {
    health_check().is_ok()
}

/// Verifica saude do servico
pub fn health_check() -> Result<HealthCheckResult, ServiceClientError> {
    let response = call_service("health_check", serde_json::json!({}))?;
    serde_json::from_value(response).map_err(|e| e.into())
}

/// Aplica politica de USB
pub fn apply_usb_policy(policy: &str) -> Result<UsbPolicyResult, ServiceClientError> {
    let response = call_service(
        "apply_usb_policy",
        serde_json::json!({ "policy": policy }),
    )?;
    serde_json::from_value(response).map_err(|e| e.into())
}

/// Obtem politica de USB atual
pub fn get_usb_policy() -> Result<String, ServiceClientError> {
    let response = call_service("get_usb_policy", serde_json::json!({}))?;
    response
        .get("policy")
        .and_then(|p| p.as_str())
        .map(String::from)
        .ok_or_else(|| ServiceClientError::CommunicationError("Resposta invalida".into()))
}

/// Provisiona RustDesk
pub fn provision_rustdesk(
    config: Option<&str>,
    password: Option<&str>,
    machine_id: Option<&str>,
) -> Result<RustdeskResult, ServiceClientError> {
    let params = serde_json::json!({
        "config": config,
        "password": password,
        "machineId": machine_id,
    });
    let response = call_service("provision_rustdesk", params)?;
    serde_json::from_value(response).map_err(|e| e.into())
}

/// Obtem status do RustDesk
pub fn get_rustdesk_status() -> Result<RustdeskStatus, ServiceClientError> {
    let response = call_service("get_rustdesk_status", serde_json::json!({}))?;
    serde_json::from_value(response).map_err(|e| e.into())
}

// =============================================================================
// Comunicacao IPC
// =============================================================================

fn call_service(
    method: &str,
    params: serde_json::Value,
) -> Result<serde_json::Value, ServiceClientError> {
    // Gera ID unico para a requisicao
    let id = uuid::Uuid::new_v4().to_string();

    let request = Request {
        id: id.clone(),
        method: method.to_string(),
        params,
    };

    // Serializa requisicao
    let request_json = serde_json::to_string(&request)?;

    // Conecta ao pipe
    let mut pipe = connect_to_pipe()?;

    // Envia requisicao
    writeln!(pipe, "{}", request_json).map_err(|e| {
        ServiceClientError::CommunicationError(format!("Erro ao enviar requisicao: {}", e))
    })?;
    pipe.flush().map_err(|e| {
        ServiceClientError::CommunicationError(format!("Erro ao flush: {}", e))
    })?;

    // Le resposta
    let mut reader = BufReader::new(pipe);
    let mut response_line = String::new();

    reader.read_line(&mut response_line).map_err(|e| {
        ServiceClientError::CommunicationError(format!("Erro ao ler resposta: {}", e))
    })?;

    // Parse da resposta
    let response: Response = serde_json::from_str(&response_line)?;

    // Verifica se o ID bate
    if response.id != id {
        return Err(ServiceClientError::CommunicationError(
            "ID de resposta nao corresponde".into(),
        ));
    }

    // Verifica erro
    if let Some(error) = response.error {
        return Err(ServiceClientError::ServiceError {
            code: error.code,
            message: error.message,
        });
    }

    // Retorna resultado
    response
        .result
        .ok_or_else(|| ServiceClientError::CommunicationError("Resposta sem resultado".into()))
}

#[cfg(target_os = "windows")]
fn connect_to_pipe() -> Result<std::fs::File, ServiceClientError> {
    // Tenta conectar ao pipe com retry
    let mut attempts = 0;
    let max_attempts = 3;

    loop {
        match std::fs::OpenOptions::new()
            .read(true)
            .write(true)
            .open(PIPE_NAME)
        {
            Ok(file) => return Ok(file),
            Err(e) => {
                attempts += 1;
                if attempts >= max_attempts {
                    return Err(ServiceClientError::ServiceUnavailable(format!(
                        "Nao foi possivel conectar ao servico apos {} tentativas: {}",
                        max_attempts, e
                    )));
                }
                std::thread::sleep(Duration::from_millis(500));
            }
        }
    }
}

#[cfg(not(target_os = "windows"))]
fn connect_to_pipe() -> Result<std::fs::File, ServiceClientError> {
    Err(ServiceClientError::ServiceUnavailable(
        "Named Pipes so estao disponiveis no Windows".into(),
    ))
}
