use crate::i18n::Language;
use crate::hashmap::HashMap;
use crate::t8x;
use serde_repr::*;
use serde::{Deserialize, Serialize};
use strum_macros::{Display, EnumIter};

#[derive(
    Clone, Deserialize, Serialize, Display, Debug, PartialEq, Eq, Copy, Default,
)]
#[strum(serialize_all = "snake_case")]
#[serde(rename_all = "snake_case")]
pub enum ProxyProtocol {
    #[default]
    Http,
    Https,
    Socks5,
}

#[derive(
    Clone,
    Deserialize,
    Serialize,
    Display,
    Debug,
    PartialEq,
    Eq,
    Copy,
    Default,
    EnumIter,
)]
#[strum(serialize_all = "snake_case")]
pub enum ProxyRegion {
    #[serde(rename = "eur")]
    Europe,
    #[serde(rename = "aus")]
    Australia,
    #[serde(rename = "usa")]
    USA,
    #[serde(rename = "bra")]
    Brazil,
    #[serde(rename = "sgp")]
    Singapore,
    #[serde(rename = "jpn")]
    Japan,
    #[default]
    #[serde(rename = "auto")]
    Auto,
}

impl ProxyRegion {
    pub fn display_name(&self, ln: &Language) -> String {
        self.display_name_custom(ln, false)
    }

    pub fn display_name_custom(
        &self,
        ln: &Language,
        with_icon: bool,
    ) -> String {
        fn resolve(
            name: &str,
            with_icon: bool,
            icon: &str,
        ) -> String {
            if with_icon {
                format!("{} {}", icon, name)
            } else {
                name.to_string()
            }
        }

        match self {
            ProxyRegion::Europe => resolve(t8x!("Europe", ln, "static"), with_icon, "🇪🇺"),
            ProxyRegion::Australia => {
                resolve(t8x!("Australia", ln, "static"), with_icon, "🇦🇺")
            }
            ProxyRegion::USA => resolve(t8x!("USA", ln, "static"), with_icon, "🇺🇸"),
            ProxyRegion::Brazil => resolve(t8x!("Brazil", ln, "static"), with_icon, "🇧🇷"),
            ProxyRegion::Singapore => {
                resolve(t8x!("Singapore", ln, "static"), with_icon, "🇸🇬")
            }
            ProxyRegion::Japan => resolve(t8x!("Japan", ln, "static"), with_icon, "🇯🇵"),
            ProxyRegion::Auto => resolve(t8x!("Auto", ln, "static"), with_icon, "🧠"),
        }
    }
}

#[derive(
    Clone, Deserialize, Serialize, Display, Debug, PartialEq, Eq, Copy, Default,
)]
#[strum(serialize_all = "snake_case")]
pub enum ProxyType {
    #[default]
    Rotating,
    Sticky(Option<u32>),
}

#[derive(
    Clone,
    Deserialize,
    Serialize,
    Debug,
    Display,
    PartialEq,
    Eq,
    Copy,
    Default,
    EnumIter,
)]
pub enum ProxyFormat {
    #[strum(serialize = "login:password:hostname:port")]
    #[serde(rename = "login:password:hostname:port")]
    LoginPasswordHostnamePort,
    #[strum(serialize = "login:password@hostname:port")]
    #[serde(rename = "login:password@hostname:port")]
    LoginPasswordAtHostnamePort,
    #[strum(serialize = "hostname:port@login:password")]
    #[serde(rename = "hostname:port@login:password")]
    HostnamePortAtLoginPassword,
    #[default]
    #[strum(serialize = "hostname:port:login:password")]
    #[serde(rename = "hostname:port:login:password")]
    HostnamePortLoginPassword,
    #[strum(serialize = "hostname:port")]
    #[serde(rename = "hostname:port")]
    HostnamePort,
}


#[derive(Clone, Deserialize, Debug, Serialize)]
pub struct PoolInfo {
    pub balance: i64,
    pub balance_format: String,
    pub password: Option<String>,
}

impl Default for PoolInfo {
    fn default() -> Self {
        Self {
            balance: 0,
            balance_format: "0 GB".to_string(),
            password: None,
        }
    }
}

