use crate::repository::api::prelude::*;
use crate::util::telegram_stars::SuccessfulPaymentRequestData;

/// Error types for purchase operations
#[derive(Debug)]
pub enum PurchaseError {
    PaymentMethodNotAvailable,
    PromoCodeNotExists,
    PromoCodeExpired,
    InsufficientReferralBalance,
    NetworkError(String),
    Other(String),
}

impl std::fmt::Display for PurchaseError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            PurchaseError::PaymentMethodNotAvailable => write!(f, "Payment method not available"),
            PurchaseError::PromoCodeNotExists => write!(f, "Promocode does not exist"),
            PurchaseError::PromoCodeExpired => write!(f, "Promocode is expired"),
            PurchaseError::InsufficientReferralBalance => write!(f, "Insufficient referral balance"),
            PurchaseError::NetworkError(msg) => write!(f, "Network error: {}", msg),
            PurchaseError::Other(msg) => write!(f, "{}", msg),
        }
    }
}

impl std::error::Error for PurchaseError {}

/// Price calculation request
#[derive(Clone, Debug, Serialize)]
pub struct PriceCalculationRequest {
    pub amounts: Vec<u32>,
}

/// Volume discount response
#[derive(Clone, Debug, Deserialize)]
pub struct VolumeDiscount(pub f64);

/// Promocode information
#[derive(Clone, Debug, Deserialize)]
pub struct PromoCodeInfo {
    pub discount: f64,
    pub expires_at: Option<String>,
    pub max_uses: Option<u32>,
    pub uses: Option<u32>,
}

/// Price information for different pools
#[derive(Clone, Debug, Deserialize)]
pub struct PoolPrices {
    pub residential: f64,
    pub residential_premium: f64,
    pub datacenter: f64,
    pub mobile: f64,
}

impl PoolPrices {
    pub fn get_price(&self, pool: &PoolType) -> f64 {
        match pool {
            PoolType::Residential => self.residential,
            PoolType::ResidentialPremium => self.residential_premium,
            PoolType::Datacenter => self.datacenter,
            PoolType::Mobile => self.mobile,
        }
    }
}

pub async fn get_pool_prices(tg_id: i64) -> Result<PoolPrices, PurchaseError> {
    let tg_id = get_test_user_id(tg_id);
    let url = format!("{host}/rpc/prices", host=&*BACKEND_API_URL);
    log::trace!("fetching {url}", url = url.red());

    let response = reqwest::Client::new()
        .get(&url)
        .header("Authorization", get_auth_token().await.map_err(|e| PurchaseError::Other(e.to_string()))?)
        .header("X-Tg-Id", tg_id.to_string())
        .send()
        .await
        .map_err(|e| PurchaseError::NetworkError(e.to_string()))?;

    if !response.status().is_success() {
        let error_text = response.text().await.unwrap_or_default();
        return Err(PurchaseError::Other(format!(
            "Failed to get prices: {}",
            prettify_json_or_text(&error_text)
        )));
    }

    let prices = response.json::<PoolPrices>().await
        .map_err(|e| PurchaseError::Other(format!("Failed to parse prices: {}", e)))?;
    Ok(prices)
}

/// Calculate volume discount for given amount
pub async fn get_volume_discount(tg_id: i64, amount: u32) -> Result<f64, PurchaseError> {
    let tg_id = get_test_user_id(tg_id);
    let url = format!("{host}/rpc/calculate", host=&*BACKEND_API_URL);
    log::trace!("fetching {url}", url = url.red());

    let response = reqwest::Client::new()
        .post(&url)
        .json(&PriceCalculationRequest {
            amounts: vec![amount],
        })
        .header("Authorization", get_auth_token().await.map_err(|e| PurchaseError::Other(e.to_string()))?)
        .header("X-Tg-Id", tg_id.to_string())
        .header("Content-Type", "application/json")
        .send()
        .await
        .map_err(|e| PurchaseError::NetworkError(e.to_string()))?;

    if !response.status().is_success() {
        let error_text = response.text().await.unwrap_or_default();
        return Err(PurchaseError::Other(format!(
            "Failed to get volume discount: {}",
            prettify_json_or_text(&error_text)
        )));
    }

    let discounts = response.json::<Vec<f64>>().await
        .map_err(|e| PurchaseError::Other(format!("Failed to parse volume discount: {}", e)))?;
    Ok(discounts.first().copied().unwrap_or(0.0))
}

