//! Synor Economics SDK for Rust.
//! Pricing, billing, staking, and discount management.

use reqwest::{Client, StatusCode};
use serde::{Deserialize, Serialize};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Duration;
use thiserror::Error;

const DEFAULT_ENDPOINT: &str = "https://economics.synor.io/v1";
const DEFAULT_TIMEOUT: u64 = 30;
const DEFAULT_RETRIES: u32 = 3;

/// Service type for pricing.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum ServiceType {
    Compute,
    Storage,
    Database,
    Hosting,
    Bridge,
    Rpc,
}

/// Billing period.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum BillingPeriod {
    Hourly,
    Daily,
    Weekly,
    Monthly,
}

/// Stake status.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum StakeStatus {
    Active,
    Unstaking,
    Withdrawn,
    Slashed,
}

/// Discount type.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum DiscountType {
    Percentage,
    Fixed,
    Volume,
    Referral,
}

/// Invoice status.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum InvoiceStatus {
    Pending,
    Paid,
    Overdue,
    Cancelled,
}

/// Economics SDK configuration.
#[derive(Debug, Clone)]
pub struct EconomicsConfig {
    pub api_key: String,
    pub endpoint: String,
    pub timeout: Duration,
    pub retries: u32,
    pub debug: bool,
}

impl EconomicsConfig {
    pub fn new(api_key: impl Into<String>) -> Self {
        Self {
            api_key: api_key.into(),
            endpoint: DEFAULT_ENDPOINT.to_string(),
            timeout: Duration::from_secs(DEFAULT_TIMEOUT),
            retries: DEFAULT_RETRIES,
            debug: false,
        }
    }

    pub fn with_endpoint(mut self, endpoint: impl Into<String>) -> Self {
        self.endpoint = endpoint.into();
        self
    }

    pub fn with_timeout(mut self, timeout: Duration) -> Self {
        self.timeout = timeout;
        self
    }

    pub fn with_retries(mut self, retries: u32) -> Self {
        self.retries = retries;
        self
    }

    pub fn with_debug(mut self, debug: bool) -> Self {
        self.debug = debug;
        self
    }
}

/// Usage metrics for pricing.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct UsageMetrics {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub compute_hours: Option<f64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub storage_bytes: Option<i64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub database_ops: Option<i64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub hosting_requests: Option<i64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub bridge_transfers: Option<i64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub rpc_calls: Option<i64>,
}

/// Service usage in a plan.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServiceUsage {
    pub service: ServiceType,
    pub metrics: UsageMetrics,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tier: Option<String>,
}

/// Usage plan for cost estimation.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UsagePlan {
    pub services: Vec<ServiceUsage>,
    pub period: BillingPeriod,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub start_date: Option<i64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub end_date: Option<i64>,
}

/// Applied discount.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AppliedDiscount {
    pub code: String,
    #[serde(rename = "type")]
    pub discount_type: DiscountType,
    pub value: String,
    pub savings: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<String>,
}

/// Tax amount.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TaxAmount {
    pub name: String,
    pub rate: f64,
    pub amount: String,
}

/// Price breakdown.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Price {
    pub service: ServiceType,
    pub base_price: String,
    pub quantity: String,
    pub unit: String,
    pub subtotal: String,
    pub discounts: Vec<AppliedDiscount>,
    pub total: String,
    pub currency: String,
}

/// Cost estimate for a usage plan.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CostEstimate {
    pub services: Vec<Price>,
    pub subtotal: String,
    pub discounts: Vec<AppliedDiscount>,
    pub taxes: Vec<TaxAmount>,
    pub total: String,
    pub currency: String,
    pub valid_until: i64,
}

/// Usage detail.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UsageDetail {
    pub timestamp: i64,
    pub operation: String,
    pub quantity: f64,
    pub unit: String,
    pub cost: String,
}

