//! Cost Estimator
//!
//! Estimate costs for projected resource usage.

use crate::error::Result;
use crate::pricing::PricingEngine;
use crate::{ResourceUnit, ServiceType, SynorDecimal};
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;

/// Usage projection for cost estimation
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct UsageProjection {
    /// Projected storage in GB
    pub storage_gb: Decimal,
    /// Projected storage retrieval in GB
    pub storage_retrieval_gb: Decimal,
    /// Projected hosting bandwidth in GB
    pub hosting_bandwidth_gb: Decimal,
    /// Projected custom domains
    pub custom_domains: u32,
    /// Projected database storage in GB
    pub database_storage_gb: Decimal,
    /// Projected database queries (in millions)
    pub database_queries_millions: Decimal,
    /// Projected vector searches (in millions)
    pub vector_searches_millions: Decimal,
    /// Projected CPU core-hours
    pub cpu_core_hours: Decimal,
    /// Projected GPU hours
    pub gpu_hours: Decimal,
    /// Projected memory GB-hours
    pub memory_gb_hours: Decimal,
    /// Projected serverless invocations (in millions)
    pub invocations_millions: Decimal,
    /// Projected network bandwidth in GB
    pub network_bandwidth_gb: Decimal,
    /// Duration in months (for monthly costs)
    pub duration_months: u32,
    /// Pricing tier to apply
    pub tier: Option<String>,
}

impl UsageProjection {
    /// Create a new empty projection
    pub fn new() -> Self {
        Self {
            duration_months: 1,
            ..Default::default()
        }
    }

    /// Set storage usage
    pub fn with_storage(mut self, storage_gb: Decimal, retrieval_gb: Decimal) -> Self {
        self.storage_gb = storage_gb;
        self.storage_retrieval_gb = retrieval_gb;
        self
    }

    /// Set hosting usage
    pub fn with_hosting(mut self, bandwidth_gb: Decimal, domains: u32) -> Self {
        self.hosting_bandwidth_gb = bandwidth_gb;
        self.custom_domains = domains;
        self
    }

    /// Set database usage
    pub fn with_database(
        mut self,
        storage_gb: Decimal,
        queries_millions: Decimal,
        vector_searches_millions: Decimal,
    ) -> Self {
        self.database_storage_gb = storage_gb;
        self.database_queries_millions = queries_millions;
        self.vector_searches_millions = vector_searches_millions;
        self
    }

    /// Set compute usage
    pub fn with_compute(
        mut self,
        cpu_hours: Decimal,
        gpu_hours: Decimal,
        memory_gb_hours: Decimal,
        invocations_millions: Decimal,
    ) -> Self {
        self.cpu_core_hours = cpu_hours;
        self.gpu_hours = gpu_hours;
        self.memory_gb_hours = memory_gb_hours;
        self.invocations_millions = invocations_millions;
        self
    }

    /// Set network usage
    pub fn with_network(mut self, bandwidth_gb: Decimal) -> Self {
        self.network_bandwidth_gb = bandwidth_gb;
        self
    }

    /// Set duration
    pub fn for_months(mut self, months: u32) -> Self {
        self.duration_months = months.max(1);
        self
    }

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

/// Detailed cost estimate
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CostEstimate {
    /// Total estimated cost
    pub total: SynorDecimal,
    /// Monthly cost (if duration > 1)
    pub monthly: SynorDecimal,
    /// Breakdown by service
    pub by_service: HashMap<ServiceType, ServiceCostBreakdown>,
    /// Free tier savings
    pub free_tier_savings: SynorDecimal,
    /// Tier discount applied
    pub tier_discount: SynorDecimal,
    /// Tier name used
    pub tier: String,
    /// Duration in months
    pub duration_months: u32,
    /// USD equivalent (if oracle available)
    pub usd_equivalent: Option<Decimal>,
}

/// Cost breakdown for a single service
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServiceCostBreakdown {
    /// Service type
    pub service: ServiceType,
    /// Line items
    pub items: Vec<CostLineItem>,
    /// Subtotal before discounts
    pub subtotal: SynorDecimal,
    /// Free tier credit applied
    pub free_tier_credit: SynorDecimal,
    /// Net cost
    pub net_cost: SynorDecimal,
}

/// Individual cost line item
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CostLineItem {
    /// Resource description
    pub description: String,
    /// Resource unit
    pub unit: ResourceUnit,
    /// Quantity
    pub quantity: Decimal,
    /// Unit price
    pub unit_price: SynorDecimal,
    /// Line total
    pub total: SynorDecimal,
}

/// Cost estimator service
pub struct CostEstimator {
    pricing: Arc<PricingEngine>,
}

impl CostEstimator {
    /// Create a new cost estimator
    pub fn new(pricing: Arc<PricingEngine>) -> Self {
        Self { pricing }
    }