/// Get promocode information
pub async fn get_promocode_info(tg_id: i64, code: &str, amount: u32) -> Result<PromoCodeInfo, PurchaseError> {
    let tg_id = get_test_user_id(tg_id);
    let url = format!("{host}/rpc/discount_promocode_info", host=&*BACKEND_API_URL);
    log::trace!("fetching {url}", url = url.red());

    let response = reqwest::Client::new()
        .get(&url)
        .query(&[("code", code), ("amount", &amount.to_string())])
        .header("Authorization", get_auth_token().await.map_err(|e| PurchaseError::Other(e.to_string()))?)
        .header("X-Tg-Id", tg_id.to_string())
        .send()
        .await
        .map_err(|e| PurchaseError::NetworkError(e.to_string()))?;

    if !response.status().is_success() {
        if response.status() == reqwest::StatusCode::UNPROCESSABLE_ENTITY {
            return Err(PurchaseError::PromoCodeNotExists);
        }
        let error_text = response.text().await.unwrap_or_default();
        return Err(PurchaseError::Other(format!(
            "Failed to get promocode info: {}",
            prettify_json_or_text(&error_text)
        )));
    }

    let promo_info = response.json::<PromoCodeInfo>().await
        .map_err(|e| PurchaseError::Other(format!("Failed to parse promocode info: {}", e)))?;
    Ok(promo_info)
}

