use serde::{Deserialize, Serialize};
use tauri::State;

use crate::crypto::key_manager;
use crate::db::connection::DbManager;
use crate::db::schema;
use crate::error::AppError;
use crate::shared::session::SessionState;

#[derive(Deserialize)]
pub struct PinData {
    pub pin: String,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct OfflineResult {
    pub success: bool,
    pub error: Option<String>,
    pub user_id: Option<String>,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct OfflineModeStatus {
    pub enabled: bool,
    pub sync_interval: i64,
    pub has_pin: bool,
    pub last_user_id: Option<String>,
}

#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OfflineModeData {}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SyncCheckResult {
    pub should_sync: bool,
    pub days_since_sync: Option<i64>,
    pub sync_interval: Option<i64>,
}

#[tauri::command]
pub fn offline_pin_set(data: PinData, session: State<SessionState>) -> Result<OfflineResult, AppError> {
    let session_guard = session.lock().map_err(|e| AppError::Internal(format!("Session lock failed: {}", e)))?;
    let user_id = session_guard.get_user_id().map_err(|e| AppError::Auth(e))?.to_string();
    drop(session_guard);

    let hashed_pin: String = bcrypt::hash(&data.pin, 10)
        .map_err(|e| AppError::Encryption(format!("Failed to hash PIN: {}", e)))?;

    key_manager::set_pin_hash(&user_id, &hashed_pin)?;

    Ok(OfflineResult { success: true, error: None, user_id: None })
}

#[tauri::command]
pub fn offline_pin_verify(data: PinData, db: State<DbManager>, session: State<SessionState>) -> Result<OfflineResult, AppError> {
    key_manager::check_pin_rate_limit()?;

    let last_user_id: Option<String> = key_manager::get_last_user_id()?;
    let last_user_id: String = match last_user_id {
        Some(id) => id,
        None => return Ok(OfflineResult { success: false, error: Some("No offline account found".to_string()), user_id: None }),
    };

    let hashed_pin: Option<String> = key_manager::get_pin_hash(&last_user_id)?;
    let hashed_pin: String = match hashed_pin {
        Some(hash) => hash,
        None => return Ok(OfflineResult { success: false, error: Some("No PIN configured".to_string()), user_id: None }),
    };

    let is_valid: bool = bcrypt::verify(&data.pin, &hashed_pin)
        .map_err(|e| AppError::Encryption(format!("Failed to verify PIN: {}", e)))?;

    if !is_valid {
        key_manager::record_pin_failure()?;
        return Ok(OfflineResult { success: false, error: Some("Invalid PIN".to_string()), user_id: None });
    }

    key_manager::reset_pin_attempts()?;

    let has_key: bool = key_manager::has_user_encryption_key(&last_user_id)?;
    if !has_key {
        return Ok(OfflineResult { success: false, error: Some("No encryption key found".to_string()), user_id: None });
    }

    let mut db_manager = db.lock().map_err(|e| AppError::Internal(format!("DB lock failed: {}", e)))?;
    db_manager.initialize(&last_user_id)?;
    let conn = db_manager.get_connection(&last_user_id)?;
    schema::run_migrations(conn)?;

    let mut session_guard = session.lock().map_err(|e| AppError::Internal(format!("Session lock failed: {}", e)))?;
    session_guard.user_id = Some(last_user_id.clone());

    Ok(OfflineResult { success: true, error: None, user_id: Some(last_user_id) })
}

#[tauri::command]
pub fn offline_mode_get() -> Result<OfflineModeStatus, AppError> {
    let last_user_id: Option<String> = key_manager::get_last_user_id()?;
    let has_pin: bool = match &last_user_id {
        Some(id) => key_manager::get_pin_hash(id)?.is_some(),
        None => false,
    };

    Ok(OfflineModeStatus {
        enabled: false,
        sync_interval: 30,
        has_pin,
        last_user_id,
    })
}

#[tauri::command]
pub fn offline_mode_set(_data: OfflineModeData) -> Result<bool, AppError> {
    // Offline mode preferences are handled by the frontend/keyring
    // This is a no-op placeholder that the frontend can call
    Ok(true)
}

#[tauri::command]
pub fn offline_sync_check() -> Result<SyncCheckResult, AppError> {
    Ok(SyncCheckResult { should_sync: false, days_since_sync: None, sync_interval: None })
}