    /// Estimate cost for a usage projection
    pub async fn estimate(&self, projection: UsageProjection) -> Result<CostEstimate> {
        let tier_name = projection.tier.clone().unwrap_or_else(|| "free".to_string());
        let months = projection.duration_months.max(1);

        let mut by_service = HashMap::new();
        let mut total_free_savings = Decimal::ZERO;

        // Storage costs
        let storage_breakdown = self.estimate_storage(&projection)?;
        total_free_savings += storage_breakdown.free_tier_credit;
        by_service.insert(ServiceType::Storage, storage_breakdown);

        // Hosting costs
        let hosting_breakdown = self.estimate_hosting(&projection)?;
        total_free_savings += hosting_breakdown.free_tier_credit;
        by_service.insert(ServiceType::Hosting, hosting_breakdown);

        // Database costs
        let database_breakdown = self.estimate_database(&projection)?;
        total_free_savings += database_breakdown.free_tier_credit;
        by_service.insert(ServiceType::Database, database_breakdown);

        // Compute costs
        let compute_breakdown = self.estimate_compute(&projection)?;
        total_free_savings += compute_breakdown.free_tier_credit;
        by_service.insert(ServiceType::Compute, compute_breakdown);

        // Network costs
        let network_breakdown = self.estimate_network(&projection)?;
        total_free_savings += network_breakdown.free_tier_credit;
        by_service.insert(ServiceType::Network, network_breakdown);

        // Calculate subtotal
        let subtotal: Decimal = by_service.values().map(|b| b.net_cost).sum();

        // Apply tier discount
        let tier_discount = self.pricing.calculate_tier_discount(&tier_name, subtotal)?;
        let total = subtotal - tier_discount;

        // Monthly cost
        let monthly = total / Decimal::from(months);

        Ok(CostEstimate {
            total,
            monthly,
            by_service,
            free_tier_savings: total_free_savings,
            tier_discount,
            tier: tier_name,
            duration_months: months,
            usd_equivalent: None, // Would come from oracle
        })
    }

    /// Estimate storage costs
    fn estimate_storage(&self, projection: &UsageProjection) -> Result<ServiceCostBreakdown> {
        let mut items = Vec::new();
        let mut subtotal = Decimal::ZERO;

        // Storage
        if projection.storage_gb > Decimal::ZERO {
            let price = self
                .pricing
                .get_base_price(ServiceType::Storage, ResourceUnit::GbMonth)
                .unwrap_or(Decimal::new(2, 2));

            let months = Decimal::from(projection.duration_months.max(1));
            let total = projection.storage_gb * price * months;

            items.push(CostLineItem {
                description: "Data Storage".to_string(),
                unit: ResourceUnit::GbMonth,
                quantity: projection.storage_gb * months,
                unit_price: price,
                total,
            });
            subtotal += total;
        }

        // Retrieval
        if projection.storage_retrieval_gb > Decimal::ZERO {
            let price = self
                .pricing
                .get_base_price(ServiceType::Storage, ResourceUnit::BandwidthGb)
                .unwrap_or(Decimal::new(1, 2));

            let total = projection.storage_retrieval_gb * price;

            items.push(CostLineItem {
                description: "Data Retrieval".to_string(),
                unit: ResourceUnit::BandwidthGb,
                quantity: projection.storage_retrieval_gb,
                unit_price: price,
                total,
            });
            subtotal += total;
        }

        // Free tier
        let free_storage = self
            .pricing
            .get_free_allocation(ServiceType::Storage, ResourceUnit::GbMonth);
        let free_credit = (free_storage * Decimal::new(2, 2)).min(subtotal);

        Ok(ServiceCostBreakdown {
            service: ServiceType::Storage,
            items,
            subtotal,
            free_tier_credit: free_credit,
            net_cost: subtotal - free_credit,
        })
    }