#[derive(Clone, Deserialize, Debug, Serialize)]
#[serde(untagged)]
pub enum PoolData {
    /// Full pool data with balance, format, and password
    Full(PoolInfo),
    /// Slim pool data (empty object) - just indicates pool exists
    Slim(serde_json::Value),
}

impl PoolData {
    pub fn get_pool_info(&self) -> PoolInfo {
        match self {
            PoolData::Full(info) => info.clone(),
            PoolData::Slim(_) => {
                log::warn!("You are using slim profile, but you are trying to get full pool info, fix it immediately!");
                PoolInfo::default()
            }
        }
    }

    pub fn is_full(&self) -> bool {
        matches!(self, PoolData::Full(_))
    }

    pub fn is_slim(&self) -> bool {
        matches!(self, PoolData::Slim(_))
    }
}

impl Default for PoolData {
    fn default() -> Self {
        PoolData::Full(PoolInfo::default())
    }
}

#[derive(Clone, Deserialize, Default, Debug, Serialize)]
pub struct Profile {
    #[serde(rename = "user_id")]
    pub id: String,
    pub username: Option<String>,
    pub email: Option<String>,
    #[serde(rename = "ref_balance_usd")]
    pub referral_balance: f64,
    #[serde(rename = "pers_percent")]
    pub personal_referral_percent: Option<String>,
    pub purchase_count: i32,
    #[serde(default)]
    pub purchase_sum_gb: i32,
    #[serde(deserialize_with = "deserialize_pools")]
    pub pools: HashMap<PoolType, PoolData>,
    #[serde(rename = "lang")]
    pub language: Language,
}

impl Profile {
    /// Get pool data for a specific pool type
    pub fn get_pool_data(&self, pool_type: &PoolType) -> Option<&PoolData> {
        self.pools.get(pool_type)
    }

    /// Get pool info for a specific pool type (extracts from PoolData)
    pub fn get_pool(&self, pool_type: &PoolType) -> Option<PoolInfo> {
        self.pools.get(pool_type).map(|data| data.get_pool_info())
    }

    /// Get pool info or default for a specific pool type
    pub fn get_pool_or_default(&self, pool_type: &PoolType) -> PoolInfo {
        self.get_pool(pool_type).unwrap_or_default()
    }

    /// Set pool data for a specific pool type
    pub fn set_pool_data(&mut self, pool_type: PoolType, pool_data: PoolData) {
        self.pools.insert(pool_type, pool_data);
    }

    /// Set pool info for a specific pool type (wraps in Full variant)
    pub fn set_pool(&mut self, pool_type: PoolType, pool_info: PoolInfo) {
        self.pools.insert(pool_type, PoolData::Full(pool_info));
    }

    /// Get all available pool types that have data
    pub fn available_pools(&self) -> Vec<PoolType> {
        self.pools.keys().cloned().collect()
    }

    /// Get all pool types (even if they don't have data)
    pub fn all_pool_types() -> Vec<PoolType> {
        vec![
            PoolType::Datacenter,
            PoolType::Residential,
            PoolType::ResidentialPremium,
            PoolType::Mobile,
        ]
    }

    /// Check if the profile contains full pool data
    pub fn has_full_pool_data(&self) -> bool {
        self.pools.values().any(|data| data.is_full())
    }

    /// Check if the profile contains only slim pool data
    pub fn is_slim_profile(&self) -> bool {
        !self.pools.is_empty() && self.pools.values().all(|data| data.is_slim())
    }
}

fn deserialize_pools<'de, D>(
    deserializer: D,
) -> Result<HashMap<PoolType, PoolData>, D::Error>
where
    D: serde::Deserializer<'de>,
{
    let pools_opt: Option<HashMap<String, PoolData>> =
        Option::deserialize(deserializer)?;

    let mut result = HashMap::default();

    if let Some(pools_map) = pools_opt {
        for (pool_name, pool_data) in pools_map {
            let pool_type = match pool_name.as_str() {
                "datacenter" => PoolType::Datacenter,
                "residential" => PoolType::Residential,
                "residential_premium" => PoolType::ResidentialPremium,
                "mobile" => PoolType::Mobile,
                _ => {
                    log::warn!(
                        "Backend returned unknown pool type: {}",
                        pool_name
                    );
                    continue; // Skip unknown pool types
                }
            };
            result.insert(pool_type, pool_data);
        }
    }

    Ok(result)
}

