pub mod admin;
pub mod user;
pub mod support;
pub mod schema;
mod shared;

pub mod prelude {
    pub use teloxide::{
        prelude::*,
        types::{self as teloxide_types, *},
        utils::command::BotCommands,
        dispatching::{UpdateHandler, dialogue},
        dptree::{self, case},
    };
    pub use color_eyre::Result;
    pub use rust_shared::{
        i18n::Language::{self, Ru, En},
        hashmap::HashMap,
    };
    pub use crate::{
        util::{
            format_bytes, format_mb,
            pagination::{Paginator, PaginationConfig, PaginationKeyboardBuilder, SearchablePaginator},
            filter::{filter_not_command, filter_callback_data, FilterNotCommandExt},
        },
        t8x,
        filter_localized_text,
        no_preview,
        Bot,
        case_expr,
        consts::*, 
        HandlerSettings,
        ForceHandlerBehavior,
        ui::{shared::*, *, AdminStateKind}
    };
    pub use crate::repository::api;
    pub use rust_shared::repository::model::{*, Location, SelectableLocation};
    pub use colored::Colorize;
    pub use strum::IntoEnumIterator;
    pub use indoc::indoc;
    pub use strfmt::strfmt;
    pub use std::{mem, error::Error, fmt::{self, Display, Formatter}};
    pub use serde::{Serialize, Deserialize};
}

use self::prelude::*;
use std::pin::Pin;
use std::future::Future;
use teloxide::{
    dispatching::dialogue::ErasedStorage, prelude::Dialogue, types::MessageId,
};

pub type HandlerFn = Pin<Box<dyn Future<Output = HandlerResult> + Send + 'static>>;
pub type HandlerError = Box<dyn Error + Send + Sync>;
pub type HandlerResult = Result<(), HandlerError>;

pub type MasterDialogue = Dialogue<MasterState, ErasedStorage<MasterState>>;
pub type GetProxyDialogue = Dialogue<GetProxyStates, ErasedStorage<GetProxyStates>>;
pub type PurchaseDialogue = Dialogue<PurchaseState, ErasedStorage<PurchaseState>>;

#[derive(Clone, Debug, Default, Serialize, Deserialize)]
#[serde(tag = "type", content = "data")]
pub enum SupportStateKind {
    #[default]
    Menu,
    ReceivePoolType,
    ReceivePoolPrice {
        pool_type: PoolType,
    },
    StatsReceiveUserId,
    StatsReceivePool {
        user_id: String,
    },
    PromoSelectPool {
        names: Vec<String>,
        activations: i32,
        gb_amount: i32,
        count: i32,
        expiration_date: Option<String>,
    },
    DeletePromos {
        page: usize,
    },
    DevelopmentBlog {
        page: usize,
    },
    AddTrafficReceiveAmount {
        user_id: String,
        pool: PoolType,
    },
    PaymentSystemsMenu,
}

#[derive(Clone, Debug, Default, Serialize, Deserialize)]
#[serde(tag = "type", content = "data")]
pub enum AdminStateKind {
    #[default]
    Menu,
    ReceivePoolType,
    ReceivePoolPrice {
        pool_type: PoolType,
    },
    StatsReceiveUserId {
        user_id: Option<String>,
    },
    StatsReceivePool {
        user_id: String,
    },
    StatsReceivePeriod,
    PromoSelectPool {
        names: Vec<String>,
        activations: i32,
        gb_amount: i32,
        count: i32,
        expiration_date: Option<String>,
    },
    DeletePromos {
        page: usize,
    },
    DevelopmentBlog {
        page: usize,
    },
    SourceLinks {
        page: usize,
        search_query: Option<String>,
    },
    CreateSourceLink,
    CreateSourceLinkWithCode {
        ref_code: String,
    },
    Broadcast {
        users_without_purchases: bool,
    },
    BroadcastConfirm {
        users_without_purchases: bool,
        broadcast_text: String,
    },
    AddTrafficReceiveAmount {
        user_id: String,
        pool: PoolType,
    },
    ReceiveReferralPercent,
    ReceiveUserReferralPercent {
        user_id: String,
    },
    PaymentSystemsMenu,
    PaymentSystemEditDescription {
        name: String,
    },
    WaitingForTranslation {
        hash: String,
        target_language: Language,
        original_content: String,
        original_language: Language,
    },
    ConfirmTranslation {
        hash: String,
        target_language: Language,
        original_content: String,
        original_language: Language,
        translation_text: String,
    },
    WithdrawRequests {
        page: usize,
        search_query: Option<String>,
    },
    ReceiveTrafficAmount {
        user_id: String,
    },
}

#[derive(Clone, Default, Serialize, Deserialize)]
pub struct SupportState {
    pub kind: SupportStateKind,
}

pub trait MasterDialogueExt {
    fn clear(&self) 
      -> impl std::future::Future<Output = Result<(), HandlerError>> + Send;
    fn update_kind(&self, kind: MasterStateKind) 
      -> impl std::future::Future<Output = Result<(), HandlerError>> + Send;
    fn update_admin_kind(&self, kind: AdminStateKind) 
      -> impl std::future::Future<Output = Result<(), HandlerError>> + Send;
    fn update_support_kind(&self, kind: SupportStateKind) 
      -> impl std::future::Future<Output = Result<(), HandlerError>> + Send;
    fn get_or_fetch_profile(&self, tg_id: i64) 
      -> impl std::future::Future<Output = Result<Profile, HandlerError>> + Send;
    fn reset_admin_to(&self, kind: AdminStateKind) 
      -> impl std::future::Future<Output = Result<(), HandlerError>> + Send;
}

