use crate::db::Database;
use crate::exchange::ExchangeRateService;
use crate::models::{Frequency, NewTransaction};
use chrono::{Datelike, Days, NaiveDate};

/// Details about a generated recurring transaction.
pub struct GeneratedInfo {
    pub description: String,
    pub amount: f64,
    pub currency: String,
}

pub fn generate_missed_transactions(
    db: &Database,
    today: NaiveDate,
    base_currency: &str,
) -> Result<usize, rusqlite::Error> {
    let (count, _details) = generate_missed_transactions_detailed(db, today, base_currency)?;
    Ok(count)
}

pub fn generate_missed_transactions_detailed(
    db: &Database,
    today: NaiveDate,
    base_currency: &str,
) -> Result<(usize, Vec<GeneratedInfo>), rusqlite::Error> {
    let recurring = db.list_recurring(true)?;
    let mut count = 0;
    let mut details = Vec::new();
    let rate_service = ExchangeRateService::new(db);

    for rec in &recurring {
        let from = match rec.last_generated {
            Some(last) => next_date(last, rec.frequency),
            None => rec.start_date,
        };

        let until = match rec.end_date {
            Some(end) if end < today => end,
            _ => today,
        };

        let dates = generate_dates(from, until, rec.frequency);

        // Fetch exchange rate once per recurring (same currency for all dates)
        let exchange_rate = if rec.currency.eq_ignore_ascii_case(base_currency) {
            1.0
        } else {
            fetch_rate_sync(&rate_service, &rec.currency, base_currency).unwrap_or(1.0)
        };

        let desc = rec
            .note
            .as_deref()
            .filter(|s| !s.is_empty())
            .map(|s| s.to_string())
            .unwrap_or_else(|| {
                db.get_category(rec.category_id)
                    .map(|c| c.name)
                    .unwrap_or_else(|_| "Recurring".to_string())
            });

        for date in &dates {
            let txn = NewTransaction {
                amount: rec.amount,
                transaction_type: rec.transaction_type,
                category_id: rec.category_id,
                currency: rec.currency.clone(),
                exchange_rate,
                note: rec.note.clone(),
                date: *date,
                recurring_id: Some(rec.id),
                payee: None,
            };
            db.insert_transaction(&txn)?;
            count += 1;
            details.push(GeneratedInfo {
                description: desc.clone(),
                amount: rec.amount,
                currency: rec.currency.clone(),
            });
        }

        if let Some(&last) = dates.last() {
            db.update_recurring_last_generated(rec.id, last)?;
        }
    }

    Ok((count, details))
}

fn fetch_rate_sync(service: &ExchangeRateService<'_>, from: &str, to: &str) -> Option<f64> {
    let rt = tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()
        .ok()?;
    rt.block_on(service.get_rate(from, to)).ok()
}

fn next_date(date: NaiveDate, freq: Frequency) -> NaiveDate {
    match freq {
        Frequency::Daily => date.checked_add_days(Days::new(1)).unwrap_or(date),
        Frequency::Weekly => date.checked_add_days(Days::new(7)).unwrap_or(date),
        Frequency::Biweekly => date.checked_add_days(Days::new(14)).unwrap_or(date),
        Frequency::Monthly => add_months(date, 1),
        Frequency::Yearly => add_months(date, 12),
    }
}

/// Compute the next occurrence date for a recurring transaction from today.
pub fn next_occurrence(rec: &crate::models::RecurringTransaction, from: NaiveDate) -> Option<NaiveDate> {
    if !rec.active {
        return None;
    }
    // Start from last_generated + 1 period, or start_date
    let mut date = match rec.last_generated {
        Some(last) => next_date(last, rec.frequency),
        None => rec.start_date,
    };

    // Advance until we reach today or beyond
    while date < from {
        date = next_date(date, rec.frequency);
    }

    // Check end_date
    if let Some(end) = rec.end_date {
        if date > end {
            return None;
        }
    }

    Some(date)
}

fn generate_dates(from: NaiveDate, until: NaiveDate, freq: Frequency) -> Vec<NaiveDate> {
    let mut dates = Vec::new();
    let mut current = from;
    while current <= until {
        dates.push(current);
        current = next_date(current, freq);
    }
    dates
}