    /// Estimate hosting costs
    fn estimate_hosting(&self, projection: &UsageProjection) -> Result<ServiceCostBreakdown> {
        let mut items = Vec::new();
        let mut subtotal = Decimal::ZERO;

        // Bandwidth
        if projection.hosting_bandwidth_gb > Decimal::ZERO {
            let price = self
                .pricing
                .get_base_price(ServiceType::Hosting, ResourceUnit::BandwidthGb)
                .unwrap_or(Decimal::new(5, 2));

            let total = projection.hosting_bandwidth_gb * price;

            items.push(CostLineItem {
                description: "Hosting Bandwidth".to_string(),
                unit: ResourceUnit::BandwidthGb,
                quantity: projection.hosting_bandwidth_gb,
                unit_price: price,
                total,
            });
            subtotal += total;
        }

        // Custom domains
        if projection.custom_domains > 0 {
            let price = self
                .pricing
                .get_base_price(ServiceType::Hosting, ResourceUnit::Domains)
                .unwrap_or(Decimal::new(50, 2));

            let months = Decimal::from(projection.duration_months.max(1));
            let total = Decimal::from(projection.custom_domains) * price * months;

            items.push(CostLineItem {
                description: "Custom Domains".to_string(),
                unit: ResourceUnit::Domains,
                quantity: Decimal::from(projection.custom_domains),
                unit_price: price,
                total,
            });
            subtotal += total;
        }

        // Free tier
        let free_bandwidth = self
            .pricing
            .get_free_allocation(ServiceType::Hosting, ResourceUnit::BandwidthGb);
        let free_credit = (free_bandwidth * Decimal::new(5, 2)).min(subtotal);

        Ok(ServiceCostBreakdown {
            service: ServiceType::Hosting,
            items,
            subtotal,
            free_tier_credit: free_credit,
            net_cost: subtotal - free_credit,
        })
    }

    /// Estimate database costs
    fn estimate_database(&self, projection: &UsageProjection) -> Result<ServiceCostBreakdown> {
        let mut items = Vec::new();
        let mut subtotal = Decimal::ZERO;

        // Storage
        if projection.database_storage_gb > Decimal::ZERO {
            let price = self
                .pricing
                .get_base_price(ServiceType::Database, ResourceUnit::GbMonth)
                .unwrap_or(Decimal::new(10, 2));

            let months = Decimal::from(projection.duration_months.max(1));
            let total = projection.database_storage_gb * price * months;

            items.push(CostLineItem {
                description: "Database Storage".to_string(),
                unit: ResourceUnit::GbMonth,
                quantity: projection.database_storage_gb * months,
                unit_price: price,
                total,
            });
            subtotal += total;
        }

        // Queries
        if projection.database_queries_millions > Decimal::ZERO {
            let price_per_query = self
                .pricing
                .get_base_price(ServiceType::Database, ResourceUnit::Queries)
                .unwrap_or(Decimal::new(1, 8));

            let queries = projection.database_queries_millions * Decimal::from(1_000_000);
            let total = queries * price_per_query;

            items.push(CostLineItem {
                description: "Database Queries".to_string(),
                unit: ResourceUnit::Queries,
                quantity: queries,
                unit_price: price_per_query,
                total,
            });
            subtotal += total;
        }

        // Vector searches
        if projection.vector_searches_millions > Decimal::ZERO {
            let price_per_search = self
                .pricing
                .get_base_price(ServiceType::Database, ResourceUnit::VectorSearches)
                .unwrap_or(Decimal::new(5, 8));

            let searches = projection.vector_searches_millions * Decimal::from(1_000_000);
            let total = searches * price_per_search;

            items.push(CostLineItem {
                description: "Vector Searches".to_string(),
                unit: ResourceUnit::VectorSearches,
                quantity: searches,
                unit_price: price_per_search,
                total,
            });
            subtotal += total;
        }

        // Free tier
        let free_queries = self
            .pricing
            .get_free_allocation(ServiceType::Database, ResourceUnit::Queries);
        let free_credit = (free_queries * Decimal::new(1, 8)).min(subtotal);

        Ok(ServiceCostBreakdown {
            service: ServiceType::Database,
            items,
            subtotal,
            free_tier_credit: free_credit,
            net_cost: subtotal - free_credit,
        })
    }