#[derive(Clone, Deserialize, Default)]
pub struct ReferralInfo {
    pub percent: String,
    #[serde(rename = "ref_num")]
    pub count: String,
    pub balance: String,
    pub links: ReferralLinks,
}

#[derive(Clone, Deserialize, Default)]
pub struct ReferralLinks {
    pub website: String,
    pub telegram: String,
}

#[derive(Clone, Serialize, Deserialize)]
pub struct TgUser {
    pub id: i64,
    pub username: Option<String>,
    pub first_name: Option<String>,
    pub surname: Option<String>,
    pub referral_id: Option<i64>,
}

impl TgUser {
    pub fn new(tg_id: i64) -> Self {
        Self {
            id: tg_id,
            username: None,
            first_name: None,
            surname: None,
            referral_id: None,
        }
    }

    pub fn with_username(mut self, username: String) -> Self {
        self.username = Some(username);
        self
    }

    pub fn with_first_name(mut self, first_name: String) -> Self {
        self.first_name = Some(first_name);
        self
    }

    pub fn with_last_name(mut self, last_name: String) -> Self {
        self.surname = Some(last_name);
        self
    }

    pub fn with_referral(mut self, referral_id: i64) -> Self {
        self.referral_id = Some(referral_id);
        self
    }
}

pub type Country = Location;
pub type State = Location;
pub type City = Location;
pub type ASN = Location;
pub type ZipCode = Location;

#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Hash, Debug, Default)]
pub struct Location {
    pub code: String,
    pub name: String,
    pub count: u32,
}

impl Location {
    /// Extract codes from a collection of locations for API calls
    pub fn extract_codes<T: AsRef<Location>>(locations: &[T]) -> Vec<String> {
        locations.iter().map(|loc| loc.as_ref().code.clone()).collect()
    }
}

impl AsRef<Location> for Location {
    fn as_ref(&self) -> &Location {
        self
    }
}

/// Wrapper for Location with selection state - used in UI for managing selections
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, Default)]
#[serde(default)]
pub struct SelectableLocation {
    pub location: Location,
    pub selected: bool,
}

impl SelectableLocation {
    pub fn new(location: Location) -> Self {
        Self {
            location,
            selected: false,
        }
    }

    pub fn new_selected(location: Location, selected: bool) -> Self {
        Self {
            location,
            selected,
        }
    }

    pub fn toggle(&mut self) {
        self.selected = !self.selected;
    }

    /// Extract codes from selected locations only
    pub fn extract_selected_codes(locations: &[SelectableLocation]) -> Vec<String> {
        locations
            .iter()
            .filter(|loc| loc.selected)
            .map(|loc| loc.location.code.clone())
            .collect()
    }

    /// Extract all selected locations
    pub fn extract_selected(locations: &[SelectableLocation]) -> Vec<Location> {
        locations
            .iter()
            .filter(|loc| loc.selected)
            .map(|loc| loc.location.clone())
            .collect()
    }
}

impl AsRef<Location> for SelectableLocation {
    fn as_ref(&self) -> &Location {
        &self.location
    }
}

impl From<Location> for SelectableLocation {
    fn from(location: Location) -> Self {
        SelectableLocation::new(location)
    }
}

#[derive(
    Clone,
    Copy,
    Display,
    Debug,
    Serialize,
    Deserialize,
    PartialEq,
    Eq,
    Hash,
    EnumIter,
)]
#[strum(serialize_all = "snake_case")]
#[serde(rename_all = "snake_case")]
pub enum PoolType {
    Datacenter,
    Residential,
    ResidentialPremium,
    Mobile,
}

