//! Invoice Management

use crate::{AccountId, ServiceType, SynorDecimal};
use chrono::{DateTime, Datelike, Utc};
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use std::time::{SystemTime, UNIX_EPOCH};

/// Invoice status
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum InvoiceStatus {
    /// Invoice created, awaiting payment
    Draft,
    /// Invoice finalized and sent
    Pending,
    /// Payment received
    Paid,
    /// Payment failed or declined
    Failed,
    /// Invoice voided/cancelled
    Voided,
    /// Payment overdue
    Overdue,
}

impl InvoiceStatus {
    /// Check if this status represents a completed invoice
    pub fn is_terminal(&self) -> bool {
        matches!(self, Self::Paid | Self::Voided)
    }
}

impl std::fmt::Display for InvoiceStatus {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            InvoiceStatus::Draft => write!(f, "draft"),
            InvoiceStatus::Pending => write!(f, "pending"),
            InvoiceStatus::Paid => write!(f, "paid"),
            InvoiceStatus::Failed => write!(f, "failed"),
            InvoiceStatus::Voided => write!(f, "voided"),
            InvoiceStatus::Overdue => write!(f, "overdue"),
        }
    }
}

/// Invoice line item
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InvoiceLineItem {
    /// Description of the service/charge
    pub description: String,
    /// Service type
    pub service_type: ServiceType,
    /// Quantity
    pub quantity: SynorDecimal,
    /// Unit price in SYNOR
    pub unit_price: SynorDecimal,
    /// Line item total (quantity * unit_price)
    pub amount: SynorDecimal,
}

impl InvoiceLineItem {
    /// Create a new line item
    pub fn new(
        description: impl Into<String>,
        service_type: ServiceType,
        quantity: SynorDecimal,
        unit_price: SynorDecimal,
    ) -> Self {
        Self {
            description: description.into(),
            service_type,
            quantity,
            unit_price,
            amount: quantity * unit_price,
        }
    }
}

/// Invoice document
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Invoice {
    /// Unique invoice ID
    pub id: String,
    /// Invoice number (human-readable)
    pub number: String,
    /// Account being billed
    pub account_id: AccountId,
    /// Invoice status
    pub status: InvoiceStatus,
    /// Billing period start
    pub period_start: DateTime<Utc>,
    /// Billing period end
    pub period_end: DateTime<Utc>,
    /// Line items
    pub line_items: Vec<InvoiceLineItem>,
    /// Subtotal before discounts
    pub subtotal: SynorDecimal,
    /// Discount amount
    pub discount: SynorDecimal,
    /// Discount description
    pub discount_description: Option<String>,
    /// Tax amount (if applicable)
    pub tax: SynorDecimal,
    /// Total amount due
    pub total: SynorDecimal,
    /// Currency (always SYNOR)
    pub currency: String,
    /// Due date
    pub due_date: DateTime<Utc>,
    /// Date created
    pub created_at: DateTime<Utc>,
    /// Date paid (if paid)
    pub paid_at: Option<DateTime<Utc>>,
    /// Payment ID (if paid)
    pub payment_id: Option<String>,
    /// Notes/memo
    pub notes: Option<String>,
}

impl Invoice {
    /// Create a new invoice
    pub fn new(account_id: impl Into<String>) -> Self {
        let id = generate_invoice_id();
        let number = generate_invoice_number();

        Self {
            id,
            number,
            account_id: account_id.into(),
            status: InvoiceStatus::Draft,
            period_start: Utc::now(),
            period_end: Utc::now(),
            line_items: Vec::new(),
            subtotal: Decimal::ZERO,
            discount: Decimal::ZERO,
            discount_description: None,
            tax: Decimal::ZERO,
            total: Decimal::ZERO,
            currency: "SYNOR".to_string(),
            due_date: Utc::now(),
            created_at: Utc::now(),
            paid_at: None,
            payment_id: None,
            notes: None,
        }
    }

    /// Set billing period
    pub fn with_period(mut self, start: DateTime<Utc>, end: DateTime<Utc>) -> Self {
        self.period_start = start;
        self.period_end = end;
        self
    }

    /// Add line items
    pub fn with_line_items(mut self, items: Vec<InvoiceLineItem>) -> Self {
        self.line_items = items;
        self
    }

    /// Add a single line item
    pub fn add_line_item(mut self, item: InvoiceLineItem) -> Self {
        self.line_items.push(item);
        self
    }

    /// Set subtotal
    pub fn with_subtotal(mut self, subtotal: SynorDecimal) -> Self {
        self.subtotal = subtotal;
        self
    }

    /// Set discount
    pub fn with_discount(mut self, discount: SynorDecimal) -> Self {
        self.discount = discount;
        self
    }

