//! Billing Engine
//!
//! Invoice generation, payment processing, and credit management for Synor services.

mod credit;
mod invoice;
mod payment;

pub use credit::{Credit, CreditError, CreditManager, CreditPolicy, CreditType};
pub use invoice::{Invoice, InvoiceLineItem, InvoiceStatus};
pub use payment::{Payment, PaymentError, PaymentMethod, PaymentProcessor, PaymentStatus};

use crate::error::{EconomicsError, Result};
use crate::metering::MeteringService;
use crate::pricing::PricingEngine;
use crate::{AccountId, FeeDistribution, ServiceType, SynorDecimal};
use chrono::{DateTime, Duration, Utc};
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;

/// Billing engine configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BillingConfig {
    /// Billing cycle in days (default: 30)
    pub billing_cycle_days: u32,
    /// Grace period for late payments in days
    pub grace_period_days: u32,
    /// Minimum invoice amount (skip if below)
    pub minimum_invoice_amount: SynorDecimal,
    /// Auto-pay threshold (if prepaid balance allows)
    pub auto_pay_enabled: bool,
    /// Send invoice reminders
    pub reminders_enabled: bool,
    /// Days before due date to send reminder
    pub reminder_days_before: u32,
}

impl Default for BillingConfig {
    fn default() -> Self {
        Self {
            billing_cycle_days: 30,
            grace_period_days: 7,
            minimum_invoice_amount: Decimal::new(1, 2), // 0.01 SYNOR
            auto_pay_enabled: true,
            reminders_enabled: true,
            reminder_days_before: 3,
        }
    }
}

/// Account billing information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AccountBillingInfo {
    pub account_id: AccountId,
    pub balance: SynorDecimal,
    pub credit_balance: SynorDecimal,
    pub prepaid_balance: SynorDecimal,
    pub tier_name: String,
    pub last_payment: Option<DateTime<Utc>>,
    pub next_invoice: Option<DateTime<Utc>>,
    pub outstanding_invoices: Vec<String>,
}

/// Billing engine for managing invoices and payments
pub struct BillingEngine {
    config: BillingConfig,
    metering: Arc<MeteringService>,
    pricing: Arc<PricingEngine>,
    /// Account billing data
    accounts: RwLock<HashMap<AccountId, AccountData>>,
    /// Invoices by ID
    invoices: RwLock<HashMap<String, Invoice>>,
    /// Payments by ID
    payments: RwLock<HashMap<String, Payment>>,
    /// Credits by ID
    credits: RwLock<HashMap<String, Credit>>,
}

/// Internal account data
#[derive(Debug, Clone)]
struct AccountData {
    account_id: AccountId,
    prepaid_balance: SynorDecimal,
    credit_balance: SynorDecimal,
    tier: String,
    billing_cycle_start: DateTime<Utc>,
    created_at: DateTime<Utc>,
    last_payment: Option<DateTime<Utc>>,
    invoice_ids: Vec<String>,
}

impl BillingEngine {
    /// Create a new billing engine
    pub fn new(metering: Arc<MeteringService>, pricing: Arc<PricingEngine>) -> Self {
        Self {
            config: BillingConfig::default(),
            metering,
            pricing,
            accounts: RwLock::new(HashMap::new()),
            invoices: RwLock::new(HashMap::new()),
            payments: RwLock::new(HashMap::new()),
            credits: RwLock::new(HashMap::new()),
        }
    }

    /// Create with custom configuration
    pub fn with_config(
        config: BillingConfig,
        metering: Arc<MeteringService>,
        pricing: Arc<PricingEngine>,
    ) -> Self {
        Self {
            config,
            metering,
            pricing,
            accounts: RwLock::new(HashMap::new()),
            invoices: RwLock::new(HashMap::new()),
            payments: RwLock::new(HashMap::new()),
            credits: RwLock::new(HashMap::new()),
        }
    }