impl From<PoolTypeWithOptimazedSerialization> for PoolType {
    fn from(pool_type: PoolTypeWithOptimazedSerialization) -> Self {
        match pool_type {
            PoolTypeWithOptimazedSerialization::Datacenter => PoolType::Datacenter,
            PoolTypeWithOptimazedSerialization::Residential => PoolType::Residential,
            PoolTypeWithOptimazedSerialization::ResidentialPremium => PoolType::ResidentialPremium,
            PoolTypeWithOptimazedSerialization::Mobile => PoolType::Mobile,
        }
    }
}

#[derive(Serialize_repr, Deserialize_repr, PartialEq, Debug, Copy, Clone)]
#[repr(u8)]
pub enum PoolTypeWithOptimazedSerialization {
    Datacenter,
    Residential,
    ResidentialPremium,
    Mobile,
}

impl From<PoolType> for PoolTypeWithOptimazedSerialization {
    fn from(pool_type: PoolType) -> Self {
        match pool_type {
            PoolType::Datacenter => PoolTypeWithOptimazedSerialization::Datacenter,
            PoolType::Residential => PoolTypeWithOptimazedSerialization::Residential,
            PoolType::ResidentialPremium => PoolTypeWithOptimazedSerialization::ResidentialPremium,
            PoolType::Mobile => PoolTypeWithOptimazedSerialization::Mobile,
        }
    }
}

impl PoolType {
    /// Get all pool types as a vector
    pub fn all() -> Vec<PoolType> {
        vec![
            PoolType::Datacenter,
            PoolType::Residential,
            PoolType::ResidentialPremium,
            PoolType::Mobile,
        ]
    }
    
    /// Get string representation for API calls
    pub fn as_str(&self) -> &'static str {
        match self {
            PoolType::Datacenter => "datacenter",
            PoolType::Residential => "residential",
            PoolType::ResidentialPremium => "residential_premium",
            PoolType::Mobile => "mobile",
        }
    }

    /// Get localized display name for the pool type
    pub fn display_name(&self, ln: &Language) -> String {
        self.display_name_custom(ln, false)
    }

    pub fn display_name_custom(
        &self,
        ln: &Language,
        with_icon: bool,
    ) -> String {
        fn resolve(
            name: &str,
            with_icon: bool,
            icon: &str,
        ) -> String {
            if with_icon {
                format!("{} {}", icon, name)
            } else {
                name.to_string()
            }
        }

        match self {
            PoolType::Datacenter => resolve(t8x!("Datacenter", ln, "static"), with_icon, "📺"),
            PoolType::Residential => {
                resolve(t8x!("Residential", ln, "static"), with_icon, "🖥")
            }
            PoolType::ResidentialPremium => {
                resolve(t8x!("Residential Premium", ln, "static"), with_icon, "🖥")
            }
            PoolType::Mobile => resolve(t8x!("Mobile", ln, "static"), with_icon, "📱"),
        }
    }

    /// Get pool types excluding specified ones
    pub fn all_except(excluded: &[PoolType]) -> Vec<PoolType> {
        Self::all()
            .into_iter()
            .filter(|pool| !excluded.contains(pool))
            .collect()
    }

    /// Check if pool type is premium tier
    pub fn is_premium(&self) -> bool {
        matches!(self, PoolType::ResidentialPremium)
    }

    /// Check if pool type is residential (any tier)
    pub fn is_residential(&self) -> bool {
        matches!(self, PoolType::Residential | PoolType::ResidentialPremium)
    }
}

#[derive(Clone, Deserialize)]
pub struct PaymentSystem {
    pub description: String,
    pub name: String,
}

#[derive(Clone, Deserialize)]
pub struct PaymentInovice {
    pub invoice_url: String,
}

#[derive(Clone, Deserialize)]
pub struct Banner {
    pub id: i32,
    pub link: String,
    pub name: String,
}

#[derive(Clone, Deserialize)]
pub struct AmountTopEntry {
    pub gb: i32,
    pub purchases: i32,
}

#[derive(Clone, Deserialize)]
pub struct UsersTopEntry {
    pub user_id: String,
    pub purchases: i32,
}