/// Service usage record.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServiceUsageRecord {
    pub service: ServiceType,
    pub metrics: UsageMetrics,
    pub cost: String,
    pub details: Vec<UsageDetail>,
}

/// Usage record.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Usage {
    pub period: BillingPeriod,
    pub start_date: i64,
    pub end_date: i64,
    pub services: Vec<ServiceUsageRecord>,
    pub total_cost: String,
    pub currency: String,
}

/// Invoice item.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InvoiceItem {
    pub service: ServiceType,
    pub description: String,
    pub quantity: String,
    pub unit_price: String,
    pub total: String,
}

/// Invoice.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Invoice {
    pub id: String,
    pub status: InvoiceStatus,
    pub period: BillingPeriod,
    pub start_date: i64,
    pub end_date: i64,
    pub items: Vec<InvoiceItem>,
    pub subtotal: String,
    pub discounts: Vec<AppliedDiscount>,
    pub taxes: Vec<TaxAmount>,
    pub total: String,
    pub currency: String,
    pub due_date: i64,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub paid_at: Option<i64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub payment_method: Option<String>,
}

/// Account balance.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AccountBalance {
    pub available: String,
    pub pending: String,
    pub reserved: String,
    pub total: String,
    pub currency: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub credit_limit: Option<String>,
    pub last_updated: i64,
}

/// Stake receipt.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StakeReceipt {
    pub id: String,
    pub amount: String,
    pub lock_duration: i32,
    pub start_date: i64,
    pub end_date: i64,
    pub apy: String,
    pub estimated_rewards: String,
    pub tx_hash: String,
}

/// Unstake receipt.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UnstakeReceipt {
    pub id: String,
    pub stake_id: String,
    pub amount: String,
    pub rewards: String,
    pub total: String,
    pub unbonding_period: i32,
    pub available_at: i64,
    pub tx_hash: String,
}

/// Stake info.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StakeInfo {
    pub id: String,
    pub amount: String,
    pub status: StakeStatus,
    pub lock_duration: i32,
    pub start_date: i64,
    pub end_date: i64,
    pub apy: String,
    pub earned_rewards: String,
    pub pending_rewards: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub validator: Option<String>,
}

/// Stake reward detail.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StakeRewardDetail {
    pub stake_id: String,
    pub earned: String,
    pub pending: String,
    pub apy: String,
}

/// Staking rewards.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StakingRewards {
    pub total_earned: String,
    pub pending: String,
    pub claimed: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub last_claim_date: Option<i64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub next_claim_available: Option<i64>,
    pub stakes: Vec<StakeRewardDetail>,
}

/// Discount.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Discount {
    pub code: String,
    #[serde(rename = "type")]
    pub discount_type: DiscountType,
    pub value: String,
    pub description: String,
    pub valid_from: i64,
    pub valid_until: i64,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub min_purchase: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub max_discount: Option<String>,
    pub applicable_services: Vec<ServiceType>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub usage_limit: Option<i32>,
    pub used_count: i32,
}

/// Stake options.
#[derive(Debug, Clone, Default, Serialize)]
pub struct StakeOptions {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub validator: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub auto_compound: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub lock_duration: Option<i32>,
}

/// Staking APY response.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StakingApy {
    pub apy: String,
    pub min_lock_duration: i32,
    pub max_lock_duration: i32,
}