#[derive(Debug, Serialize, Deserialize)]
pub struct DetailedPriceCalculation {
    pub base_price: f64,
    pub pool_price: f64,
    pub volume_discount: f64,
    pub volume_discount_amount: f64,
    pub promo_discount: Option<f64>,
    pub promo_discount_amount: Option<f64>,
    pub final_price: f64,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct RawPriceCalculation {
    pub base_price: Option<f64>,
    pub pool_price: Option<f64>,
    pub volume_discount: Option<f64>,
    pub volume_discount_amount: Option<f64>,
    pub promo_discount: Option<f64>,
    pub promo_discount_amount: Option<f64>,
    pub final_price: Option<f64>,
    #[serde(flatten)]
    pub extra: std::collections::HashMap<String, serde_json::Value>,
}

/// Calculate final price with all discounts applied using database function
pub async fn calculate_detailed_price(
    tg_id: i64,
    pool: &PoolType,
    amount: u32,
    promocode: Option<&str>,
) -> Result<DetailedPriceCalculation, PurchaseError> {
    let url = format!("{host}/rpc/calculate_detailed_price", host=&*BACKEND_API_URL);
    log::trace!("fetching {url}", url = url.red());

    let mut params = vec![
        ("pool", pool.to_string()),
        ("amount", amount.to_string()),
    ];

    if let Some(code) = promocode {
        if !code.trim().is_empty() {
            params.push(("promocode", code.to_string()));
        }
    }

    log::debug!("🔍 Request parameters: {:?}", params);

    let tg_id = get_test_user_id(tg_id);
    let response = reqwest::Client::new()
        .get(&url)
        .query(&params)
        .header("Authorization", get_auth_token().await.map_err(|e| PurchaseError::Other(e.to_string()))?)
        .header("X-Tg-Id", tg_id.to_string())
        .send()
        .await
        .map_err(|e| PurchaseError::NetworkError(e.to_string()))?;

    let status = response.status();

    if !status.is_success() {
        let error_text = response.text().await.unwrap_or_default();
        if status == reqwest::StatusCode::UNPROCESSABLE_ENTITY {
            if error_text.contains("promocode") {
                return Err(PurchaseError::PromoCodeNotExists);
            } else {
                return Err(PurchaseError::Other(error_text));
            }
        }
        return Err(PurchaseError::Other(format!(
            "Failed to calculate price: {}",
            prettify_json_or_text(&error_text)
        )));
    }

    let response_text = response.text().await
        .map_err(|e| PurchaseError::Other(format!("Failed to read response: {}", e)))?;
    
    log::debug!("🔍 Raw API response: {}", response_text);
    
    match serde_json::from_str::<RawPriceCalculation>(&response_text) {
        Ok(raw_calc) => {
            log::debug!("🔍 Parsed raw calculation: {:?}", raw_calc);
            log::debug!("🔍 Extra fields: {:?}", raw_calc.extra);
            
            let calculation = DetailedPriceCalculation {
                base_price: raw_calc.base_price.unwrap_or(0.0),
                pool_price: raw_calc.pool_price.unwrap_or(0.0),
                volume_discount: raw_calc.volume_discount.unwrap_or(0.0),
                volume_discount_amount: raw_calc.volume_discount_amount.unwrap_or(0.0),
                promo_discount: raw_calc.promo_discount,
                promo_discount_amount: raw_calc.promo_discount_amount,
                final_price: raw_calc.final_price.unwrap_or(0.0),
            };
            
            log::debug!("✅ Successfully converted to calculation: {:?}", calculation);
            Ok(calculation)
        }
        Err(e) => {
            log::error!("❌ Failed to parse as RawPriceCalculation: {}", e);
            log::error!("❌ Response text: {}", response_text);
            
            let calculation = serde_json::from_str::<DetailedPriceCalculation>(&response_text)
                .map_err(|e2| {
                    log::error!("❌ Also failed to parse as DetailedPriceCalculation: {}", e2);
                    PurchaseError::Other(format!("Failed to parse price calculation: {}", e2))
                })?;
            
            log::debug!("✅ Successfully parsed calculation: {:?}", calculation);
            Ok(calculation)
        }
    }
}

/// Invoice creation result with URL and optional ID
#[derive(Debug)]
pub struct InvoiceResult {
    pub url: String,
    pub id: Option<String>,
}

/// Create invoice for web payment methods
pub async fn create_invoice_web(
    tg_id: i64,
    request: PurchaseRequest,
) -> Result<InvoiceResult, PurchaseError> {
    let url = format!("{host}/rpc/create_invoice_web", host=&*BACKEND_API_URL);
    log::trace!("fetching {url}", url = url.red());

    let tg_id = get_test_user_id(tg_id);
    let response = reqwest::Client::new()
        .post(&url)
        .json(&request)
        .header("Authorization", get_auth_token().await.map_err(|e| PurchaseError::Other(e.to_string()))?)
        .header("X-Tg-Id", tg_id.to_string())
        .header("Content-Type", "application/json")
        .header("Accept", "text/plain")
        .send()
        .await
        .map_err(|e| PurchaseError::NetworkError(e.to_string()))?;

    let status = response.status();

    if !status.is_success() {
        if status == reqwest::StatusCode::CONFLICT {
            return Err(PurchaseError::PaymentMethodNotAvailable);
        }
        let error_text = response.text().await.unwrap_or_default();
        if status == reqwest::StatusCode::UNPROCESSABLE_ENTITY {
            if error_text.contains("promocode") {
                return Err(PurchaseError::PromoCodeNotExists);
            } else {
                return Err(PurchaseError::Other(error_text));
            }
        }
        return Err(PurchaseError::Other(format!(
            "Failed to create invoice: {}",
            prettify_json_or_text(&error_text)
        )));
    }

    let invoice_id = response.headers().get("x-invoice-id")
        .and_then(|v| v.to_str().ok())
        .map(|s| s.to_string());
    
    if let Some(ref id) = invoice_id {
        log::info!("💳 Invoice created with ID: #{}", id);
    }
    
    let invoice_url = response.text().await
        .map_err(|e| PurchaseError::Other(format!("Failed to read invoice URL: {}", e)))?;

    log::debug!("Invoice url: {}", invoice_url.clone());
    
    Ok(InvoiceResult {
        url: invoice_url,
        id: invoice_id,
    })
}

/// Process referral payment
pub async fn process_referral_payment(
    tg_id: i64,
    request: ReferralPaymentRequest,
) -> Result<(), PurchaseError> {
    let tg_id = get_test_user_id(tg_id);
    let url = format!("{host}/rpc/referral_payment", host=&*BACKEND_API_URL);
    log::trace!("fetching {url}", url = url.red());

    let response = reqwest::Client::new()
        .post(&url)
        .json(&request)
        .header("Authorization", get_auth_token().await.map_err(|e| PurchaseError::Other(e.to_string()))?)
        .header("X-Tg-Id", tg_id.to_string())
        .header("Content-Type", "application/json")
        .send()
        .await
        .map_err(|e| PurchaseError::NetworkError(e.to_string()))?;

    let status = response.status();

    if !status.is_success() {
        if status == reqwest::StatusCode::PAYMENT_REQUIRED {
            return Err(PurchaseError::InsufficientReferralBalance);
        }
        let error_text = response.text().await.unwrap_or_default();
        if status == reqwest::StatusCode::UNPROCESSABLE_ENTITY {
            if error_text.contains("promocode") {
                return Err(PurchaseError::PromoCodeNotExists);
            } else {
                return Err(PurchaseError::Other(error_text));
            }
        }
        let formatted_error = if error_text.trim().is_empty() {
            format!("HTTP {} with empty response", status)
        } else {
            prettify_json_or_text(&error_text)
        };
        return Err(PurchaseError::Other(format!(
            "Failed to process referral payment: {}",
            formatted_error
        )));
    }

    Ok(())
}

pub async fn get_payment_systems(tg_id: i64) -> Result<Vec<PaymentSystem>, PurchaseError> {
    let tg_id = get_test_user_id(tg_id);
    let url = format!("{host}/payment_systems", host=&*BACKEND_API_URL);
    log::trace!("fetching {url}", url = url.red());

    let response = reqwest::Client::new()
        .get(&url)
        .header("Authorization", get_auth_token().await.map_err(|e| PurchaseError::Other(e.to_string()))?)
        .header("X-Tg-Id", tg_id.to_string())
        .send()
        .await
        .map_err(|e| PurchaseError::NetworkError(e.to_string()))?;

    if !response.status().is_success() {
        let error_text = response.text().await.unwrap_or_default();
        return Err(PurchaseError::Other(format!(
            "Failed to get payment systems: {}",
            prettify_json_or_text(&error_text)
        )));
    }

    let payment_systems = response.json::<Vec<PaymentSystem>>().await
        .map_err(|e| PurchaseError::Other(format!("Failed to parse payment systems: {}", e)))?;
    
    Ok(payment_systems)
}

/// Process Telegram Stars payment
pub async fn process_telegram_stars_payment(
    payment_data: SuccessfulPaymentRequestData
) -> Result<(), PurchaseError> {
    let tg_id = payment_data.user_id;
    let tg_id = get_test_user_id(tg_id);

    let url = format!("{host}/rpc/telegram_stars_payment_2", host=&*BACKEND_API_URL);
    log::trace!("processing telegram stars payment: {url}", url = url.red());

    let request = reqwest::Client::new()
        .post(&url)
        .json(&payment_data)
        .header("Authorization", get_auth_token().await.map_err(|e| PurchaseError::Other(e.to_string()))?)
        .header("X-Tg-Id", tg_id.to_string())
        .header("Content-Type", "application/json");

    log::debug!("{request:#?}");

    let response = request
        .send()
        .await
        .map_err(|e| PurchaseError::NetworkError(e.to_string()))?;

    let status = response.status();

    if !status.is_success() {
        let error_text = response.text().await.unwrap_or_default();
        if status == reqwest::StatusCode::UNPROCESSABLE_ENTITY {
            if error_text.contains("promocode") {
                return Err(PurchaseError::PromoCodeNotExists);
            }
        }
        return Err(PurchaseError::Other(format!(
            "Failed to process Telegram Stars payment: {}",
            prettify_json_or_text(&error_text)
        )));
    }

    Ok(())
} 