    /// Set discount with description
    pub fn with_discount_desc(
        mut self,
        discount: SynorDecimal,
        description: impl Into<String>,
    ) -> Self {
        self.discount = discount;
        self.discount_description = Some(description.into());
        self
    }

    /// Set total
    pub fn with_total(mut self, total: SynorDecimal) -> Self {
        self.total = total;
        self
    }

    /// Set due date
    pub fn with_due_date(mut self, due_date: DateTime<Utc>) -> Self {
        self.due_date = due_date;
        self
    }

    /// Set notes
    pub fn with_notes(mut self, notes: impl Into<String>) -> Self {
        self.notes = Some(notes.into());
        self
    }

    /// Finalize invoice (move from draft to pending)
    pub fn finalize(&mut self) {
        if self.status == InvoiceStatus::Draft {
            self.status = InvoiceStatus::Pending;
        }
    }

    /// Mark invoice as paid
    pub fn mark_paid(&mut self, payment_id: String) {
        self.status = InvoiceStatus::Paid;
        self.paid_at = Some(Utc::now());
        self.payment_id = Some(payment_id);
    }

    /// Mark invoice as void
    pub fn void(&mut self) {
        self.status = InvoiceStatus::Voided;
    }

    /// Check if overdue and update status
    pub fn check_overdue(&mut self) {
        if self.status == InvoiceStatus::Pending && Utc::now() > self.due_date {
            self.status = InvoiceStatus::Overdue;
        }
    }

    /// Check if invoice is paid
    pub fn is_paid(&self) -> bool {
        self.status == InvoiceStatus::Paid
    }

    /// Check if invoice is overdue
    pub fn is_overdue(&self) -> bool {
        self.status == InvoiceStatus::Overdue
            || (self.status == InvoiceStatus::Pending && Utc::now() > self.due_date)
    }

    /// Calculate invoice from line items
    pub fn calculate(&mut self) {
        self.subtotal = self.line_items.iter().map(|item| item.amount).sum();
        self.total = self.subtotal - self.discount + self.tax;
    }

    /// Get amount remaining to be paid
    pub fn amount_due(&self) -> SynorDecimal {
        if self.is_paid() {
            Decimal::ZERO
        } else {
            self.total
        }
    }
}

/// Generate unique invoice ID
fn generate_invoice_id() -> String {
    let nanos = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_nanos();
    format!("inv_{:x}", nanos)
}

/// Generate human-readable invoice number
fn generate_invoice_number() -> String {
    let now = Utc::now();
    let seq = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .subsec_micros();
    format!("SYN-{:04}{:02}-{:06}", now.year(), now.month(), seq)
}

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

    #[test]
    fn test_invoice_creation() {
        let invoice = Invoice::new("account_123")
            .with_subtotal(dec!(100))
            .with_discount(dec!(10))
            .with_total(dec!(90));

        assert_eq!(invoice.account_id, "account_123");
        assert_eq!(invoice.subtotal, dec!(100));
        assert_eq!(invoice.discount, dec!(10));
        assert_eq!(invoice.total, dec!(90));
        assert_eq!(invoice.status, InvoiceStatus::Draft);
    }

    #[test]
    fn test_invoice_finalization() {
        let mut invoice = Invoice::new("account_123");
        assert_eq!(invoice.status, InvoiceStatus::Draft);

        invoice.finalize();
        assert_eq!(invoice.status, InvoiceStatus::Pending);
    }

    #[test]
    fn test_invoice_payment() {
        let mut invoice = Invoice::new("account_123").with_total(dec!(50));
        invoice.finalize();

        invoice.mark_paid("pay_123".to_string());
        assert!(invoice.is_paid());
        assert_eq!(invoice.payment_id, Some("pay_123".to_string()));
        assert!(invoice.paid_at.is_some());
    }

    #[test]
    fn test_line_item() {
        let item = InvoiceLineItem::new(
            "Storage L2",
            ServiceType::Storage,
            dec!(10),
            dec!(0.02),
        );

        assert_eq!(item.amount, dec!(0.20));
    }

    #[test]
    fn test_invoice_calculate() {
        let mut invoice = Invoice::new("test")
            .add_line_item(InvoiceLineItem::new("Storage", ServiceType::Storage, dec!(100), dec!(0.02)))
            .add_line_item(InvoiceLineItem::new("Compute", ServiceType::Compute, dec!(10), dec!(0.50)));

        invoice.discount = dec!(1);
        invoice.calculate();

        assert_eq!(invoice.subtotal, dec!(7)); // 2 + 5
        assert_eq!(invoice.total, dec!(6)); // 7 - 1
    }
}
