use chrono::NaiveDate;
use serde::{Deserialize, Serialize};
use std::fmt;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum TransactionType {
    Expense,
    Income,
}

impl TransactionType {
    pub fn as_str(&self) -> &'static str {
        match self {
            TransactionType::Expense => "expense",
            TransactionType::Income => "income",
        }
    }

    pub fn from_str(s: &str) -> Option<Self> {
        match s {
            "expense" => Some(TransactionType::Expense),
            "income" => Some(TransactionType::Income),
            _ => None,
        }
    }
}

impl fmt::Display for TransactionType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(self.as_str())
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Frequency {
    Daily,
    Weekly,
    Biweekly,
    Monthly,
    Yearly,
}

impl Frequency {
    pub fn as_str(&self) -> &'static str {
        match self {
            Frequency::Daily => "daily",
            Frequency::Weekly => "weekly",
            Frequency::Biweekly => "biweekly",
            Frequency::Monthly => "monthly",
            Frequency::Yearly => "yearly",
        }
    }

    pub fn from_str(s: &str) -> Option<Self> {
        match s {
            "daily" => Some(Frequency::Daily),
            "weekly" => Some(Frequency::Weekly),
            "biweekly" => Some(Frequency::Biweekly),
            "monthly" => Some(Frequency::Monthly),
            "yearly" => Some(Frequency::Yearly),
            _ => None,
        }
    }
}