impl MasterDialogueExt for MasterDialogue {
    async fn clear(&self) -> Result<(), HandlerError> {
        self.update(MasterState::default()).await
    }

    async fn update_kind(&self, kind: MasterStateKind) -> Result<(), HandlerError> {
        let current_state = self.get().await?;
        let new_state = match current_state {
            Some(state) => state.update_kind(kind),
            None => {
                log::warn!("No dialogue state, updating kind to {:?}", kind);
                MasterState::default().update_kind(kind)
            }
        };
        self.update(new_state).await
    }

    async fn update_support_kind(&self, kind: SupportStateKind) -> Result<(), HandlerError> {
        let current_state = self.get().await?;
        let new_state = match current_state {
            Some(state) => state.update_support_kind(kind),
            None => {
                log::warn!("No dialogue state, updating kind to {:?}", kind);
                MasterState::default().update_support_kind(kind)
            }
        };
        self.update(new_state).await
    }
    
    async fn update_admin_kind(&self, kind: AdminStateKind) -> Result<(), HandlerError> {
        let current_state = self.get().await?;
        let new_state = match current_state {
            Some(state) => state.update_admin_kind(kind),
            None => {
                log::warn!("No dialogue state, updating kind to {:?}", kind);
                MasterState::default().update_admin_kind(kind)
            }
        };
        self.update(new_state).await
    }

    /// Helper function to get profile from cache or fetch if needed
    async fn get_or_fetch_profile(&self, tg_id: i64) -> Result<Profile, HandlerError> {
        if let Ok(Some(mut state)) = self.get().await {
            log::trace!("{}", format!("Found dialogue state for tg_id: {}, has_cache: {}", tg_id, state.cached_profile.is_some()).bright_black());
            if let Some(fresh_profile) = state.get_fresh_profile() {
                log::trace!("Using cached profile for tg_id: {}", tg_id);
                return Ok(fresh_profile.clone());
            }
            
            log::trace!("Profile cache {expired} or missing for tg_id: {id}, fetching...", expired = "expired".red(), id = tg_id);
            let new_profile = api::get_profile(tg_id).await?;
            
            state.update_profile(new_profile.clone());
            self.update(state).await?;
            
            Ok(new_profile)
        } else {
            log::warn!("No dialogue state exists for tg_id: {}, fetching profile...", tg_id);
            let new_profile = api::get_profile(tg_id).await?;
            
            let new_state = MasterState::with_profile(UserStateKind::Start, new_profile.clone());
            self.update(new_state).await?;
            
            Ok(new_profile)
        }
    }
    
    async fn reset_admin_to(&self, kind: AdminStateKind) -> Result<(), HandlerError> {
        self.update(MasterState::default().update_admin_kind(kind)).await
    }
}

#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub enum UserStateKind {
    #[default]
    Start,
    ProxyManagement {
        selected_pool: PoolType,
    },
    WhitelistManagement {
        selected_pool: PoolType,
    },
    ReceiveIPToAdd {
        selected_pool: PoolType,
    },
    ReceiveIPToRemove {
        selected_pool: PoolType,
    },
    ReceiveRotationInterval {
        selected_pool: PoolType,
    },
    ReceiveAccountPassword,
    ReceiveMergeAccountEmail,
    ReceiveMergeAccountPassword {
        email_or_login: String,
    },
    ReceiveCaptcha {
        start: String,
        captcha_text: String,
    },
    ReceiveLanguage {
        referral: Option<String>,
    },
    UserMenu,
    ReceiveTrafficBuyPool,
    ReceiveTrafficBuyAmount {
        bot_msg_id: MessageId,
        pool_type: PoolType,
    },
    ReceivePromo,
    ReceiveEmail,
    ReceiveWithdrawAmount,
    ReceiveWithdrawComment { amount: f64 },
    ConfirmWithdraw { amount: f64, comment: String },
    Profile,
    ProxyGeneration,
    Purchase,
}

#[derive(Clone, Debug, Serialize, Deserialize, Default)]
pub enum MasterStateKind {
    #[default]
    User,
    Admin,
    Support,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CachedProfile {
    pub profile: Profile,
    pub cached_at: std::time::SystemTime,
    pub cache_ttl: std::time::Duration,
}

impl CachedProfile {
    pub fn new(profile: Profile) -> Self {
        let now = std::time::SystemTime::now();
        log::trace!("{}", format!("Creating new cached profile at {:?} with TTL 300s", now).bright_black());
        Self {
            profile,
            cached_at: std::time::SystemTime::now(),
            cache_ttl: std::time::Duration::from_secs(300), // 5 minutes default
        }
    }
    
    pub fn new_with_ttl(profile: Profile, ttl: std::time::Duration) -> Self {
        Self {
            profile,
            cached_at: std::time::SystemTime::now(),
            cache_ttl: ttl,
        }
    }
    
    pub fn is_expired(&self) -> bool {
        let elapsed = self.cached_at.elapsed().unwrap_or_default();
        let is_expired = elapsed > self.cache_ttl;
        log::trace!("{}", format!("Cache check: elapsed={:?}, ttl={:?}, expired={}", elapsed, self.cache_ttl, is_expired).bright_black());
        is_expired
    }
    