    /// Register a new account
    pub async fn register_account(&self, account_id: &str, tier: &str) -> Result<()> {
        let mut accounts = self.accounts.write().await;

        if accounts.contains_key(account_id) {
            return Err(EconomicsError::Internal(format!(
                "Account {} already registered",
                account_id
            )));
        }

        let now = Utc::now();
        accounts.insert(
            account_id.to_string(),
            AccountData {
                account_id: account_id.to_string(),
                prepaid_balance: Decimal::ZERO,
                credit_balance: Decimal::ZERO,
                tier: tier.to_string(),
                billing_cycle_start: now,
                created_at: now,
                last_payment: None,
                invoice_ids: Vec::new(),
            },
        );

        Ok(())
    }

    /// Get account billing information
    pub async fn get_account_info(&self, account_id: &str) -> Result<AccountBillingInfo> {
        let accounts = self.accounts.read().await;
        let invoices = self.invoices.read().await;

        let account = accounts
            .get(account_id)
            .ok_or_else(|| EconomicsError::AccountNotFound(account_id.to_string()))?;

        let outstanding: Vec<_> = account
            .invoice_ids
            .iter()
            .filter(|id| {
                invoices
                    .get(*id)
                    .map(|inv| !inv.is_paid())
                    .unwrap_or(false)
            })
            .cloned()
            .collect();

        let next_invoice = account.billing_cycle_start
            + Duration::days(self.config.billing_cycle_days as i64);

        Ok(AccountBillingInfo {
            account_id: account_id.to_string(),
            balance: account.prepaid_balance + account.credit_balance,
            credit_balance: account.credit_balance,
            prepaid_balance: account.prepaid_balance,
            tier_name: account.tier.clone(),
            last_payment: account.last_payment,
            next_invoice: Some(next_invoice),
            outstanding_invoices: outstanding,
        })
    }

    /// Add prepaid balance to account
    pub async fn add_prepaid(&self, account_id: &str, amount: SynorDecimal) -> Result<()> {
        let mut accounts = self.accounts.write().await;

        let account = accounts
            .get_mut(account_id)
            .ok_or_else(|| EconomicsError::AccountNotFound(account_id.to_string()))?;

        account.prepaid_balance += amount;

        tracing::info!(
            "Added {} SYNOR prepaid to account {}",
            amount,
            account_id
        );

        Ok(())
    }

    /// Add credit to account
    pub async fn add_credit(&self, account_id: &str, credit: Credit) -> Result<()> {
        let mut accounts = self.accounts.write().await;
        let mut credits = self.credits.write().await;

        let account = accounts
            .get_mut(account_id)
            .ok_or_else(|| EconomicsError::AccountNotFound(account_id.to_string()))?;

        account.credit_balance += credit.amount;
        credits.insert(credit.id.clone(), credit.clone());

        tracing::info!(
            "Added {} SYNOR credit to account {}: {}",
            credit.amount,
            account_id,
            credit.reason
        );

        Ok(())
    }