impl fmt::Display for Frequency {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(self.as_str())
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Category {
    pub id: i64,
    pub name: String,
    pub icon: Option<String>,
    pub color: Option<String>,
    pub transaction_type: TransactionType,
    pub is_default: bool,
    pub sort_order: i32,
    pub parent_id: Option<i64>,
}

#[derive(Debug, Clone)]
pub struct NewCategory {
    pub name: String,
    pub icon: Option<String>,
    pub color: Option<String>,
    pub transaction_type: TransactionType,
    pub sort_order: i32,
    pub parent_id: Option<i64>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Transaction {
    pub id: i64,
    pub amount: f64,
    pub transaction_type: TransactionType,
    pub category_id: i64,
    pub currency: String,
    pub exchange_rate: f64,
    pub note: Option<String>,
    pub date: NaiveDate,
    pub created_at: String,
    pub recurring_id: Option<i64>,
    pub payee: Option<String>,
}

#[derive(Debug, Clone)]
pub struct NewTransaction {
    pub amount: f64,
    pub transaction_type: TransactionType,
    pub category_id: i64,
    pub currency: String,
    pub exchange_rate: f64,
    pub note: Option<String>,
    pub date: NaiveDate,
    pub recurring_id: Option<i64>,
    pub payee: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Budget {
    pub id: i64,
    pub category_id: i64,
    pub amount: f64,
    pub month: String,
    pub rollover: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RecurringTransaction {
    pub id: i64,
    pub amount: f64,
    pub transaction_type: TransactionType,
    pub category_id: i64,
    pub currency: String,
    pub note: Option<String>,
    pub frequency: Frequency,
    pub start_date: NaiveDate,
    pub end_date: Option<NaiveDate>,
    pub last_generated: Option<NaiveDate>,
    pub active: bool,
    pub resume_date: Option<NaiveDate>,
    pub is_bill: bool,
    pub reminder_days: i32,
    pub subscription_id: Option<i64>,
}

#[derive(Debug, Clone)]
pub struct NewRecurringTransaction {
    pub amount: f64,
    pub transaction_type: TransactionType,
    pub category_id: i64,
    pub currency: String,
    pub note: Option<String>,
    pub frequency: Frequency,
    pub start_date: NaiveDate,
    pub end_date: Option<NaiveDate>,
    pub is_bill: bool,
    pub reminder_days: i32,
    pub subscription_id: Option<i64>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExchangeRate {
    pub base: String,
    pub target: String,
    pub rate: f64,
    pub fetched_at: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Tag {
    pub id: i64,
    pub name: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Split {
    pub id: i64,
    pub transaction_id: i64,
    pub category_id: i64,
    pub amount: f64,
    pub note: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TransactionTemplate {
    pub id: i64,
    pub name: String,
    pub amount: Option<f64>,
    pub transaction_type: TransactionType,
    pub category_id: i64,
    pub currency: String,
    pub payee: Option<String>,
    pub note: Option<String>,
    pub tags: Option<String>,
    pub sort_order: i32,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CategorizeRule {
    pub id: i64,
    pub field: String,
    pub pattern: String,
    pub category_id: i64,
    pub priority: i32,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SavingsGoal {
    pub id: i64,
    pub name: String,
    pub target: f64,
    pub saved: f64,
    pub currency: String,
    pub deadline: Option<NaiveDate>,
    pub color: Option<String>,
    pub icon: Option<String>,
    pub created_at: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WishlistItem {
    pub id: i64,
    pub name: String,
    pub amount: f64,
    pub category_id: Option<i64>,
    pub url: Option<String>,
    pub note: Option<String>,
    pub priority: i32,
    pub purchased: bool,
    pub created_at: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SubscriptionCategory {
    pub id: i64,
    pub name: String,
    pub icon: Option<String>,
    pub color: Option<String>,
    pub sort_order: i32,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Subscription {
    pub id: i64,
    pub name: String,
    pub amount: f64,
    pub currency: String,
    pub frequency: Frequency,
    pub category_id: i64,
    pub start_date: NaiveDate,
    pub next_due: NaiveDate,
    pub active: bool,
    pub note: Option<String>,
    pub url: Option<String>,
    pub recurring_id: Option<i64>,
}

#[derive(Debug, Clone)]
pub struct NewSubscription {
    pub name: String,
    pub amount: f64,
    pub currency: String,
    pub frequency: Frequency,
    pub category_id: i64,
    pub start_date: NaiveDate,
    pub note: Option<String>,
    pub url: Option<String>,
    pub recurring_id: Option<i64>,
}

#[derive(Debug, Clone)]
pub struct CreditCard {
    pub id: i64,
    pub name: String,
    pub credit_limit: Option<f64>,
    pub statement_close_day: i32,
    pub due_day: i32,
    pub min_payment_pct: f64,
    pub current_balance: f64,
    pub currency: String,
    pub color: Option<String>,
    pub active: bool,
}

#[derive(Debug, Clone)]
pub struct NewCreditCard {
    pub name: String,
    pub credit_limit: Option<f64>,
    pub statement_close_day: i32,
    pub due_day: i32,
    pub min_payment_pct: f64,
    pub currency: String,
    pub color: Option<String>,
}

#[derive(Debug, Clone)]
pub struct Achievement {
    pub id: i64,
    pub name: String,
    pub description: String,
    pub earned_at: Option<String>,
    pub icon: Option<String>,
}

#[derive(Debug, Clone)]
pub struct ParsedTransaction {
    pub amount: f64,
    pub category_name: Option<String>,
    pub category_id: Option<i64>,
    pub note: Option<String>,
    pub payee: Option<String>,
    pub transaction_type: TransactionType,
}

#[derive(Debug, Clone)]
pub struct SankeyNode {
    pub label: String,
    pub value: f64,
    pub color: (f64, f64, f64),
    pub y: f64,
    pub height: f64,
}

#[derive(Debug, Clone)]
pub struct SankeyFlow {
    pub from_idx: usize,
    pub to_idx: usize,
    pub value: f64,
    pub from_y: f64,
    pub to_y: f64,
    pub width: f64,
}

#[derive(Debug, Clone)]
pub struct SankeyLayout {
    pub left_nodes: Vec<SankeyNode>,
    pub right_nodes: Vec<SankeyNode>,
    pub center_y: f64,
    pub center_height: f64,
    pub flows_in: Vec<SankeyFlow>,
    pub flows_out: Vec<SankeyFlow>,
    pub net: f64,
}

#[derive(Debug, Clone)]
pub struct RecapCategory {
    pub category_name: String,
    pub category_icon: Option<String>,
    pub category_color: Option<String>,
    pub amount: f64,
    pub percentage: f64,
    pub change_pct: Option<f64>,
}

#[derive(Debug, Clone)]
pub struct MonthlyRecap {
    pub total_income: f64,
    pub total_expenses: f64,
    pub net: f64,
    pub transaction_count: i64,
    pub categories: Vec<RecapCategory>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BudgetCycleMode {
    Calendar,
    Payday,
    Rolling,
}

impl BudgetCycleMode {
    pub fn as_str(&self) -> &'static str {
        match self {
            BudgetCycleMode::Calendar => "calendar",
            BudgetCycleMode::Payday => "payday",
            BudgetCycleMode::Rolling => "rolling",
        }
    }

    pub fn from_str(s: &str) -> Self {
        match s {
            "payday" => BudgetCycleMode::Payday,
            "rolling" => BudgetCycleMode::Rolling,
            _ => BudgetCycleMode::Calendar,
        }
    }
}

impl fmt::Display for BudgetCycleMode {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(self.as_str())
    }
}

#[derive(Debug, Clone)]
pub struct PdfParsedRow {
    pub date: Option<NaiveDate>,
    pub description: String,
    pub amount: f64,
    pub is_credit: bool,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_transaction_type_round_trip() {
        for tt in [TransactionType::Expense, TransactionType::Income] {
            let s = tt.as_str();
            let parsed = TransactionType::from_str(s).unwrap();
            assert_eq!(tt, parsed);
        }
    }

    #[test]
    fn test_transaction_type_invalid() {
        assert_eq!(TransactionType::from_str("bogus"), None);
    }

    #[test]
    fn test_frequency_round_trip() {
        for f in [
            Frequency::Daily,
            Frequency::Weekly,
            Frequency::Biweekly,
            Frequency::Monthly,
            Frequency::Yearly,
        ] {
            let s = f.as_str();
            let parsed = Frequency::from_str(s).unwrap();
            assert_eq!(f, parsed);
        }
    }

    #[test]
    fn test_frequency_invalid() {
        assert_eq!(Frequency::from_str("quarterly"), None);
    }

    #[test]
    fn test_transaction_type_display() {
        assert_eq!(format!("{}", TransactionType::Expense), "expense");
        assert_eq!(format!("{}", TransactionType::Income), "income");
    }

    #[test]
    fn test_frequency_display() {
        assert_eq!(format!("{}", Frequency::Monthly), "monthly");
        assert_eq!(format!("{}", Frequency::Biweekly), "biweekly");
    }
}
