use chrono::{Datelike, Local, NaiveDate};
use rand::Rng;
use rusqlite::params;

use crate::db::Database;

/// Populate the database with realistic demo data spanning ~2 years.
/// Assumes the database already has default categories seeded.
pub fn seed_demo_data(db: &Database) -> Result<(), Box<dyn std::error::Error>> {
    let mut rng = rand::thread_rng();
    let today = Local::now().date_naive();
    let start = NaiveDate::from_ymd_opt(today.year() - 2, today.month(), 1).unwrap();

    // -- Settings --
    db.set_setting("base_currency", "USD")?;
    db.set_setting("theme", "system")?;

    // -- Look up category IDs --
    let cats: Vec<(i64, String, String)> = db.conn.prepare(
        "SELECT id, name, type FROM categories ORDER BY id"
    )?.query_map([], |row| {
        Ok((row.get(0)?, row.get(1)?, row.get(2)?))
    })?.filter_map(|r| r.ok()).collect();

    let cat_id = |name: &str| -> i64 {
        cats.iter().find(|(_, n, _)| n == name).map(|(id, _, _)| *id).unwrap_or(1)
    };

    // Expense category IDs
    let food_id = cat_id("Food and Dining");
    let groceries_id = cat_id("Groceries");
    let transport_id = cat_id("Transport");
    let housing_id = cat_id("Housing/Rent");
    let utilities_id = cat_id("Utilities");
    let entertainment_id = cat_id("Entertainment");
    let shopping_id = cat_id("Shopping");
    let health_id = cat_id("Health");
    let education_id = cat_id("Education");
    let subscriptions_id = cat_id("Subscriptions");
    let personal_id = cat_id("Personal Care");
    let gifts_id = cat_id("Gifts");
    let travel_id = cat_id("Travel");

    // Income category IDs
    let salary_id = cat_id("Salary");
    let freelance_id = cat_id("Freelance");
    let investment_id = cat_id("Investment");
    let gift_income_id = cat_id("Gift");
    let refund_id = cat_id("Refund");

    // Realistic payees and notes per category
    let food_payees = ["Chipotle", "Starbucks", "Panda Express", "Subway", "Pizza Hut",
                       "Local Diner", "Thai Kitchen", "Burger Joint", "Sushi Bar", "Taco Bell"];
    let grocery_payees = ["Whole Foods", "Trader Joe's", "Kroger", "Costco", "Aldi",
                          "Safeway", "Target", "Walmart"];
    let transport_notes = ["Gas station", "Bus pass", "Uber ride", "Lyft", "Parking",
                           "Car wash", "Oil change", "Tire rotation"];
    let entertainment_notes = ["Movie tickets", "Netflix", "Concert", "Board game",
                               "Bowling", "Escape room", "Museum", "Book"];
    let shopping_payees = ["Amazon", "Target", "Best Buy", "IKEA", "Home Depot",
                           "Etsy", "Thrift store"];
    let health_notes = ["Pharmacy", "Doctor copay", "Gym membership", "Vitamins",
                        "Dentist", "Eye exam"];
    let personal_notes = ["Haircut", "Toiletries", "Dry cleaning", "Laundry"];

    // Helper: random float in range
    let rand_amount = |rng: &mut rand::rngs::ThreadRng, low: f64, high: f64| -> f64 {
        let val = rng.gen_range(low..high);
        (val * 100.0).round() / 100.0
    };

    let rand_pick = |rng: &mut rand::rngs::ThreadRng, items: &[&str]| -> String {
        items[rng.gen_range(0..items.len())].to_string()
    };

    let insert_txn = |date: NaiveDate, amount: f64, txn_type: &str, cat: i64,
                      note: Option<&str>, payee: Option<&str>| -> Result<(), Box<dyn std::error::Error>> {
        let date_str = date.format("%Y-%m-%d").to_string();
        let created = format!("{} 12:00:00", date_str);
        db.conn.execute(
            "INSERT INTO transactions (amount, type, category_id, currency, exchange_rate, note, date, created_at, payee)
             VALUES (?1, ?2, ?3, 'USD', 1.0, ?4, ?5, ?6, ?7)",
            params![amount, txn_type, cat, note, date_str, created, payee],
        )?;
        Ok(())
    };

    // -- Generate transactions month by month --
    let mut current = start;
    while current <= today {
        let year = current.year();
        let month = current.month();
        let days_in_month = if month == 12 {
            NaiveDate::from_ymd_opt(year + 1, 1, 1)
        } else {
            NaiveDate::from_ymd_opt(year, month + 1, 1)
        }.and_then(|d| d.pred_opt()).map(|d| d.day()).unwrap_or(30);

        let month_str = format!("{}-{:02}", year, month);

        // Monthly income: salary on the 1st and 15th (biweekly)
        let base_salary = 2850.0 + (year - start.year()) as f64 * 150.0;
        if let Some(d) = NaiveDate::from_ymd_opt(year, month, 1) {
            if d <= today {
                insert_txn(d, base_salary, "income", salary_id, Some("Biweekly paycheck"), Some("Acme Corp"))?;
            }
        }
        if let Some(d) = NaiveDate::from_ymd_opt(year, month, 15) {
            if d <= today {
                insert_txn(d, base_salary, "income", salary_id, Some("Biweekly paycheck"), Some("Acme Corp"))?;
            }
        }

        // Occasional freelance income (30% of months)
        if rng.gen_bool(0.3) {
            let day = rng.gen_range(5..=25).min(days_in_month);
            if let Some(d) = NaiveDate::from_ymd_opt(year, month, day) {
                if d <= today {
                    let amt = rand_amount(&mut rng, 200.0, 1200.0);
                    insert_txn(d, amt, "income", freelance_id, Some("Web dev project"), Some("Freelance client"))?;
                }
            }
        }

        // Investment dividends quarterly (March, June, Sept, Dec)
        if matches!(month, 3 | 6 | 9 | 12) {
            let day = rng.gen_range(10..=20).min(days_in_month);
            if let Some(d) = NaiveDate::from_ymd_opt(year, month, day) {
                if d <= today {
                    let amt = rand_amount(&mut rng, 50.0, 180.0);
                    insert_txn(d, amt, "income", investment_id, Some("Dividend payment"), Some("Vanguard"))?;
                }
            }
        }

        // Occasional refunds
        if rng.gen_bool(0.15) {
            let day = rng.gen_range(1..=days_in_month);
            if let Some(d) = NaiveDate::from_ymd_opt(year, month, day) {
                if d <= today {
                    let amt = rand_amount(&mut rng, 10.0, 80.0);
                    insert_txn(d, amt, "income", refund_id, Some("Return item"), Some("Amazon"))?;
                }
            }
        }

        // Birthday/holiday gift income (December, month of user)
        if month == 12 {
            if let Some(d) = NaiveDate::from_ymd_opt(year, month, 25) {
                if d <= today {
                    let amt = rand_amount(&mut rng, 50.0, 200.0);
                    insert_txn(d, amt, "income", gift_income_id, Some("Holiday gift"), None)?;
                }
            }
        }

        // -- EXPENSES --

        // Rent: 1st of every month
        if let Some(d) = NaiveDate::from_ymd_opt(year, month, 1) {
            if d <= today {
                insert_txn(d, 1350.00, "expense", housing_id, Some("Monthly rent"), Some("Pinewood Apartments"))?;
            }
        }

        // Utilities: ~10th of month
        if let Some(d) = NaiveDate::from_ymd_opt(year, month, 10.min(days_in_month)) {
            if d <= today {
                let electric = rand_amount(&mut rng, 60.0, 140.0);
                insert_txn(d, electric, "expense", utilities_id, Some("Electric bill"), Some("City Power Co"))?;
                let internet = 65.00;
                insert_txn(d, internet, "expense", utilities_id, Some("Internet"), Some("Comcast"))?;
            }
        }

        // Phone bill: 5th
        if let Some(d) = NaiveDate::from_ymd_opt(year, month, 5.min(days_in_month)) {
            if d <= today {
                insert_txn(d, 45.00, "expense", subscriptions_id, Some("Phone plan"), Some("Mint Mobile"))?;
            }
        }

        // Streaming subscriptions: 1st
        if let Some(d) = NaiveDate::from_ymd_opt(year, month, 1) {
            if d <= today {
                insert_txn(d, 15.99, "expense", subscriptions_id, Some("Streaming service"), Some("Netflix"))?;
                insert_txn(d, 10.99, "expense", subscriptions_id, Some("Music streaming"), Some("Spotify"))?;
            }
        }

        // Groceries: 2-4 trips per month
        let grocery_trips = rng.gen_range(2..=4);
        for _ in 0..grocery_trips {
            let day = rng.gen_range(1..=days_in_month);
            if let Some(d) = NaiveDate::from_ymd_opt(year, month, day) {
                if d <= today {
                    let amt = rand_amount(&mut rng, 45.0, 160.0);
                    let payee = rand_pick(&mut rng, &grocery_payees);
                    insert_txn(d, amt, "expense", groceries_id, Some("Weekly groceries"), Some(&payee))?;
                }
            }
        }

        // Food and dining: 4-8 meals out per month
        let meals_out = rng.gen_range(4..=8);
        for _ in 0..meals_out {
            let day = rng.gen_range(1..=days_in_month);
            if let Some(d) = NaiveDate::from_ymd_opt(year, month, day) {
                if d <= today {
                    let amt = rand_amount(&mut rng, 8.0, 55.0);
                    let payee = rand_pick(&mut rng, &food_payees);
                    insert_txn(d, amt, "expense", food_id, None, Some(&payee))?;
                }
            }
        }

        // Transport: 2-5 per month
        let transport_count = rng.gen_range(2..=5);
        for _ in 0..transport_count {
            let day = rng.gen_range(1..=days_in_month);
            if let Some(d) = NaiveDate::from_ymd_opt(year, month, day) {
                if d <= today {
                    let amt = rand_amount(&mut rng, 5.0, 65.0);
                    let note = rand_pick(&mut rng, &transport_notes);
                    insert_txn(d, amt, "expense", transport_id, Some(&note), None)?;
                }
            }
        }

        // Entertainment: 1-3 per month
        let ent_count = rng.gen_range(1..=3);
        for _ in 0..ent_count {
            let day = rng.gen_range(1..=days_in_month);
            if let Some(d) = NaiveDate::from_ymd_opt(year, month, day) {
                if d <= today {
                    let amt = rand_amount(&mut rng, 10.0, 70.0);
                    let note = rand_pick(&mut rng, &entertainment_notes);
                    insert_txn(d, amt, "expense", entertainment_id, Some(&note), None)?;
                }
            }
        }

        // Shopping: 1-3 per month
        let shop_count = rng.gen_range(1..=3);
        for _ in 0..shop_count {
            let day = rng.gen_range(1..=days_in_month);
            if let Some(d) = NaiveDate::from_ymd_opt(year, month, day) {
                if d <= today {
                    let amt = rand_amount(&mut rng, 15.0, 120.0);
                    let payee = rand_pick(&mut rng, &shopping_payees);
                    insert_txn(d, amt, "expense", shopping_id, None, Some(&payee))?;
                }
            }
        }

        // Health: 0-2 per month
        let health_count = rng.gen_range(0..=2);
        for _ in 0..health_count {
            let day = rng.gen_range(1..=days_in_month);
            if let Some(d) = NaiveDate::from_ymd_opt(year, month, day) {
                if d <= today {
                    let amt = rand_amount(&mut rng, 15.0, 120.0);
                    let note = rand_pick(&mut rng, &health_notes);
                    insert_txn(d, amt, "expense", health_id, Some(&note), None)?;
                }
            }
        }

        // Personal care: 0-2 per month
        let personal_count = rng.gen_range(0..=2);
        for _ in 0..personal_count {
            let day = rng.gen_range(1..=days_in_month);
            if let Some(d) = NaiveDate::from_ymd_opt(year, month, day) {
                if d <= today {
                    let amt = rand_amount(&mut rng, 12.0, 60.0);
                    let note = rand_pick(&mut rng, &personal_notes);
                    insert_txn(d, amt, "expense", personal_id, Some(&note), None)?;
                }
            }
        }

        // Education: occasional (20% of months)
        if rng.gen_bool(0.2) {
            let day = rng.gen_range(1..=days_in_month);
            if let Some(d) = NaiveDate::from_ymd_opt(year, month, day) {
                if d <= today {
                    let amt = rand_amount(&mut rng, 15.0, 80.0);
                    insert_txn(d, amt, "expense", education_id, Some("Online course"), Some("Udemy"))?;
                }
            }
        }

        // Gifts: mainly November/December, occasionally otherwise
        let gift_chance = if matches!(month, 11 | 12) { 0.8 } else { 0.1 };
        if rng.gen_bool(gift_chance) {
            let day = rng.gen_range(1..=days_in_month);
            if let Some(d) = NaiveDate::from_ymd_opt(year, month, day) {
                if d <= today {
                    let amt = rand_amount(&mut rng, 20.0, 150.0);
                    insert_txn(d, amt, "expense", gifts_id, Some("Birthday/holiday gift"), None)?;
                }
            }
        }

        // Travel: 1-2 trips per year (spread across a few months)
        if rng.gen_bool(0.08) {
            for _ in 0..rng.gen_range(2..=4) {
                let day = rng.gen_range(1..=days_in_month);
                if let Some(d) = NaiveDate::from_ymd_opt(year, month, day) {
                    if d <= today {
                        let amt = rand_amount(&mut rng, 50.0, 400.0);
                        let notes = ["Hotel stay", "Flight", "Restaurant abroad", "Sightseeing"];
                        let note = rand_pick(&mut rng, &notes);
                        insert_txn(d, amt, "expense", travel_id, Some(&note), None)?;
                    }
                }
            }
        }

        // -- Budgets for this month --
        let budget_items: Vec<(i64, f64)> = vec![
            (groceries_id, 500.0),
            (food_id, 350.0),
            (transport_id, 200.0),
            (entertainment_id, 150.0),
            (shopping_id, 200.0),
            (utilities_id, 250.0),
            (subscriptions_id, 80.0),
            (health_id, 100.0),
            (personal_id, 75.0),
        ];
        for (cat, amt) in &budget_items {
            db.conn.execute(
                "INSERT OR IGNORE INTO budgets (category_id, amount, month) VALUES (?1, ?2, ?3)",
                params![cat, amt, month_str],
            )?;
        }

        // Advance to next month
        current = if month == 12 {
            NaiveDate::from_ymd_opt(year + 1, 1, 1).unwrap()
        } else {
            NaiveDate::from_ymd_opt(year, month + 1, 1).unwrap()
        };
    }

    // -- Recurring transactions (plain, non-subscription) --
    let two_years_ago = format!("{}-{:02}-01", today.year() - 2, today.month());
    let recurring_items: Vec<(f64, &str, i64, &str, &str)> = vec![
        (1350.00, "expense", housing_id,  "monthly", "Monthly rent"),
        (65.00,   "expense", utilities_id, "monthly", "Internet"),
    ];
    for (amount, txn_type, cat, freq, note) in &recurring_items {
        db.conn.execute(
            "INSERT INTO recurring_transactions (amount, type, category_id, currency, note, frequency, start_date, active)
             VALUES (?1, ?2, ?3, 'USD', ?4, ?5, ?6, 1)",
            params![amount, txn_type, cat, note, freq, two_years_ago],
        )?;
    }

    // -- Linked subscriptions + recurring --
    use crate::models::{Frequency, NewRecurringTransaction, TransactionType};

    let sub_services: Vec<(&str, f64, &str, &str)> = vec![
        ("Netflix",        15.99, "tabler-brand-netflix", "#E50914"),
        ("Spotify",        10.99, "tabler-brand-spotify", "#1DB954"),
        ("iCloud",          2.99, "tabler-cloud",         "#3693F3"),
        ("GitHub",          4.00, "tabler-brand-github",  "#333333"),
        ("Xbox Game Pass", 16.99, "tabler-brand-xbox",    "#107C10"),
    ];

    for (name, amount, _icon, _color) in &sub_services {
        // Find the subscription category by name
        let sub_cat_id: i64 = db.conn.query_row(
            "SELECT id FROM subscription_categories WHERE name = ?1",
            params![name],
            |row| row.get(0),
        ).unwrap_or_else(|_| {
            // Fallback to "Other" category
            db.conn.query_row(
                "SELECT id FROM subscription_categories WHERE name = 'Other'",
                [],
                |row| row.get(0),
            ).unwrap_or(1)
        });

        let start = chrono::NaiveDate::parse_from_str(&two_years_ago, "%Y-%m-%d")
            .unwrap_or(today);

        let new_rec = NewRecurringTransaction {
            amount: *amount,
            transaction_type: TransactionType::Expense,
            category_id: subscriptions_id,
            currency: "USD".to_string(),
            note: Some(name.to_string()),
            frequency: Frequency::Monthly,
            start_date: start,
            end_date: None,
            is_bill: true,
            reminder_days: 3,
            subscription_id: None,
        };
        db.insert_linked_recurring_and_subscription(&new_rec, sub_cat_id, name)?;
    }

    // -- Savings goals --
    db.conn.execute(
        "INSERT INTO savings_goals (name, target, saved, currency, deadline, color, icon)
         VALUES ('Emergency Fund', 10000.0, 6450.0, 'USD', ?1, '#27ae60', 'tabler-shield')",
        params![format!("{}-12-31", today.year())],
    )?;
    db.conn.execute(
        "INSERT INTO savings_goals (name, target, saved, currency, deadline, color, icon)
         VALUES ('Vacation Fund', 3000.0, 1820.0, 'USD', ?1, '#3498db', 'tabler-plane')",
        params![format!("{}-06-30", today.year() + 1)],
    )?;
    db.conn.execute(
        "INSERT INTO savings_goals (name, target, saved, currency, deadline, color, icon)
         VALUES ('New Laptop', 1500.0, 950.0, 'USD', ?1, '#9b59b6', 'tabler-device-laptop')",
        params![format!("{}-09-01", today.year())],
    )?;

    // -- Wishlist items --
    db.conn.execute(
        "INSERT INTO wishlist_items (name, amount, category_id, note, priority)
         VALUES ('Noise Cancelling Headphones', 299.99, ?1, 'Sony WH-1000XM5', 1)",
        params![shopping_id],
    )?;
    db.conn.execute(
        "INSERT INTO wishlist_items (name, amount, category_id, note, priority)
         VALUES ('Ergonomic Keyboard', 179.00, ?1, 'Kinesis Advantage 360', 2)",
        params![shopping_id],
    )?;
    db.conn.execute(
        "INSERT INTO wishlist_items (name, amount, category_id, note, priority)
         VALUES ('Camping Gear Set', 450.00, ?1, 'Tent + sleeping bag + mat', 3)",
        params![travel_id],
    )?;

    // -- Credit Cards --
    db.conn.execute(
        "INSERT INTO credit_cards (name, credit_limit, statement_close_day, due_day, min_payment_pct, current_balance, currency, color)
         VALUES ('Chase Sapphire', 8000.0, 25, 15, 2.0, 2340.0, 'USD', '#003087')",
        [],
    )?;
    db.conn.execute(
        "INSERT INTO credit_cards (name, credit_limit, statement_close_day, due_day, min_payment_pct, current_balance, currency, color)
         VALUES ('Amex Gold', 12000.0, 20, 10, 2.0, 890.0, 'USD', '#C4A000')",
        [],
    )?;

    // -- Purchased wishlist items --
    db.conn.execute(
        "INSERT INTO wishlist_items (name, amount, category_id, note, priority, purchased)
         VALUES ('Mechanical Keyboard', 149.99, ?1, 'Cherry MX Brown switches', 2, 1)",
        params![shopping_id],
    )?;
    db.conn.execute(
        "INSERT INTO wishlist_items (name, amount, category_id, note, priority, purchased)
         VALUES ('Running Shoes', 89.99, ?1, 'Nike Pegasus', 1, 1)",
        params![shopping_id],
    )?;

    // -- Achievements --
    let two_years_ago_dt = format!(
        "{}-{:02}-15 12:00:00",
        today.year() - 2,
        today.month()
    );
    let one_year_ago_dt = format!(
        "{}-{:02}-15 12:00:00",
        today.year() - 1,
        today.month()
    );
    let six_months_ago_dt = {
        let m = if today.month() > 6 { today.month() - 6 } else { today.month() + 6 };
        let y = if today.month() > 6 { today.year() } else { today.year() - 1 };
        format!("{}-{:02}-15 12:00:00", y, m)
    };
    db.conn.execute(
        "UPDATE achievements SET earned_at = ?1 WHERE name = 'First Transaction'",
        params![two_years_ago_dt],
    )?;
    db.conn.execute(
        "UPDATE achievements SET earned_at = ?1 WHERE name = '100 Transactions'",
        params![one_year_ago_dt],
    )?;
    db.conn.execute(
        "UPDATE achievements SET earned_at = ?1 WHERE name = 'Month Under Budget'",
        params![six_months_ago_dt],
    )?;

    // -- Transaction Templates --
    db.insert_template(
        "Morning Coffee",
        Some(5.50),
        TransactionType::Expense,
        food_id,
        "USD",
        Some("Starbucks"),
        Some("Daily coffee"),
        None,
    )?;
    db.insert_template(
        "Weekly Groceries",
        Some(85.00),
        TransactionType::Expense,
        groceries_id,
        "USD",
        Some("Trader Joe's"),
        Some("Weekly grocery run"),
        None,
    )?;

    // -- Tags --
    db.conn.execute("INSERT OR IGNORE INTO tags (name) VALUES ('essential')", [])?;
    db.conn.execute("INSERT OR IGNORE INTO tags (name) VALUES ('splurge')", [])?;
    db.conn.execute("INSERT OR IGNORE INTO tags (name) VALUES ('recurring')", [])?;
    db.conn.execute("INSERT OR IGNORE INTO tags (name) VALUES ('work-related')", [])?;

    // -- Categorization rules --
    db.conn.execute(
        "INSERT INTO categorization_rules (field, pattern, category_id, priority)
         VALUES ('payee', 'Starbucks', ?1, 1)",
        params![food_id],
    )?;
    db.conn.execute(
        "INSERT INTO categorization_rules (field, pattern, category_id, priority)
         VALUES ('payee', 'Whole Foods', ?1, 1)",
        params![groceries_id],
    )?;
    db.conn.execute(
        "INSERT INTO categorization_rules (field, pattern, category_id, priority)
         VALUES ('payee', 'Amazon', ?1, 1)",
        params![shopping_id],
    )?;

    Ok(())
}