    /// Generate invoice for an account
    pub async fn generate_invoice(&self, account_id: &str) -> Result<Invoice> {
        let mut accounts = self.accounts.write().await;
        let mut invoices = self.invoices.write().await;

        let account = accounts
            .get_mut(account_id)
            .ok_or_else(|| EconomicsError::AccountNotFound(account_id.to_string()))?;

        let period_start = account.billing_cycle_start;
        let period_end = Utc::now();

        // Get usage for the period
        let usage = self
            .metering
            .get_period_usage(account_id, period_start, period_end)
            .await?;

        // Generate line items
        let mut line_items = Vec::new();
        let mut subtotal = Decimal::ZERO;

        for (service_type, cost) in usage.by_service.iter() {
            let description = match service_type {
                ServiceType::Storage => "Storage L2 - Data storage and retrieval",
                ServiceType::Hosting => "Hosting - Web hosting and bandwidth",
                ServiceType::Database => "Database L2 - Queries and storage",
                ServiceType::Compute => "Compute L2 - CPU/GPU processing",
                ServiceType::Network => "Network - Bandwidth usage",
                ServiceType::Contract => "Smart Contracts - Gas fees",
            };

            line_items.push(InvoiceLineItem {
                description: description.to_string(),
                service_type: *service_type,
                quantity: Decimal::ONE,
                unit_price: *cost,
                amount: *cost,
            });

            subtotal += *cost;
        }

        // Apply discounts from tier
        let discount = self
            .pricing
            .calculate_tier_discount(&account.tier, subtotal)?;
        let discounted_total = subtotal - discount;

        // Calculate fee distribution
        let distribution = FeeDistribution::l2_service_fees();
        let _fees = distribution.distribute(discounted_total);

        // Skip if below minimum
        if discounted_total < self.config.minimum_invoice_amount {
            return Err(EconomicsError::Internal(format!(
                "Invoice amount {} below minimum {}",
                discounted_total, self.config.minimum_invoice_amount
            )));
        }

        // Create invoice
        let due_date = Utc::now() + Duration::days(self.config.billing_cycle_days as i64);
        let invoice = Invoice::new(account_id)
            .with_period(period_start, period_end)
            .with_line_items(line_items)
            .with_subtotal(subtotal)
            .with_discount(discount)
            .with_total(discounted_total)
            .with_due_date(due_date);

        // Store invoice
        invoices.insert(invoice.id.clone(), invoice.clone());
        account.invoice_ids.push(invoice.id.clone());

        // Reset billing cycle
        account.billing_cycle_start = Utc::now();

        // Reset metering for new period
        self.metering.reset_period(account_id).await?;

        tracing::info!(
            "Generated invoice {} for account {}: {} SYNOR",
            invoice.id,
            account_id,
            discounted_total
        );

        Ok(invoice)
    }

    /// Process a payment
    pub async fn process_payment(&self, payment: Payment) -> Result<()> {
        use payment::PaymentProcessor;

        // Validate payment using PaymentProcessor
        PaymentProcessor::validate(&payment)
            .map_err(|e| EconomicsError::PaymentFailed(e.to_string()))?;

        let mut accounts = self.accounts.write().await;
        let mut invoices = self.invoices.write().await;
        let mut payments = self.payments.write().await;

        let account = accounts
            .get_mut(&payment.account_id)
            .ok_or_else(|| EconomicsError::AccountNotFound(payment.account_id.clone()))?;

        // Validate invoice if specified
        if let Some(ref invoice_id) = payment.invoice_id {
            let invoice = invoices
                .get_mut(invoice_id)
                .ok_or_else(|| EconomicsError::InvoiceNotFound(invoice_id.clone()))?;

            if invoice.is_paid() {
                return Err(EconomicsError::InvoiceAlreadyPaid(invoice_id.clone()));
            }

            if payment.amount < invoice.total {
                return Err(EconomicsError::PaymentFailed(format!(
                    "Payment amount {} less than invoice total {}",
                    payment.amount, invoice.total
                )));
            }

            // Mark invoice as paid
            invoice.mark_paid(payment.id.clone());
        }

        // Process payment based on method
        let mut payment = payment;
        match payment.method {
            PaymentMethod::PrepaidBalance => {
                PaymentProcessor::process_prepaid(&mut payment, account.prepaid_balance)
                    .await
                    .map_err(|e| EconomicsError::PaymentFailed(e.to_string()))?;
                // Deduct from prepaid balance
                account.prepaid_balance -= payment.amount;
            }
            PaymentMethod::SynorTransfer => {
                // In production, addresses would come from configuration
                let from_address = format!("synor1{}", payment.account_id);
                let to_address = "synor1treasury".to_string();
                PaymentProcessor::process_synor_transfer(&mut payment, &from_address, &to_address)
                    .await
                    .map_err(|e| EconomicsError::PaymentFailed(e.to_string()))?;
            }
            PaymentMethod::CreditBalance => {
                // Deduct from credit balance
                if account.credit_balance < payment.amount {
                    return Err(EconomicsError::PaymentFailed("Insufficient credit balance".to_string()));
                }
                account.credit_balance -= payment.amount;
                payment.mark_completed();
            }
            _ => {
                // Other payment methods - just mark as completed for now
                payment.mark_completed();
            }
        }

        // Update account
        account.last_payment = Some(Utc::now());

        // Store payment
        payments.insert(payment.id.clone(), payment.clone());

        tracing::info!(
            "Processed payment {} for account {}: {} SYNOR via {:?}",
            payment.id,
            payment.account_id,
            payment.amount,
            payment.method
        );

        Ok(())
    }