pub fn add_months(date: NaiveDate, months: u32) -> NaiveDate {
    let total_months = date.month0() + months;
    let new_year = date.year() + (total_months / 12) as i32;
    let new_month = (total_months % 12) + 1;
    let max_day = days_in_month(new_year, new_month);
    let new_day = date.day().min(max_day);
    NaiveDate::from_ymd_opt(new_year, new_month, new_day).unwrap_or(date)
}

fn days_in_month(year: i32, month: u32) -> u32 {
    match month {
        1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
        4 | 6 | 9 | 11 => 30,
        2 => {
            if (year % 4 == 0 && year % 100 != 0) || year % 400 == 0 {
                29
            } else {
                28
            }
        }
        _ => 30,
    }
}

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

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

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

        let rec = NewRecurringTransaction {
            amount: 5.0,
            transaction_type: TransactionType::Expense,
            category_id: cat_id,
            currency: "USD".to_string(),
            note: Some("Coffee".to_string()),
            frequency: Frequency::Daily,
            start_date: NaiveDate::from_ymd_opt(2026, 2, 24).unwrap(),
            end_date: None,
            is_bill: false,
            reminder_days: 3,
            subscription_id: None,
        };
        let rec_id = db.insert_recurring(&rec).unwrap();

        let today = NaiveDate::from_ymd_opt(2026, 3, 1).unwrap();
        db.update_recurring_last_generated(rec_id, NaiveDate::from_ymd_opt(2026, 2, 26).unwrap()).unwrap();

        let count = generate_missed_transactions(&db, today, "USD").unwrap();
        // Should generate Feb 27, Feb 28, Mar 1 = 3 transactions
        assert_eq!(count, 3);
    }

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

        let rec = NewRecurringTransaction {
            amount: 100.0,
            transaction_type: TransactionType::Expense,
            category_id: cat_id,
            currency: "USD".to_string(),
            note: None,
            frequency: Frequency::Monthly,
            start_date: NaiveDate::from_ymd_opt(2026, 1, 15).unwrap(),
            end_date: None,
            is_bill: false,
            reminder_days: 3,
            subscription_id: None,
        };
        let rec_id = db.insert_recurring(&rec).unwrap();

        db.update_recurring_last_generated(rec_id, NaiveDate::from_ymd_opt(2026, 1, 15).unwrap()).unwrap();

        let today = NaiveDate::from_ymd_opt(2026, 3, 20).unwrap();
        let count = generate_missed_transactions(&db, today, "USD").unwrap();
        // Should generate Feb 15 and Mar 15
        assert_eq!(count, 2);
    }

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

        let rec = NewRecurringTransaction {
            amount: 50.0,
            transaction_type: TransactionType::Expense,
            category_id: cat_id,
            currency: "USD".to_string(),
            note: None,
            frequency: Frequency::Daily,
            start_date: NaiveDate::from_ymd_opt(2026, 1, 1).unwrap(),
            end_date: Some(NaiveDate::from_ymd_opt(2026, 1, 5).unwrap()),
            is_bill: false,
            reminder_days: 3,
            subscription_id: None,
        };
        db.insert_recurring(&rec).unwrap();

        let today = NaiveDate::from_ymd_opt(2026, 3, 1).unwrap();
        let count = generate_missed_transactions(&db, today, "USD").unwrap();
        // end_date is Jan 5, generates Jan 1-5 = 5 transactions
        assert_eq!(count, 5);
    }

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

        let rec = NewRecurringTransaction {
            amount: 25.0,
            transaction_type: TransactionType::Expense,
            category_id: cat_id,
            currency: "USD".to_string(),
            note: None,
            frequency: Frequency::Weekly,
            start_date: NaiveDate::from_ymd_opt(2026, 2, 1).unwrap(),
            end_date: None,
            is_bill: false,
            reminder_days: 3,
            subscription_id: None,
        };
        db.insert_recurring(&rec).unwrap();

        let today = NaiveDate::from_ymd_opt(2026, 2, 22).unwrap();
        let count = generate_missed_transactions(&db, today, "USD").unwrap();
        // From Feb 1 weekly: Feb 1, 8, 15, 22 = 4
        assert_eq!(count, 4);
    }

    #[test]
    fn test_monthly_handles_month_end() {
        let result = add_months(
            NaiveDate::from_ymd_opt(2026, 1, 31).unwrap(),
            1,
        );
        assert_eq!(result, NaiveDate::from_ymd_opt(2026, 2, 28).unwrap());
    }
}