    pub fn force_expire(&mut self) {
        log::trace!("{}", "Force expiring profile cache".bright_black());
        self.cached_at = std::time::SystemTime::UNIX_EPOCH;
    }
    
    pub fn refresh(&mut self) {
        self.cached_at = std::time::SystemTime::now();
    }
    
    pub fn update_profile(&mut self, new_profile: Profile) {
        self.profile = new_profile;
        self.refresh();
    }
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(default)]
#[derive(Default)]
pub struct MasterState {
    pub kind: MasterStateKind,
    pub user_kind: UserStateKind,
    pub admin_kind: AdminStateKind,
    pub support_kind: SupportStateKind,
    pub cached_profile: Option<CachedProfile>,
    pub cached_default_settings: Option<DefaultProxySettingsState>,
}
impl MasterState {
    pub fn new(user_kind: UserStateKind) -> Self {
        Self {
            kind: MasterStateKind::User,
            user_kind,
            admin_kind: AdminStateKind::default(),
            support_kind: SupportStateKind::default(),
            cached_profile: None,
            cached_default_settings: None,
        }
    }

    pub fn update_kind(mut self, kind: MasterStateKind) -> Self {
        self.kind = kind;
        self
    }

    
    pub fn with_profile(user_kind: UserStateKind, profile: Profile) -> Self {
        Self {
            kind: MasterStateKind::User,
            user_kind,
            admin_kind: AdminStateKind::default(),
            support_kind: SupportStateKind::default(),
            cached_profile: Some(CachedProfile::new(profile)),
            cached_default_settings: None,
        }
    }
    
    pub fn set_cached_profile(&mut self, profile: Profile) {
        self.cached_profile = Some(CachedProfile::new(profile));
    }
    
    pub fn get_cached_profile(&self) -> Option<&Profile> {
        self.cached_profile.as_ref().map(|cp| &cp.profile)
    }
    
    pub fn get_fresh_profile(&self) -> Option<&Profile> {
        self.cached_profile.as_ref().and_then(|cp| {
            if cp.is_expired() {
                log::trace!("{}", "Profile cache is expired, returning None".bright_black());
                None
            } else {
                log::trace!("{}", "Profile cache is fresh, returning cached profile".bright_black());
                Some(&cp.profile)
            }
        })
    }
    
    pub fn invalidate_profile_cache(&mut self) {
        if let Some(ref mut cached) = self.cached_profile {
            cached.force_expire();
        }
    }
    
    pub fn update_profile(&mut self, new_profile: Profile) {
        match &mut self.cached_profile {
            Some(cached) => cached.update_profile(new_profile),
            None => self.cached_profile = Some(CachedProfile::new(new_profile)),
        }
    }
    
    pub fn needs_profile_refresh(&self) -> bool {
        match &self.cached_profile {
            Some(cached) => cached.is_expired(),
            None => true,
        }
    }
    
    pub fn get_cached_default_settings(&self) -> Option<&DefaultProxySettings> {
        self.cached_default_settings
            .as_ref()
            .and_then(|state| if state.is_fresh() { Some(&state.settings) } else { None })
    }
    
    pub fn update_default_settings(&mut self, settings: DefaultProxySettings) {
        if let Some(ref mut cached_settings) = self.cached_default_settings {
            let cached_countries = cached_settings.cached_countries.clone();
            cached_settings.settings = settings;
            cached_settings.last_fetched = std::time::SystemTime::now();
            cached_settings.cached_countries = cached_countries;
        } else {
            self.cached_default_settings = Some(DefaultProxySettingsState::new(settings));
        }
    }
    
    pub fn get_cached_countries(&self) -> Option<&Vec<Country>> {
        self.cached_default_settings.as_ref()?.get_cached_countries()
    }
    
    pub fn set_cached_countries(&mut self, countries: Vec<Country>) {
        if let Some(ref mut cached_settings) = self.cached_default_settings {
            cached_settings.set_cached_countries(countries);
        } else {
            let mut new_state = DefaultProxySettingsState::default();
            new_state.set_cached_countries(countries);
            self.cached_default_settings = Some(new_state);
        }
    }
    
    pub fn get_cached_cities(&self) -> Option<&Vec<City>> {
        self.cached_default_settings.as_ref()?.get_cached_cities()
    }
    
    pub fn set_cached_cities(&mut self, cities: Vec<City>) {
        if let Some(ref mut cached_settings) = self.cached_default_settings {
            cached_settings.set_cached_cities(cities);
        } else {
            let mut new_state = DefaultProxySettingsState::default();
            new_state.set_cached_cities(cities);
            self.cached_default_settings = Some(new_state);
        }
    }
    
    pub fn get_cached_states(&self) -> Option<&Vec<State>> {
        self.cached_default_settings.as_ref()?.get_cached_states()
    }
    
    pub fn set_cached_states(&mut self, states: Vec<State>) {
        if let Some(ref mut cached_settings) = self.cached_default_settings {
            cached_settings.set_cached_states(states);
        } else {
            let mut new_state = DefaultProxySettingsState::default();
            new_state.set_cached_states(states);
            self.cached_default_settings = Some(new_state);
        }
    }
    
    pub fn get_cached_zip_codes(&self) -> Option<&Vec<ZipCode>> {
        self.cached_default_settings.as_ref()?.get_cached_zip_codes()
    }
    
