use crate::db::Database;
use crate::models::{Budget, Category, RecurringTransaction, Transaction};
use serde::Serialize;
use std::io::Write;

#[derive(Debug, Serialize, serde::Deserialize)]
pub struct ExportData {
    pub transactions: Vec<Transaction>,
    pub categories: Vec<Category>,
    pub budgets: Vec<Budget>,
    pub recurring: Vec<RecurringTransaction>,
}

#[derive(Debug)]
pub enum ExportError {
    Db(rusqlite::Error),
    Json(serde_json::Error),
    Io(std::io::Error),
}

impl From<rusqlite::Error> for ExportError {
    fn from(e: rusqlite::Error) -> Self {
        ExportError::Db(e)
    }
}

impl From<serde_json::Error> for ExportError {
    fn from(e: serde_json::Error) -> Self {
        ExportError::Json(e)
    }
}

impl From<std::io::Error> for ExportError {
    fn from(e: std::io::Error) -> Self {
        ExportError::Io(e)
    }
}

impl std::fmt::Display for ExportError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ExportError::Db(e) => write!(f, "Database error: {}", e),
            ExportError::Json(e) => write!(f, "JSON error: {}", e),
            ExportError::Io(e) => write!(f, "IO error: {}", e),
        }
    }
}

pub fn export_json<W: Write>(db: &Database, writer: W) -> Result<ExportData, ExportError> {
    let transactions = db.list_all_transactions(None, None)?;
    let categories = db.list_categories(None)?;
    let budgets = db.list_all_budgets()?;
    let recurring = db.list_recurring(false)?;

    let data = ExportData {
        transactions,
        categories,
        budgets,
        recurring,
    };

    serde_json::to_writer_pretty(writer, &data)?;
    Ok(data)
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::models::{NewTransaction, TransactionType};
    use chrono::NaiveDate;

    fn setup_db() -> Database {
        Database::open_in_memory().unwrap()
    }

    #[test]
    fn test_json_export_produces_valid_json() {
        let db = setup_db();
        let cats = db.list_categories(Some(TransactionType::Expense)).unwrap();

        let txn = NewTransaction {
            amount: 25.0,
            transaction_type: TransactionType::Expense,
            category_id: cats[0].id,
            currency: "USD".to_string(),
            exchange_rate: 1.0,
            note: Some("Test".to_string()),
            date: NaiveDate::from_ymd_opt(2026, 3, 1).unwrap(),
            recurring_id: None,
            payee: None,
        };
        db.insert_transaction(&txn).unwrap();

        let mut buf = Vec::new();
        let data = export_json(&db, &mut buf).unwrap();

        assert_eq!(data.transactions.len(), 1);
        assert!(!data.categories.is_empty());
        assert!(data.budgets.is_empty());
        assert!(data.recurring.is_empty());

        // Verify it's valid JSON by parsing it back
        let output = String::from_utf8(buf).unwrap();
        let parsed: serde_json::Value = serde_json::from_str(&output).unwrap();
        assert!(parsed["transactions"].is_array());
        assert!(parsed["categories"].is_array());
    }

    #[test]
    fn test_json_export_includes_all_sections() {
        let db = setup_db();

        let mut buf = Vec::new();
        let data = export_json(&db, &mut buf).unwrap();

        // Even with no transactions, we should have default categories
        assert!(!data.categories.is_empty());

        let output = String::from_utf8(buf).unwrap();
        assert!(output.contains("\"transactions\""));
        assert!(output.contains("\"categories\""));
        assert!(output.contains("\"budgets\""));
        assert!(output.contains("\"recurring\""));
    }

    #[test]
    fn test_json_export_transaction_fields() {
        let db = setup_db();
        let cats = db.list_categories(Some(TransactionType::Income)).unwrap();

        let txn = NewTransaction {
            amount: 500.0,
            transaction_type: TransactionType::Income,
            category_id: cats[0].id,
            currency: "EUR".to_string(),
            exchange_rate: 0.92,
            note: Some("Freelance".to_string()),
            date: NaiveDate::from_ymd_opt(2026, 2, 15).unwrap(),
            recurring_id: None,
            payee: None,
        };
        db.insert_transaction(&txn).unwrap();

        let mut buf = Vec::new();
        export_json(&db, &mut buf).unwrap();

        let output = String::from_utf8(buf).unwrap();
        assert!(output.contains("500.0"));
        assert!(output.contains("EUR"));
        assert!(output.contains("Freelance"));
    }
}