#[derive(Clone, Deserialize)]
pub struct AdminStats {
    pub total_users: u64,
    pub web_users: u64,
    pub activated_subusers: u64,
    pub users_with_purchases: u64,
    pub di_balance: f64,
    pub ref_balance: f64,
    pub paid_total: f64,
    pub purchases_total: i32,
    pub amount_top: Vec<AmountTopEntry>,
    pub users_top: Vec<UsersTopEntry>,
}

#[derive(Clone, Deserialize, Default)]
pub struct TrafficPromoStats {
    #[serde(default)]
    pub gb: u32,
    #[serde(default)]
    pub amount: u32,
}

#[derive(Clone, Deserialize, Default)]
pub struct DiscountPromoStats {
    #[serde(default)]
    pub count: u32,
}

#[derive(Clone, Deserialize)]
pub struct PromoStats {
    #[serde(default)]
    pub traffic: TrafficPromoStats,
    #[serde(default)]
    pub discount: DiscountPromoStats,
}

#[derive(Clone, Deserialize)]
pub struct PoolSourceStats {
    pub gb: u32,
    pub sum: f64,
    pub count: u32,
}

impl Default for PoolSourceStats {
    fn default() -> Self {
        PoolSourceStats {
            gb: 0,
            sum: 0.0,
            count: 0,
        }
    }
}

#[derive(Clone, Deserialize)]
pub struct DailyStats {
    #[serde(default)]
    pub datacenter_web: PoolSourceStats,
    #[serde(default)]
    pub residential_web: PoolSourceStats,
    #[serde(default)]
    pub mobile_web: PoolSourceStats,
    #[serde(default)]
    pub residential_premium_web: PoolSourceStats,
    #[serde(default)]
    pub datacenter_bot: PoolSourceStats,
    #[serde(default)]
    pub residential_bot: PoolSourceStats,
    #[serde(default)]
    pub mobile_bot: PoolSourceStats,
    #[serde(default)]
    pub residential_premium_bot: PoolSourceStats,
    pub web_users_total: u32,
    pub new_users_day: u32,
    pub new_users_week: u32,
    pub new_users_month: u32,
    pub promo: PromoStats,
}

impl DailyStats {
    pub fn web_purchase_count(&self) -> u32 {
        self.datacenter_web.count
            + self.mobile_web.count
            + self.residential_web.count
            + self.residential_premium_web.count
    }

    pub fn bot_purchase_count(&self) -> u32 {
        self.datacenter_bot.count
            + self.mobile_bot.count
            + self.residential_bot.count
            + self.residential_premium_bot.count
    }

    pub fn total_purchase_count(&self) -> u32 {
        self.bot_purchase_count() + self.web_purchase_count()
    }

    pub fn web_purchase_sum(&self) -> f64 {
        self.datacenter_web.sum
            + self.mobile_web.sum
            + self.residential_web.sum
            + self.residential_premium_web.sum
    }

    pub fn bot_purchase_sum(&self) -> f64 {
        self.datacenter_bot.sum
            + self.mobile_bot.sum
            + self.residential_bot.sum
            + self.residential_premium_bot.sum
    }

    pub fn total_purchase_sum(&self) -> f64 {
        self.bot_purchase_sum() + self.web_purchase_sum()
    }
}

#[derive(Clone, Debug, Deserialize)]
pub struct UserStatistics {
    pub usage: Vec<UsageItem>,
    pub total: TotalUsage,
}

#[derive(Clone, Debug, Deserialize)]
pub struct UsageItem {
    #[serde(rename = "d_usage")]
    pub used_at: String,
    pub traffic: i64,
    pub extra_traffic: i64,
    pub request: i64,
}

#[derive(Clone, Debug, Deserialize)]
pub struct TotalUsage {
    pub traffic: i64,
    pub extra_traffic: i64,
    pub request: i64,
}

#[derive(Clone, Debug)]
pub enum Period {
    Week,
    Month,
}