    /// Process payment from prepaid balance
    pub async fn pay_from_prepaid(&self, account_id: &str, invoice_id: &str) -> Result<()> {
        // Get invoice total first
        let invoice_total = {
            let invoices = self.invoices.read().await;
            let invoice = invoices
                .get(invoice_id)
                .ok_or_else(|| EconomicsError::InvoiceNotFound(invoice_id.to_string()))?;
            invoice.total
        };

        // Update account balances
        {
            let mut accounts = self.accounts.write().await;
            let account = accounts
                .get_mut(account_id)
                .ok_or_else(|| EconomicsError::AccountNotFound(account_id.to_string()))?;

            let total_balance = account.prepaid_balance + account.credit_balance;

            if total_balance < invoice_total {
                return Err(EconomicsError::InsufficientBalance {
                    required: invoice_total.to_string(),
                    available: total_balance.to_string(),
                });
            }

            // Use credits first, then prepaid
            let mut remaining = invoice_total;

            if account.credit_balance > Decimal::ZERO {
                let credit_used = remaining.min(account.credit_balance);
                account.credit_balance -= credit_used;
                remaining -= credit_used;
            }

            if remaining > Decimal::ZERO {
                account.prepaid_balance -= remaining;
            }
        }

        // Create and process payment
        let payment = Payment::new(account_id, invoice_total, PaymentMethod::PrepaidBalance)
            .with_invoice(invoice_id);

        self.process_payment(payment).await
    }

    /// Get invoice by ID
    pub async fn get_invoice(&self, invoice_id: &str) -> Result<Invoice> {
        let invoices = self.invoices.read().await;
        invoices
            .get(invoice_id)
            .cloned()
            .ok_or_else(|| EconomicsError::InvoiceNotFound(invoice_id.to_string()))
    }

    /// Get all invoices for an account
    pub async fn get_account_invoices(&self, account_id: &str) -> Result<Vec<Invoice>> {
        let accounts = self.accounts.read().await;
        let invoices = self.invoices.read().await;

        let account = accounts
            .get(account_id)
            .ok_or_else(|| EconomicsError::AccountNotFound(account_id.to_string()))?;

        let result: Vec<_> = account
            .invoice_ids
            .iter()
            .filter_map(|id| invoices.get(id).cloned())
            .collect();

        Ok(result)
    }

    /// Get unpaid invoices for an account
    pub async fn get_unpaid_invoices(&self, account_id: &str) -> Result<Vec<Invoice>> {
        let all_invoices = self.get_account_invoices(account_id).await?;
        Ok(all_invoices.into_iter().filter(|inv| !inv.is_paid()).collect())
    }

    /// Get detailed account information including creation date
    pub async fn get_account_details(&self, account_id: &str) -> Result<AccountInfo> {
        let accounts = self.accounts.read().await;
        let account = accounts
            .get(account_id)
            .ok_or_else(|| EconomicsError::AccountNotFound(account_id.to_string()))?;

        Ok(AccountInfo {
            account_id: account.account_id.clone(),
            prepaid_balance: account.prepaid_balance,
            credit_balance: account.credit_balance,
            tier: account.tier.clone(),
            billing_cycle_start: account.billing_cycle_start,
            created_at: account.created_at,
            last_payment: account.last_payment,
            invoice_count: account.invoice_ids.len(),
        })
    }

    /// Generate all pending invoices (for batch processing)
    pub async fn generate_all_pending_invoices(&self) -> Result<Vec<Invoice>> {
        let account_ids: Vec<_> = {
            let accounts = self.accounts.read().await;
            accounts.keys().cloned().collect()
        };

        let mut generated = Vec::new();
        let now = Utc::now();

        for account_id in account_ids {
            // Check if billing cycle has ended
            let should_invoice = {
                let accounts = self.accounts.read().await;
                if let Some(account) = accounts.get(&account_id) {
                    let cycle_end = account.billing_cycle_start
                        + Duration::days(self.config.billing_cycle_days as i64);
                    now >= cycle_end
                } else {
                    false
                }
            };

            if should_invoice {
                match self.generate_invoice(&account_id).await {
                    Ok(invoice) => generated.push(invoice),
                    Err(e) => {
                        tracing::warn!("Failed to generate invoice for {}: {}", account_id, e);
                    }
                }
            }
        }

        Ok(generated)
    }