    pub fn set_cached_zip_codes(&mut self, zip_codes: Vec<ZipCode>) {
        if let Some(ref mut cached_settings) = self.cached_default_settings {
            cached_settings.set_cached_zip_codes(zip_codes);
        } else {
            let mut new_state = DefaultProxySettingsState::default();
            new_state.set_cached_zip_codes(zip_codes);
            self.cached_default_settings = Some(new_state);
        }
    }
    
    pub fn get_cached_asns(&self) -> Option<&Vec<ASN>> {
        self.cached_default_settings.as_ref()?.get_cached_asns()
    }
    
    pub fn set_cached_asns(&mut self, asns: Vec<ASN>) {
        if let Some(ref mut cached_settings) = self.cached_default_settings {
            cached_settings.set_cached_asns(asns);
        } else {
            let mut new_state = DefaultProxySettingsState::default();
            new_state.set_cached_asns(asns);
            self.cached_default_settings = Some(new_state);
        }
    }

    pub fn update_admin_kind(mut self, kind: AdminStateKind) -> Self {
        self.admin_kind = kind;
        self
    }

    pub fn update_support_kind(mut self, kind: SupportStateKind) -> Self {
        self.support_kind = kind;
        self
    }
}

#[derive(Clone, Default, Serialize, Deserialize)]
pub struct AdminState {
    pub kind: AdminStateKind,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum ProxySearcheble {
    ChoseCountry,
    ChoseState,
    ChoseCity,
    ChoseZip,
    ChoseAsn,
    ChoseExcludeAsn,
}

#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub enum ProxySelectionStep {
    #[default]
    MainMenu,
    ChoseSearchable {
        query: String,
        step: ProxySearcheble,
    },
    ReceiveAmount,
    ReceiveInterval,
}

#[derive(Clone, Serialize, Deserialize)]
pub struct GetProxyState {
    countries: Vec<SelectableLocation>,
    states: Vec<SelectableLocation>,
    cities: Vec<SelectableLocation>,
    included_asns: Vec<SelectableLocation>,
    excluded_asns: Vec<SelectableLocation>,
    zip_codes: Vec<SelectableLocation>,
    page: usize,
    amount: u32,
    protocol: ProxyProtocol,
    r#type: ProxyType,
    anonymity: bool,
    region: ProxyRegion,
    format: ProxyFormat,
}

impl Default for GetProxyState {
    fn default() -> Self {
        GetProxyState {
            countries: Vec::new(),
            states: Vec::new(),
            cities: Vec::new(),
            included_asns: Vec::new(),
            excluded_asns: Vec::new(),
            zip_codes: Vec::new(),
            page: 0,
            amount: 1,
            protocol: ProxyProtocol::default(),
            r#type: ProxyType::default(),
            anonymity: false,
            region: ProxyRegion::default(),
            format: ProxyFormat::default(),
        }
    }
}

impl GetProxyState {
    pub fn selected_countries(&self) -> Vec<Country> {
        SelectableLocation::extract_selected(&self.countries)
    }

    pub fn selected_states(&self) -> Vec<State> {
        SelectableLocation::extract_selected(&self.states)
    }

    pub fn selected_cities(&self) -> Vec<City> {
        SelectableLocation::extract_selected(&self.cities)
    }

    pub fn selected_included_asns(&self) -> Vec<ASN> {
        SelectableLocation::extract_selected(&self.included_asns)
    }

    pub fn selected_excluded_asns(&self) -> Vec<ASN> {
        SelectableLocation::extract_selected(&self.excluded_asns)
    }

    pub fn selected_zip_codes(&self) -> Vec<ZipCode> {
        SelectableLocation::extract_selected(&self.zip_codes)
    }
}

impl std::fmt::Debug for GetProxyState {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("GetProxyState")
            .field("page", &self.page)
            .field("countries", &"<skipped>")
            .finish()
    }
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(default)]
pub struct GetProxyStates {
    current_message: Option<Message>,
    message_to_remove: Vec<Message>,
    pub pool: Option<PoolType>,
    storage: HashMap<PoolType, GetProxyState>,
    step: ProxySelectionStep,
}

impl Default for GetProxyStates {
    fn default() -> Self {
        GetProxyStates::new(None, None)
    }
}

impl GetProxyStates {
    pub fn new(
        pool: Option<PoolType>,
        current_message: Option<Message>,
    ) -> Self {
        let mut map = HashMap::default();
        for pool in PoolType::iter() {
            map.insert(pool, GetProxyState::default());
        }
        GetProxyStates {
            message_to_remove: Vec::new(),
            current_message,
            pool,
            storage: map,
            step: ProxySelectionStep::default(),
        }
    }

    pub fn storage(&self, pool: PoolType) -> &GetProxyState {
        self.storage.get(&pool).unwrap()
    }

    pub fn storage_mut(&mut self, pool: PoolType) -> &mut GetProxyState {
        self.storage.get_mut(&pool).unwrap()
    }

    pub fn step(&self) -> &ProxySelectionStep {
        &self.step
    }

    pub fn set_step(&mut self, step: ProxySelectionStep) {
        if let ProxySelectionStep::ChoseSearchable { ref mut query, .. } =
            &mut self.step
        {
            *query = "".to_string();
        }
        self.step = step;
    }

    pub fn clear_search(&mut self) {
        if let ProxySelectionStep::ChoseSearchable { ref mut query, .. } =
            &mut self.step
        {
            *query = "".to_string();
        } else {
            log::warn!("clear_search called when not in ChoseSearchable step");
        }
    }