    /// Estimate compute costs
    fn estimate_compute(&self, projection: &UsageProjection) -> Result<ServiceCostBreakdown> {
        let mut items = Vec::new();
        let mut subtotal = Decimal::ZERO;

        // CPU
        if projection.cpu_core_hours > Decimal::ZERO {
            let price = self
                .pricing
                .get_base_price(ServiceType::Compute, ResourceUnit::CpuCoreHours)
                .unwrap_or(Decimal::new(2, 2));

            let total = projection.cpu_core_hours * price;

            items.push(CostLineItem {
                description: "CPU Core-Hours".to_string(),
                unit: ResourceUnit::CpuCoreHours,
                quantity: projection.cpu_core_hours,
                unit_price: price,
                total,
            });
            subtotal += total;
        }

        // GPU
        if projection.gpu_hours > Decimal::ZERO {
            let price = self
                .pricing
                .get_base_price(ServiceType::Compute, ResourceUnit::GpuHours)
                .unwrap_or(Decimal::new(50, 2));

            let total = projection.gpu_hours * price;

            items.push(CostLineItem {
                description: "GPU Hours".to_string(),
                unit: ResourceUnit::GpuHours,
                quantity: projection.gpu_hours,
                unit_price: price,
                total,
            });
            subtotal += total;
        }

        // Memory
        if projection.memory_gb_hours > Decimal::ZERO {
            let price = self
                .pricing
                .get_base_price(ServiceType::Compute, ResourceUnit::MemoryGbHours)
                .unwrap_or(Decimal::new(5, 3));

            let total = projection.memory_gb_hours * price;

            items.push(CostLineItem {
                description: "Memory GB-Hours".to_string(),
                unit: ResourceUnit::MemoryGbHours,
                quantity: projection.memory_gb_hours,
                unit_price: price,
                total,
            });
            subtotal += total;
        }

        // Invocations
        if projection.invocations_millions > Decimal::ZERO {
            let price_per_inv = self
                .pricing
                .get_base_price(ServiceType::Compute, ResourceUnit::Invocations)
                .unwrap_or(Decimal::new(2, 7));

            let invocations = projection.invocations_millions * Decimal::from(1_000_000);
            let total = invocations * price_per_inv;

            items.push(CostLineItem {
                description: "Serverless Invocations".to_string(),
                unit: ResourceUnit::Invocations,
                quantity: invocations,
                unit_price: price_per_inv,
                total,
            });
            subtotal += total;
        }

        // Free tier
        let free_cpu = self
            .pricing
            .get_free_allocation(ServiceType::Compute, ResourceUnit::CpuCoreHours);
        let free_credit = (free_cpu * Decimal::new(2, 2)).min(subtotal);

        Ok(ServiceCostBreakdown {
            service: ServiceType::Compute,
            items,
            subtotal,
            free_tier_credit: free_credit,
            net_cost: subtotal - free_credit,
        })
    }