    /// Get billing statistics
    pub async fn stats(&self) -> BillingStats {
        let accounts = self.accounts.read().await;
        let invoices = self.invoices.read().await;
        let payments = self.payments.read().await;

        let total_prepaid: Decimal = accounts.values().map(|a| a.prepaid_balance).sum();
        let total_credits: Decimal = accounts.values().map(|a| a.credit_balance).sum();

        let total_invoiced: Decimal = invoices.values().map(|i| i.total).sum();
        let total_paid: Decimal = payments.values().map(|p| p.amount).sum();

        let unpaid_count = invoices.values().filter(|i| !i.is_paid()).count();

        BillingStats {
            total_accounts: accounts.len(),
            total_invoices: invoices.len(),
            unpaid_invoices: unpaid_count,
            total_prepaid_balance: total_prepaid,
            total_credit_balance: total_credits,
            total_invoiced,
            total_paid,
            outstanding_amount: total_invoiced - total_paid,
        }
    }
}

/// Billing statistics
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BillingStats {
    pub total_accounts: usize,
    pub total_invoices: usize,
    pub unpaid_invoices: usize,
    pub total_prepaid_balance: SynorDecimal,
    pub total_credit_balance: SynorDecimal,
    pub total_invoiced: SynorDecimal,
    pub total_paid: SynorDecimal,
    pub outstanding_amount: SynorDecimal,
}

/// Account information for external use
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AccountInfo {
    /// Account identifier
    pub account_id: AccountId,
    /// Prepaid balance in SYNOR
    pub prepaid_balance: SynorDecimal,
    /// Credit balance in SYNOR
    pub credit_balance: SynorDecimal,
    /// Pricing tier name
    pub tier: String,
    /// Current billing cycle start
    pub billing_cycle_start: DateTime<Utc>,
    /// Account creation date
    pub created_at: DateTime<Utc>,
    /// Last payment date
    pub last_payment: Option<DateTime<Utc>>,
    /// Number of invoices
    pub invoice_count: usize,
}

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

    async fn setup_engine() -> BillingEngine {
        let pricing = Arc::new(PricingEngine::new());
        let metering = Arc::new(MeteringService::new(pricing.clone()));
        BillingEngine::new(metering, pricing)
    }

    #[tokio::test]
    async fn test_register_account() {
        let engine = setup_engine().await;
        engine.register_account("test_account", "standard").await.unwrap();

        let info = engine.get_account_details("test_account").await.unwrap();
        assert_eq!(info.account_id, "test_account");
        assert_eq!(info.tier, "standard");
    }

    #[tokio::test]
    async fn test_add_prepaid() {
        let engine = setup_engine().await;
        engine.register_account("prepaid_test", "standard").await.unwrap();
        engine.add_prepaid("prepaid_test", dec!(100)).await.unwrap();

        let info = engine.get_account_info("prepaid_test").await.unwrap();
        assert_eq!(info.prepaid_balance, dec!(100));
    }

    #[tokio::test]
    async fn test_add_credit() {
        let engine = setup_engine().await;
        engine.register_account("credit_test", "standard").await.unwrap();

        let credit = Credit::new("credit_test", dec!(50), "Welcome bonus");
        engine.add_credit("credit_test", credit).await.unwrap();

        let info = engine.get_account_info("credit_test").await.unwrap();
        assert_eq!(info.credit_balance, dec!(50));
    }

    #[tokio::test]
    async fn test_account_not_found() {
        let engine = setup_engine().await;
        let result = engine.get_account_info("nonexistent").await;
        assert!(matches!(result, Err(EconomicsError::AccountNotFound(_))));
    }
}