impl Period {
    pub fn as_str(&self) -> &'static str {
        match self {
            Period::Week => "week",
            Period::Month => "month",
        }
    }
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DetailedUsageItem {
    pub host: Option<String>,
    #[serde(default)]
    pub status: String,
    #[serde(default)]
    pub message: Option<String>,
    #[serde(default)]
    pub traffic: String,
    #[serde(default)]
    pub datetime: String,
    pub requests: u32,
    #[serde(default)]
    pub extra_traffic: String,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DetailedUserStatistics {
    pub usage: Vec<DetailedUsageItem>,
    pub total_count: u32,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SourceLink {
    pub name: String,
    pub value: String,
    pub count: HashMap<String, i64>,
}

impl SourceLink {
    pub fn total_count(&self) -> i64 {
        self.count.values().sum()
    }
    
    pub fn get_source_count(&self, source: &str) -> i64 {
        self.count.get(source).copied().unwrap_or(0)
    }
    
    pub fn bot_count(&self) -> i64 {
        self.get_source_count("bot")
    }
    
    pub fn web_count(&self) -> i64 {
        self.get_source_count("web")
    }
}

#[derive( Clone, Deserialize, Serialize, Debug, Default)]
pub struct RefLinkDetails {
    pub stats: RefLinkStats,
    pub users: Vec<RefLinkUser>,
    pub ref_code: String,
    pub created_at: String,
    pub display_name: String,
}

#[derive( Clone, Deserialize, Serialize, Debug, Default)]
pub struct RefLinkUser {
    pub source: String,
    pub user_id: Option<String>,
    pub has_paid: bool,
    pub traffic_pools: HashMap<PoolType, RefLinkTrafficPool>,
}

#[derive( Clone, Deserialize, Serialize, Debug, Default)]
pub struct RefLinkTrafficPool {
    pub sub_id: u32,
    pub balance_format: String,
}

#[derive( Clone, Deserialize, Serialize, Debug, Default)]
pub struct RefLinkStats {
    pub paid_users: u32,
    pub total_users: u32,
    pub conversion_rate: f64,
    pub total_traffic_gb: u64,
}

#[derive(Clone, Deserialize, Debug)]
pub struct PromoCode {
    pub promo_name: String,
    pub traffic_amount: i32,
    pub remaining_activation: i32,
    pub term: Option<String>,
    pub pool: String,
    pub created_by: Option<String>,
}

#[derive(Clone, Debug, Serialize)]
pub struct PurchaseRequest {
    pub payment_method: String,
    pub amount: u32,
    pub pool: PoolType,
    pub promocode: Option<String>,
}

#[derive(Clone, Debug, Serialize)]
pub struct ReferralPaymentRequest {
    pub amount: u32,
    pub pool: PoolType,
    pub promocode: Option<String>,
}

#[derive(Serialize)]
pub struct WithdrawalRequest {
    pub amount: f64,
    pub comment: String,
}

#[derive(Deserialize)]
pub struct WithdrawalResponse {
    pub success: bool,
    pub message: String,
    pub request_id: i64,
    pub amount: f64,
    pub status: String,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct DefaultProxySettings {
    #[serde(default)]
    pub countries: Vec<String>,
    #[serde(default)]
    pub cities: Vec<String>,
    #[serde(default)]
    pub states: Vec<String>,
    #[serde(default)]
    pub zipcodes: Vec<String>,
    #[serde(default)]
    pub asns: Vec<String>,
    #[serde(default)]
    pub exclude_countries: Vec<String>,
    #[serde(default)]
    pub exclude_asn: Vec<String>,
    #[serde(default)]
    pub anonymous_filter: bool,
    pub rotation_interval: Option<u32>, // in minutes
}


#[derive(Debug, Clone, PartialEq)]
pub enum LocationType {
    Countries,
    Cities,
    States,
    ZipCodes,
    ASNs,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FullPaymentSystem {
    pub name: String,
    pub is_available: String,
    pub description: Option<String>,
    pub created_at: Option<String>,
    pub updated_at: Option<String>,
}

impl FullPaymentSystem {
    pub fn is_enabled(&self) -> bool {
        self.is_available == "true"
    }
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct WithdrawRequest {
    pub id: i64,
    pub user_id: String,
    pub amount: i64, // amount in cents
    pub comment: String,
    pub status: String,
    pub created_at: String,
    pub updated_at: String,
}

impl WithdrawRequest {
    pub fn amount_usd(&self) -> f64 {
        self.amount as f64 / 100.0
    }
}