    /// Estimate network costs
    fn estimate_network(&self, projection: &UsageProjection) -> Result<ServiceCostBreakdown> {
        let mut items = Vec::new();
        let mut subtotal = Decimal::ZERO;

        if projection.network_bandwidth_gb > Decimal::ZERO {
            let price = self
                .pricing
                .get_base_price(ServiceType::Network, ResourceUnit::BandwidthGb)
                .unwrap_or(Decimal::new(1, 2));

            let total = projection.network_bandwidth_gb * price;

            items.push(CostLineItem {
                description: "Network Bandwidth".to_string(),
                unit: ResourceUnit::BandwidthGb,
                quantity: projection.network_bandwidth_gb,
                unit_price: price,
                total,
            });
            subtotal += total;
        }

        // Free tier
        let free_bandwidth = self
            .pricing
            .get_free_allocation(ServiceType::Network, ResourceUnit::BandwidthGb);
        let free_credit = (free_bandwidth * Decimal::new(1, 2)).min(subtotal);

        Ok(ServiceCostBreakdown {
            service: ServiceType::Network,
            items,
            subtotal,
            free_tier_credit: free_credit,
            net_cost: subtotal - free_credit,
        })
    }

    /// Quick estimate for a single service
    pub fn quick_estimate(
        &self,
        service: ServiceType,
        resource: ResourceUnit,
        amount: Decimal,
    ) -> Result<SynorDecimal> {
        self.pricing.calculate_cost(service, resource, amount)
    }
}

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

    fn setup_estimator() -> CostEstimator {
        let pricing = Arc::new(PricingEngine::new());
        CostEstimator::new(pricing)
    }

    #[tokio::test]
    async fn test_storage_estimate() {
        let estimator = setup_estimator();

        let projection = UsageProjection::new()
            .with_storage(dec!(100), dec!(50)) // 100 GB storage, 50 GB retrieval
            .for_months(1);

        let estimate = estimator.estimate(projection).await.unwrap();

        // Storage: 100 * 0.02 = 2.00
        // Retrieval: 50 * 0.01 = 0.50
        // Total before free tier: 2.50
        assert!(estimate.total > Decimal::ZERO);
        assert!(estimate.by_service.contains_key(&ServiceType::Storage));
    }

    #[tokio::test]
    async fn test_compute_estimate() {
        let estimator = setup_estimator();

        let projection = UsageProjection::new()
            .with_compute(dec!(100), dec!(10), dec!(200), dec!(5)) // 100 CPU hours, 10 GPU hours, 200 mem hours, 5M invocations
            .for_months(1);

        let estimate = estimator.estimate(projection).await.unwrap();

        // CPU: 100 * 0.02 = 2.00
        // GPU: 10 * 0.50 = 5.00
        // Memory: 200 * 0.005 = 1.00
        // Invocations: 5M * 0.0000002 = 1.00
        // Total: ~9.00 before free tier
        assert!(estimate.by_service.contains_key(&ServiceType::Compute));

        let compute = &estimate.by_service[&ServiceType::Compute];
        assert_eq!(compute.items.len(), 4);
    }

    #[tokio::test]
    async fn test_tier_discount() {
        let estimator = setup_estimator();

        let projection = UsageProjection::new()
            .with_storage(dec!(1000), dec!(500))
            .with_tier("premium"); // 20% discount

        let estimate = estimator.estimate(projection).await.unwrap();

        assert_eq!(estimate.tier, "premium");
        assert!(estimate.tier_discount > Decimal::ZERO);
    }

    #[test]
    fn test_quick_estimate() {
        let estimator = setup_estimator();

        let cost = estimator
            .quick_estimate(ServiceType::Compute, ResourceUnit::GpuHours, dec!(10))
            .unwrap();

        assert_eq!(cost, dec!(5.00)); // 10 * 0.50
    }
}