    pub fn search_query(&self) -> &str {
        if let ProxySelectionStep::ChoseSearchable { ref query, .. } =
            &self.step
        {
            query
        } else {
            log::error!(
                "get_search_query called when not in ChoseSearchable step"
            );
            ""
        }
    }

    pub fn get_search_query_mut(&mut self) -> Option<&mut String> {
        if let ProxySelectionStep::ChoseSearchable { ref mut query, .. } =
            &mut self.step
        {
            Some(query)
        } else {
            None
        }
    }

    pub fn set_search_query(&mut self, new_query: impl Into<String>) {
        if let ProxySelectionStep::ChoseSearchable { ref mut query, .. } =
            &mut self.step
        {
            *query = new_query.into().trim().to_lowercase();
        } else {
            log::error!(
                "set_search_query called when not in ChoseSearchable step"
            );
        }
    }

    pub async fn garbage_collect(&mut self, bot: &Bot) -> HandlerResult {
        for message in self.message_to_remove.drain(..) {
            bot.delete_message(message.chat.id, message.id).await?;
        }
        Ok(())
    }

    pub fn promise_to_remove(&mut self, message: Message) {
        self.message_to_remove.push(message);
    }
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum Callback {
    #[serde(rename = "u0")]
    Noop,
    #[serde(rename = "u1")]
    ActivatePromo,
    #[serde(rename = "u2")]
    Referral,
    #[serde(rename = "u3")]
    BackFromBuying,
    #[serde(rename = "u4")]
    DeleteThisMessage,
    #[serde(rename = "u6")]
    Api,
    #[serde(rename = "u7")]
    WebsiteLogin,
    #[serde(rename = "u8")]
    ChangeLanguage,
    #[serde(rename = "u10")]
    BindEmail,
    #[serde(rename = "u11")]
    BuyTraffic { pool: Option<PoolType> },
    #[serde(rename = "u12")]
    Online {
        pool: PoolType,
    },
    #[serde(rename = "u14")]
    BackFromOnline,
    #[serde(rename = "u15")]
    BackToProfile,
    #[serde(rename = "u16")]
    WithdrawRequest,
    #[serde(rename = "u17")]
    ConfirmWithdraw {
        amount: f64,
    },
    #[serde(rename = "u18")]
    CaptchaAnswer {
        value: String,
    },
    #[serde(rename = "u19")]
    SelectLanguage {
        language: Language,
    },
    #[serde(rename = "u20")]
    ManageProxy,
    #[serde(rename = "u21")]
    SelectPoolForManagement { pool: PoolType },
    #[serde(rename = "u22")]
    ChangePasswordForPool { pool: PoolType },
    #[serde(rename = "u23")]
    CheckStatsForPool { pool: PoolType },
    #[serde(rename = "u24")]
    CheckStatsWeekForPool { pool: PoolType },
    #[serde(rename = "u25")]
    CheckStatsMonthForPool { pool: PoolType },
    #[serde(rename = "u26")]
    BackToStatsMenu { pool: PoolType },
    #[serde(rename = "u27")]
    ManageWhitelistForPool { pool: PoolType },
    #[serde(rename = "u28")]
    AddIPToWhitelist { pool: PoolType },
    #[serde(rename = "u29")]
    RemoveIPFromWhitelist { pool: PoolType },
    #[serde(rename = "u30")]
    ViewWhitelistForPool { pool: PoolType },
    #[serde(rename = "u31")]
    BackToWhitelistMenu { pool: PoolType },
    #[serde(rename = "u32")]
    ManageDefaultProxySettings { pool: PoolType },
    #[serde(rename = "u33")]
    SetAccountPassword,
    #[serde(rename = "u34")]
    AdvancedFeatures,
    #[serde(rename = "u35")]
    ResellerProfile,
    #[serde(rename = "u36")]
    MergeAccount,
    #[serde(rename = "update_profile")]
    UpdateProfile,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum GetProxyCallback {
    #[serde(rename = "p0")]
    GetProxy { pool: PoolType },
    #[serde(rename = "p1")]
    ProxySelectCountries,
    #[serde(rename = "p2")]
    ProxySelectRegion,
    #[serde(rename = "p3")]
    ProxySetRegion { region: ProxyRegion },
    #[serde(rename = "p4")]
    ProxySelectLocations,
    #[serde(rename = "p5")]
    ProxySelectStates,
    #[serde(rename = "p6")]
    ProxySelectCities,
    #[serde(rename = "p7")]
    ProxySelectASNs,
    #[serde(rename = "p9")]
    ProxySelectExcludedASNs,
    #[serde(rename = "p10")]
    ProxySelectZipCodes,
    #[serde(rename = "p11")]
    ProxySetProtocol { protocol: ProxyProtocol },
    #[serde(rename = "p12")]
    ProxySetType { proxy_type: ProxyType },
    #[serde(rename = "p13")]
    ProxySetAnonymity { enabled: bool },
    #[serde(rename = "p14")]
    ProxySetFormat,
    #[serde(rename = "p15")]
    ProxySetAmount,
    #[serde(rename = "p16")]
    ProxySetInterval,
    #[serde(rename = "p17")]
    ProxySetASN,
    #[serde(rename = "p18")]
    ProxyFormatSelect { format: ProxyFormat },
    #[serde(rename = "p19")]
    ProxyCountryPage { page: usize },
    #[serde(rename = "p20")]
    ProxyCountrySelect { index: usize },
    #[serde(rename = "p21")]
    ProxySelectAll,
    #[serde(rename = "p22")]
    ProxyClearSearch,
    #[serde(rename = "p23")]
    ProxyCountryConfirm,
    #[serde(rename = "p24")]
    ProxyStatePage { page: usize },
    #[serde(rename = "p25")]
    ProxyStateSelect { index: usize },
    #[serde(rename = "p26")]
    ProxySelectAllStates,
    #[serde(rename = "p27")]
    ProxyStateClearSearch,
    #[serde(rename = "p28")]
    ProxyStateConfirm,
    #[serde(rename = "p29")]
    ProxyCityPage {
        page: usize,
    },
    #[serde(rename = "p30")]
    ProxyCitySelect {
        index: usize,
    },
    #[serde(rename = "p31")]
    ProxySelectAllCities,
    #[serde(rename = "p32")]
    ProxyCityClearSearch,
    #[serde(rename = "p33")]
    ProxyCityConfirm,
    #[serde(rename = "p34")]
    ProxyASNPage {
        page: usize,
    },
    #[serde(rename = "p35")]
    ProxyASNSelect {
        index: usize,
    },
    #[serde(rename = "p36")]
    ProxySelectAllASNs,
    #[serde(rename = "p37")]
    ProxyASNClearSearch,
    #[serde(rename = "p38")]
    ProxyASNConfirm,
    #[serde(rename = "p39")]
    ProxyExcludedASNPage {
        page: usize,
    },
    #[serde(rename = "p40")]
    ProxyExcludedASNSelect {
        index: usize,
    },
    #[serde(rename = "p41")]
    ProxySelectAllExcludedASNs,
    #[serde(rename = "p42")]
    ProxyExcludedASNClearSearch,
    #[serde(rename = "p43")]
    ProxyExcludedASNConfirm,
    #[serde(rename = "p44")]
    ProxyZipCodePage {
        page: usize,
    },
    #[serde(rename = "p45")]
    ProxyZipCodeSelect {
        index: usize,
    },
    #[serde(rename = "p46")]
    ProxySelectAllZipCodes,
    #[serde(rename = "p47")]
    ProxyZipCodeClearSearch,
    #[serde(rename = "p48")]
    ProxyZipCodeConfirm,
    #[serde(rename = "p49")]
    ProxyGenerate,
    #[serde(rename = "p50")]
    ProxyBack,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum PurchaseCallback {
    #[serde(rename = "b0")]
    SelectPool { pool: PoolType },
    #[serde(rename = "b1")]
    SelectPaymentMethod { index: u8 },
    #[serde(rename = "b2")]
    EnterPromocode,
    #[serde(rename = "b3")]
    PayWithReferral,
    #[serde(rename = "b4")]
    Back,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum DefaultProxySettingsCallback {
    #[serde(rename = "S0")]
    ManageDefaultSettings { pool: PoolType },
    #[serde(rename = "S1")]
    AdvancedLocationSettings { pool: PoolType },
    #[serde(rename = "S2")]
    EditCountries { pool: PoolType },
    #[serde(rename = "S3")]
    EditCities { pool: PoolType },
    #[serde(rename = "S4")]
    EditStates { pool: PoolType },
    #[serde(rename = "S5")]
    EditZipCodes { pool: PoolType },
    #[serde(rename = "S6")]
    EditASNs { pool: PoolType },
    #[serde(rename = "S7")]
    EditExcludeCountries { pool: PoolType },
    #[serde(rename = "S8")]
    EditExcludeASNs { pool: PoolType },
    #[serde(rename = "S9")]
    ToggleAnonymousFilter { pool: PoolType },
    #[serde(rename = "S10")]
    EditRotationInterval { pool: PoolType },
    #[serde(rename = "S11")]
    ResetToDefaults { pool: PoolType },
    #[serde(rename = "S12")]
    SaveSettings { pool: PoolType },
    #[serde(rename = "S13")]
    BackToAdvancedLocation { pool: PoolType },
    #[serde(rename = "S14")]
    Back { pool: PoolType },
    #[serde(rename = "S15")]
    CountryPage { pool: PoolType, page: usize },
    #[serde(rename = "S16")]
    CountrySelect { pool: PoolType, index: usize },
    #[serde(rename = "S17")]
    CityPage { pool: PoolType, page: usize },
    #[serde(rename = "S18")]
    CitySelect { pool: PoolType, index: usize },
    #[serde(rename = "S19")]
    StatePage { pool: PoolType, page: usize },
    #[serde(rename = "S20")]
    StateSelect { pool: PoolType, index: usize },
    #[serde(rename = "S21")]
    ZipCodePage { pool: PoolType, page: usize },
    #[serde(rename = "S22")]
    ZipCodeSelect { pool: PoolType, index: usize },
    #[serde(rename = "S23")]
    ASNPage { pool: PoolType, page: usize },
    #[serde(rename = "S24")]
    ASNSelect { pool: PoolType, index: usize },
}

impl Display for Callback {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        write!(f, "{}", serde_json::to_string(self).unwrap())
    }
}

impl Display for GetProxyCallback {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        write!(f, "{}", serde_json::to_string(self).unwrap())
    }
}

impl Display for PurchaseCallback {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        write!(f, "{}", serde_json::to_string(self).unwrap())
    }
}

impl Display for DefaultProxySettingsCallback {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        write!(f, "{}", serde_json::to_string(self).unwrap())
    }
}