/// Economics SDK error.
#[derive(Error, Debug)]
pub enum EconomicsError {
    #[error("HTTP error: {message}")]
    Http {
        message: String,
        code: Option<String>,
        status_code: u16,
    },
    #[error("Client closed")]
    ClientClosed,
    #[error("Request error: {0}")]
    Request(#[from] reqwest::Error),
    #[error("JSON error: {0}")]
    Json(#[from] serde_json::Error),
}

/// Synor Economics SDK client.
pub struct SynorEconomics {
    config: EconomicsConfig,
    client: Client,
    closed: Arc<AtomicBool>,
}

impl SynorEconomics {
    /// Create a new Economics client.
    pub fn new(config: EconomicsConfig) -> Result<Self, EconomicsError> {
        let client = Client::builder()
            .timeout(config.timeout)
            .build()?;

        Ok(Self {
            config,
            client,
            closed: Arc::new(AtomicBool::new(false)),
        })
    }

    // ==================== Pricing Operations ====================

    /// Get price for a service based on usage.
    pub async fn get_price(
        &self,
        service: ServiceType,
        usage: UsageMetrics,
    ) -> Result<Price, EconomicsError> {
        #[derive(Serialize)]
        struct Request {
            service: ServiceType,
            usage: UsageMetrics,
        }
        #[derive(Deserialize)]
        struct Response {
            price: Price,
        }
        let resp: Response = self
            .post("/pricing/calculate", &Request { service, usage })
            .await?;
        Ok(resp.price)
    }

    /// Estimate cost for a usage plan.
    pub async fn estimate_cost(&self, plan: UsagePlan) -> Result<CostEstimate, EconomicsError> {
        self.post("/pricing/estimate", &plan).await
    }

    /// Get pricing tiers for a service.
    pub async fn get_pricing_tiers(
        &self,
        service: ServiceType,
    ) -> Result<Vec<Price>, EconomicsError> {
        #[derive(Deserialize)]
        struct Response {
            tiers: Vec<Price>,
        }
        let resp: Response = self
            .get(&format!("/pricing/{:?}/tiers", service).to_lowercase())
            .await?;
        Ok(resp.tiers)
    }

    // ==================== Usage & Billing Operations ====================

    /// Get usage for a billing period.
    pub async fn get_usage(
        &self,
        period: Option<BillingPeriod>,
    ) -> Result<Usage, EconomicsError> {
        let path = match period {
            Some(p) => format!("/usage?period={:?}", p).to_lowercase(),
            None => "/usage".to_string(),
        };
        self.get(&path).await
    }

    /// Get usage history.
    pub async fn get_usage_history(
        &self,
        limit: Option<i32>,
        offset: Option<i32>,
    ) -> Result<Vec<Usage>, EconomicsError> {
        let mut params = Vec::new();
        if let Some(l) = limit {
            params.push(format!("limit={}", l));
        }
        if let Some(o) = offset {
            params.push(format!("offset={}", o));
        }
        let path = if params.is_empty() {
            "/usage/history".to_string()
        } else {
            format!("/usage/history?{}", params.join("&"))
        };
        #[derive(Deserialize)]
        struct Response {
            usage: Vec<Usage>,
        }
        let resp: Response = self.get(&path).await?;
        Ok(resp.usage)
    }

    /// Get invoices.
    pub async fn get_invoices(&self) -> Result<Vec<Invoice>, EconomicsError> {
        #[derive(Deserialize)]
        struct Response {
            invoices: Vec<Invoice>,
        }
        let resp: Response = self.get("/invoices").await?;
        Ok(resp.invoices)
    }

    /// Get a specific invoice.
    pub async fn get_invoice(&self, invoice_id: &str) -> Result<Invoice, EconomicsError> {
        self.get(&format!("/invoices/{}", urlencoding::encode(invoice_id)))
            .await
    }

    /// Pay an invoice.
    pub async fn pay_invoice(
        &self,
        invoice_id: &str,
        payment_method: Option<&str>,
    ) -> Result<Invoice, EconomicsError> {
        #[derive(Serialize)]
        struct Request {
            #[serde(skip_serializing_if = "Option::is_none")]
            payment_method: Option<String>,
        }
        self.post(
            &format!("/invoices/{}/pay", urlencoding::encode(invoice_id)),
            &Request {
                payment_method: payment_method.map(String::from),
            },
        )
        .await
    }

    /// Get account balance.
    pub async fn get_balance(&self) -> Result<AccountBalance, EconomicsError> {
        self.get("/balance").await
    }

    /// Add funds to account.
    pub async fn add_funds(
        &self,
        amount: &str,
        payment_method: &str,
    ) -> Result<AccountBalance, EconomicsError> {
        #[derive(Serialize)]
        struct Request {
            amount: String,
            payment_method: String,
        }
        self.post(
            "/balance/deposit",
            &Request {
                amount: amount.to_string(),
                payment_method: payment_method.to_string(),
            },
        )
        .await
    }

    // ==================== Staking Operations ====================

    /// Stake tokens.
    pub async fn stake(
        &self,
        amount: &str,
        options: Option<StakeOptions>,
    ) -> Result<StakeReceipt, EconomicsError> {
        #[derive(Serialize)]
        struct Request {
            amount: String,
            #[serde(flatten)]
            options: Option<StakeOptions>,
        }
        self.post(
            "/staking/stake",
            &Request {
                amount: amount.to_string(),
                options,
            },
        )
        .await
    }

    /// Unstake tokens.
    pub async fn unstake(&self, stake_id: &str) -> Result<UnstakeReceipt, EconomicsError> {
        self.post::<(), UnstakeReceipt>(
            &format!("/staking/stakes/{}/unstake", urlencoding::encode(stake_id)),
            &(),
        )
        .await
    }

    /// Get staking rewards.
    pub async fn get_staking_rewards(&self) -> Result<StakingRewards, EconomicsError> {
        self.get("/staking/rewards").await
    }

    /// Claim staking rewards.
    pub async fn claim_rewards(
        &self,
        stake_id: Option<&str>,
    ) -> Result<StakingRewards, EconomicsError> {
        let path = match stake_id {
            Some(id) => format!("/staking/stakes/{}/claim", urlencoding::encode(id)),
            None => "/staking/rewards/claim".to_string(),
        };
        self.post::<(), StakingRewards>(&path, &()).await
    }

    /// List active stakes.
    pub async fn list_stakes(&self) -> Result<Vec<StakeInfo>, EconomicsError> {
        #[derive(Deserialize)]
        struct Response {
            stakes: Vec<StakeInfo>,
        }
        let resp: Response = self.get("/staking/stakes").await?;
        Ok(resp.stakes)
    }

    /// Get stake details.
    pub async fn get_stake(&self, stake_id: &str) -> Result<StakeInfo, EconomicsError> {
        self.get(&format!(
            "/staking/stakes/{}",
            urlencoding::encode(stake_id)
        ))
        .await
    }

    /// Get current APY for staking.
    pub async fn get_staking_apy(&self) -> Result<StakingApy, EconomicsError> {
        self.get("/staking/apy").await
    }

    // ==================== Discount Operations ====================

    /// Apply a discount code.
    pub async fn apply_discount(&self, code: &str) -> Result<Discount, EconomicsError> {
        #[derive(Serialize)]
        struct Request {
            code: String,
        }
        self.post(
            "/discounts/apply",
            &Request {
                code: code.to_string(),
            },
        )
        .await
    }

    /// Get available discounts.
    pub async fn get_available_discounts(&self) -> Result<Vec<Discount>, EconomicsError> {
        #[derive(Deserialize)]
        struct Response {
            discounts: Vec<Discount>,
        }
        let resp: Response = self.get("/discounts").await?;
        Ok(resp.discounts)
    }

    /// Get active discounts on account.
    pub async fn get_active_discounts(&self) -> Result<Vec<Discount>, EconomicsError> {
        #[derive(Deserialize)]
        struct Response {
            discounts: Vec<Discount>,
        }
        let resp: Response = self.get("/discounts/active").await?;
        Ok(resp.discounts)
    }

    /// Remove a discount.
    pub async fn remove_discount(&self, code: &str) -> Result<(), EconomicsError> {
        self.delete(&format!("/discounts/{}", urlencoding::encode(code)))
            .await
    }

    // ==================== Lifecycle ====================

    /// Health check.
    pub async fn health_check(&self) -> bool {
        #[derive(Deserialize)]
        struct Response {
            status: String,
        }
        match self.get::<Response>("/health").await {
            Ok(resp) => resp.status == "healthy",
            Err(_) => false,
        }
    }

    /// Check if client is closed.
    pub fn is_closed(&self) -> bool {
        self.closed.load(Ordering::SeqCst)
    }

    /// Close the client.
    pub fn close(&self) {
        self.closed.store(true, Ordering::SeqCst);
    }

    // ==================== Private Methods ====================

    async fn get<T: for<'de> Deserialize<'de>>(&self, path: &str) -> Result<T, EconomicsError> {
        self.execute(|| async {
            let resp = self
                .client
                .get(format!("{}{}", self.config.endpoint, path))
                .header("Authorization", format!("Bearer {}", self.config.api_key))
                .header("Content-Type", "application/json")
                .header("X-SDK-Version", "rust/0.1.0")
                .send()
                .await?;
            self.handle_response(resp).await
        })
        .await
    }

    async fn post<B: Serialize, T: for<'de> Deserialize<'de>>(
        &self,
        path: &str,
        body: &B,
    ) -> Result<T, EconomicsError> {
        self.execute(|| async {
            let resp = self
                .client
                .post(format!("{}{}", self.config.endpoint, path))
                .header("Authorization", format!("Bearer {}", self.config.api_key))
                .header("Content-Type", "application/json")
                .header("X-SDK-Version", "rust/0.1.0")
                .json(body)
                .send()
                .await?;
            self.handle_response(resp).await
        })
        .await
    }

    async fn delete(&self, path: &str) -> Result<(), EconomicsError> {
        self.execute(|| async {
            let resp = self
                .client
                .delete(format!("{}{}", self.config.endpoint, path))
                .header("Authorization", format!("Bearer {}", self.config.api_key))
                .header("X-SDK-Version", "rust/0.1.0")
                .send()
                .await?;
            if resp.status().is_success() {
                Ok(())
            } else {
                Err(self.parse_error(resp).await)
            }
        })
        .await
    }

    async fn execute<F, Fut, T>(&self, operation: F) -> Result<T, EconomicsError>
    where
        F: Fn() -> Fut,
        Fut: std::future::Future<Output = Result<T, EconomicsError>>,
    {
        if self.closed.load(Ordering::SeqCst) {
            return Err(EconomicsError::ClientClosed);
        }

        let mut last_error = None;
        for attempt in 0..self.config.retries {
            match operation().await {
                Ok(result) => return Ok(result),
                Err(e) => {
                    if self.config.debug {
                        eprintln!("Attempt {} failed: {:?}", attempt + 1, e);
                    }
                    last_error = Some(e);
                    if attempt < self.config.retries - 1 {
                        tokio::time::sleep(Duration::from_secs(1 << attempt)).await;
                    }
                }
            }
        }
        Err(last_error.unwrap())
    }

    async fn handle_response<T: for<'de> Deserialize<'de>>(
        &self,
        resp: reqwest::Response,
    ) -> Result<T, EconomicsError> {
        if resp.status().is_success() {
            Ok(resp.json().await?)
        } else {
            Err(self.parse_error(resp).await)
        }
    }

    async fn parse_error(&self, resp: reqwest::Response) -> EconomicsError {
        let status = resp.status().as_u16();
        let body = resp.text().await.unwrap_or_default();

        #[derive(Deserialize)]
        struct ErrorResponse {
            message: Option<String>,
            code: Option<String>,
        }

        let (message, code) = match serde_json::from_str::<ErrorResponse>(&body) {
            Ok(err) => (
                err.message.unwrap_or_else(|| format!("HTTP {}", status)),
                err.code,
            ),
            Err(_) => (format!("HTTP {}", status), None),
        };

        EconomicsError::Http {
            message,
            code,
            status_code: status,
        }
    }
}