pub async fn error_handler(
    bot: Bot,
    dialogue: MasterDialogue,
    msg: Message,
    settings: HandlerSettings,
) -> HandlerResult {
    error_handler_with_message(bot, dialogue, msg, settings, t8x!(Ru, "Что-то пошло не так", &settings.language)).await
}

pub async fn error_handler_with_message(
    bot: Bot,
    dialogue: MasterDialogue,
    msg: Message,
    settings: HandlerSettings,
    message: &str,
) -> HandlerResult {
    let ln = settings.language;

    bot.delete_message(msg.chat.id, msg.id).await?;
    bot.send_message(msg.chat.id, message)
        .reply_markup(user::make_menu_keyboard(ln))
        .await?;
    crate::ui::user::update_state_preserving_cache(&dialogue, UserStateKind::Start).await?;
    Ok(())
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum PurchaseStep {
    SelectPool,
    EnterAmount,
    SelectPaymentMethod,
    EnterPromocode,
    ConfirmPayment,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(default)]
pub struct PurchaseState {
    pub pool: PoolType,
    pub amount: Option<u32>,
    pub promocode: Option<String>,
    pub payment_method: Option<String>,
    pub bot_msg_id: Option<MessageId>,
    pub step: PurchaseStep,
}

impl Default for PurchaseState {
    fn default() -> Self {
        PurchaseState {
            pool: PoolType::Residential,
            amount: None,
            promocode: None,
            payment_method: None,
            bot_msg_id: None,
            step: PurchaseStep::SelectPool,
        }
    }
}

impl PurchaseState {
    pub fn new(pool: PoolType) -> Self {
        PurchaseState {
            pool,
            amount: None,
            promocode: None,
            payment_method: None,
            bot_msg_id: None,
            step: PurchaseStep::EnterAmount,
        }
    }

    pub fn with_message(pool: PoolType, bot_msg_id: MessageId) -> Self {
        PurchaseState {
            pool,
            amount: None,
            promocode: None,
            payment_method: None,
            bot_msg_id: Some(bot_msg_id),
            step: PurchaseStep::EnterAmount,
        }
    }

    pub fn set_amount(&mut self, amount: u32) {
        self.amount = Some(amount);
        self.step = PurchaseStep::SelectPaymentMethod;
    }

    pub fn set_payment_method(&mut self, payment_method: String) {
        self.payment_method = Some(payment_method);
    }

    pub fn set_promocode(&mut self, promocode: Option<String>) {
        self.promocode = promocode;
        self.step = PurchaseStep::SelectPaymentMethod;
    }

    pub fn can_proceed(&self) -> bool {
        self.amount.is_some() && self.payment_method.is_some()
    }
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DefaultProxySettingsState {
    pub settings: DefaultProxySettings,
    pub last_fetched: std::time::SystemTime,
    pub cached_countries: Option<Vec<Country>>,
    pub cached_cities: Option<Vec<City>>,
    pub cached_states: Option<Vec<State>>,
    pub cached_zip_codes: Option<Vec<ZipCode>>,
    pub cached_asns: Option<Vec<ASN>>,
}

impl DefaultProxySettingsState {
    pub fn new(settings: DefaultProxySettings) -> Self {
        Self {
            settings,
            last_fetched: std::time::SystemTime::now(),
            cached_countries: None,
            cached_cities: None,
            cached_states: None,
            cached_zip_codes: None,
            cached_asns: None,
        }
    }
    
    pub fn is_fresh(&self) -> bool {
        let cache_duration = std::time::Duration::from_secs(300);
        self.last_fetched.elapsed().unwrap_or(cache_duration) < cache_duration
    }
}

impl Default for DefaultProxySettingsState {
    fn default() -> Self {
        Self::new(DefaultProxySettings::default())
    }
}

impl DefaultProxySettingsState {
    pub fn get_cached_countries(&self) -> Option<&Vec<Country>> {
        self.cached_countries.as_ref()
    }
    
    pub fn set_cached_countries(&mut self, countries: Vec<Country>) {
        self.cached_countries = Some(countries);
    }
    
    pub fn get_cached_cities(&self) -> Option<&Vec<City>> {
        self.cached_cities.as_ref()
    }
    
    pub fn set_cached_cities(&mut self, cities: Vec<City>) {
        self.cached_cities = Some(cities);
    }
    
    pub fn get_cached_states(&self) -> Option<&Vec<State>> {
        self.cached_states.as_ref()
    }
    
    pub fn set_cached_states(&mut self, states: Vec<State>) {
        self.cached_states = Some(states);
    }
    
    pub fn get_cached_zip_codes(&self) -> Option<&Vec<ZipCode>> {
        self.cached_zip_codes.as_ref()
    }
    
    pub fn set_cached_zip_codes(&mut self, zip_codes: Vec<ZipCode>) {
        self.cached_zip_codes = Some(zip_codes);
    }
    
    pub fn get_cached_asns(&self) -> Option<&Vec<ASN>> {
        self.cached_asns.as_ref()
    }
    
    pub fn set_cached_asns(&mut self, asns: Vec<ASN>) {
        self.cached_asns = Some(asns);
    }
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum AdminCallback {
    #[serde(rename = "a0")]
    AdminMenu,
    #[serde(rename = "a1")]
    StatsToday,
    #[serde(rename = "a2")]
    StatsByDate,
    #[serde(rename = "a4")]
    PromoSelectPool { pool: PoolType },
    #[serde(rename = "a5")]
    DeletePromos,
    #[serde(rename = "a6")]
    DeletePromosPage { page: usize },
    #[serde(rename = "a7")]
    DeletePromo { promo_name: String },
    #[serde(rename = "a8")]
    ConfirmDeletePromo { promo_name: String },
    #[serde(rename = "a9")]
    ChangeReferralPercent,
    #[serde(rename = "a10")]
    AddTraffic { pool: Option<PoolType> },
    #[serde(rename = "a11")]
    PromoMenu,
    #[serde(rename = "a12")]
    BannerMenu,
    #[serde(rename = "a13")]
    PreferencesMenu,
    #[serde(rename = "a14")]
    UserWeeklyStats { pool: PoolType },
    #[serde(rename = "a15")]
    UserMonthlyStats { pool: PoolType },
    #[serde(rename = "a16")]
    UserWeeklyStatsPoolChoice { pools: Vec<PoolTypeWithOptimazedSerialization> },
    #[serde(rename = "a17")]
    UserMonthlyStatsPoolChoice { pools: Vec<PoolTypeWithOptimazedSerialization> },
    #[serde(rename = "a18")]
    DevelopmentBlogPage { page: usize },
    #[serde(rename = "a19")]
    ChangePricePool { pool: PoolType },
    #[serde(rename = "a20")]
    DropBalance { pool: Option<PoolType> },
    #[serde(rename = "a21")]
    ConfirmDropBalance { user_id: String, pool: PoolType },
    #[serde(rename = "a22")]
    SourceLinksMenu,
    #[serde(rename = "a23")]
    SourceLinksPage { page: usize },
    #[serde(rename = "a24")]
    SourceLinksClearSearch,
    #[serde(rename = "a25")]
    SourceLinkDetails { link_value: String },
    #[serde(rename = "a26")]
    SourceLinkExportJson { link_value: String },
    #[serde(rename = "a27")]
    SourceLinkExportCsv { link_value: String },
    #[serde(rename = "a28")]
    SourceLinksCreate,
    #[serde(rename = "a29")]
    BroadcastMenu,
    #[serde(rename = "a30")]
    BroadcastToAll,
    #[serde(rename = "a31")]
    BroadcastToWithoutPurchases,
    #[serde(rename = "a32")]
    BroadcastConfirm { users_without_purchases: bool },
    #[serde(rename = "a33")]
    BroadcastTest { users_without_purchases: bool },
    #[serde(rename = "a34")]
    PaymentSystemsMenu,
    #[serde(rename = "a35")]
    PaymentSystemToggle { name: String },
    #[serde(rename = "a36")]
    PaymentSystemEditDescription { name: String },
    #[serde(rename = "a37")]
    PaymentSystemConfirmToggle { name: String, enabled: bool },
    #[serde(rename = "a38")]
    ShowUserProfile { user_id: String },
    #[serde(rename = "a39")]
    ConfirmTranslation,
    #[serde(rename = "a40")]
    CancelTranslation,
    #[serde(rename = "a41")]
    WithdrawRequestsMenu,
    #[serde(rename = "a42")]
    WithdrawRequestsPage { page: usize },
    #[serde(rename = "a43")]
    WithdrawRequestDetails { request_id: i64 },
    #[serde(rename = "a44")]
    WithdrawRequestApprove { request_id: i64 },
    #[serde(rename = "a45")]
    WithdrawRequestReject { request_id: i64 },
    #[serde(rename = "a46")]
    WithdrawRequestCancel { request_id: i64 },
    #[serde(rename = "a47")]
    WithdrawRequestForce { request_id: i64 },
    #[serde(rename = "a48")]
    ChangeResellerTraffic { user_id: String },
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum SupportCallback {
    #[serde(rename = "s0")]
    AdminMenu,
    #[serde(rename = "s4")]
    PromoSelectPool { pool: PoolType },
    #[serde(rename = "s5")]
    DeletePromos,
    #[serde(rename = "s6")]
    DeletePromosPage { page: usize },
    #[serde(rename = "s7")]
    DeletePromo { promo_name: String },
    #[serde(rename = "s8")]
    ConfirmDeletePromo { promo_name: String },
    #[serde(rename = "s10")]
    AddTraffic { user_id: String, pool: Option<PoolType> },
    #[serde(rename = "s11")]
    PromoMenu,
    #[serde(rename = "s14")]
    UserWeeklyStats { user_id: String, pool: PoolType },
    #[serde(rename = "s15")]
    UserMonthlyStats { user_id: String, pool: PoolType },
    #[serde(rename = "s16")]
    UserWeeklyStatsPoolChoice { user_id: String, pools: Vec<PoolTypeWithOptimazedSerialization> },
    #[serde(rename = "s17")]
    UserMonthlyStatsPoolChoice { user_id: String, pools: Vec<PoolTypeWithOptimazedSerialization> },
    #[serde(rename = "s19")]
    ChangePricePool { pool: PoolType },
    #[serde(rename = "s20")]
    DropBalance { user_id: String, pool: Option<PoolType> },
    #[serde(rename = "s21")]
    ConfirmDropBalance { user_id: String, pool: PoolType },
}
