use std::time::Instant;

use chrono::Utc;
use redis::Client as RedisClient;
use sqlx::PgPool;
use uuid::Uuid;

pub use crate::analytics::{AnalyticsBatchRequest, AnalyticsEventInput, AttributeInput};
pub use crate::bets::{BetIntentRecord, BetIntentRequest, BetIntentResponse};
pub use crate::events::{EventManifestSnapshot, EventSnapshot};
pub use crate::events::{MarketSnapshot, OutcomeSnapshot};
pub use crate::admin::{EventMediaSnapshot, OddsVersionSnapshot, OutcomeOddsSnapshot, SettlementSnapshot};
pub use crate::experiments::ExperimentConfigSnapshot;
pub use crate::localization::LocalizationBundleSnapshot;
pub use crate::sessions::{SessionSnapshot, SessionStartRequest};

use crate::{
    analytics::AnalyticsStore,
    audit::AuditStore,
    bets::{
        BetsStore, ACCEPTANCE_ACCEPTED, ACCEPTANCE_REJECTED_INVALID_MARKET,
        ACCEPTANCE_REJECTED_INVALID_SESSION, ACCEPTANCE_REJECTED_TOO_LATE,
    },
    config::AppConfig,
    error::AppError,
    events::{EventsStore},
    experiments::ExperimentsStore,
    localization::LocalizationStore,
    markets::MarketsStore,
    settlement::SettlementsStore,
    sessions::{SessionDefaults, SessionsStore},
    users::{UserCreateRequest, UsersStore},
};

#[derive(Clone)]
pub struct AppState {
    pub config: AppConfig,
    pub started_at: Instant,
    pub database_pool: Option<PgPool>,
    pub redis_client: Option<RedisClient>,
    events: EventsStore,
    bets: BetsStore,
    settlements: SettlementsStore,
    experiments: ExperimentsStore,
    localization: LocalizationStore,
    analytics: AnalyticsStore,
    audit: AuditStore,
    markets: MarketsStore,
    users: UsersStore,
    sessions: SessionsStore,
}

impl AppState {
    pub fn new(
        config: AppConfig,
        database_pool: Option<PgPool>,
        redis_client: Option<RedisClient>,
    ) -> Self {
        Self {
            config,
            started_at: Instant::now(),
            database_pool,
            redis_client,
            events: EventsStore::new(),
            bets: BetsStore::new(),
            settlements: SettlementsStore::new(),
            experiments: ExperimentsStore::new(),
            localization: LocalizationStore::new(),
            analytics: AnalyticsStore::new(),
            audit: AuditStore::new(),
            markets: MarketsStore::new(),
            users: UsersStore::new(),
            sessions: SessionsStore::new(),
        }
    }

    pub async fn start_session(&self, request: SessionStartRequest) -> SessionSnapshot {
        let user = self
            .users
            .upsert(UserCreateRequest {
                external_ref: request.external_ref.clone(),
                preferred_language: request
                    .locale_code
                    .clone()
                    .unwrap_or_else(|| self.config.default_locale.clone()),
                device_platform: request
                    .device_platform
                    .clone()
                    .unwrap_or_else(|| self.config.default_device_platform.clone()),
            })
            .await;

        let session = self
            .sessions
            .start_session(
                user.id,
                request,
                SessionDefaults {
                    default_locale: self.config.default_locale.clone(),
                    default_device_platform: self.config.default_device_platform.clone(),
                    default_app_version: self.config.app_version.clone(),
                    default_experiment_variant: self.config.default_experiment_variant.clone(),
                },
            )
            .await;

        self.audit
            .record(
                "session_started",
                Some(session.session_id),
                Some(session.user_id),
                Utc::now(),
                [
                    ("locale_code", session.locale_code.clone()),
                    ("device_platform", session.device_platform.clone()),
                    ("app_version", session.app_version.clone()),
                    ("experiment_variant", session.experiment_variant.clone()),
                ],
            )
            .await;

        session
    }

    pub async fn current_session(&self) -> Option<SessionSnapshot> {
        self.sessions.current_session().await
    }

    fn build_bet_intent_record(
        &self,
        request: &BetIntentRequest,
        user_id: Option<Uuid>,
        accepted: bool,
        acceptance_code: &str,
        accepted_odds_version_id: Option<Uuid>,
    ) -> BetIntentRecord {
        BetIntentRecord {
            id: Uuid::new_v4(),
            session_id: request.session_id,
            user_id,
            event_id: request.event_id,
            market_id: request.market_id,
            outcome_id: request.outcome_id,
            idempotency_key: request.idempotency_key.clone(),
            client_sent_at: request.client_sent_at,
            server_received_at: Utc::now(),
            accepted,
            acceptance_code: acceptance_code.to_string(),
            accepted_odds_version_id,
        }
    }

    async fn record_bet_audit(
        &self,
        action: &str,
        session: Option<&SessionSnapshot>,
        request: &BetIntentRequest,
        reason: &str,
        accepted_odds_version_id: Option<Uuid>,
    ) {
        let mut attributes = vec![
            ("reason".to_string(), reason.to_string()),
            ("session_id".to_string(), request.session_id.to_string()),
            ("event_id".to_string(), request.event_id.to_string()),
            ("market_id".to_string(), request.market_id.to_string()),
            ("outcome_id".to_string(), request.outcome_id.to_string()),
        ];

        if let Some(accepted_odds_version_id) = accepted_odds_version_id {
            attributes.push((
                "accepted_odds_version_id".to_string(),
                accepted_odds_version_id.to_string(),
            ));
        }

        self.audit
            .record(
                action,
                session.map(|session| session.session_id),
                session.map(|session| session.user_id),
                Utc::now(),
                attributes,
            )
            .await;
    }

    pub async fn submit_bet_intent(&self, request: BetIntentRequest) -> BetIntentResponse {
        let Some(existing) = self
            .bets
            .get_by_idempotency_key(&request.idempotency_key)
            .await
        else {
            return self.submit_bet_intent_fresh(request).await;
        };

        existing
    }

    async fn submit_bet_intent_fresh(&self, request: BetIntentRequest) -> BetIntentResponse {
        let Some(session) = self.current_session().await else {
            let response = self
                .bets
                .record(self.build_bet_intent_record(
                    &request,
                    None,
                    false,
                    ACCEPTANCE_REJECTED_INVALID_SESSION,
                    None,
                ))
                .await;

            self.record_bet_audit(
                "bet_rejected",
                None,
                &request,
                ACCEPTANCE_REJECTED_INVALID_SESSION,
                None,
            )
            .await;

            return response;
        };

        if session.ended_at.is_some() || session.session_id != request.session_id {
            let response = self
                .bets
                .record(self.build_bet_intent_record(
                    &request,
                    Some(session.user_id),
                    false,
                    ACCEPTANCE_REJECTED_INVALID_SESSION,
                    None,
                ))
                .await;

            self.record_bet_audit(
                "bet_rejected",
                Some(&session),
                &request,
                ACCEPTANCE_REJECTED_INVALID_SESSION,
                None,
            )
            .await;

            return response;
        }

        let Some(market) = self.markets.market(request.market_id).await else {
            let response = self
                .bets
                .record(self.build_bet_intent_record(
                    &request,
                    Some(session.user_id),
                    false,
                    ACCEPTANCE_REJECTED_INVALID_MARKET,
                    None,
                ))
                .await;

            self.record_bet_audit(
                "bet_rejected",
                Some(&session),
                &request,
                ACCEPTANCE_REJECTED_INVALID_MARKET,
                None,
            )
            .await;

            return response;
        };

        if market.event_id != request.event_id {
            let response = self
                .bets
                .record(self.build_bet_intent_record(
                    &request,
                    Some(session.user_id),
                    false,
                    ACCEPTANCE_REJECTED_INVALID_MARKET,
                    None,
                ))
                .await;

            self.record_bet_audit(
                "bet_rejected",
                Some(&session),
                &request,
                ACCEPTANCE_REJECTED_INVALID_MARKET,
                None,
            )
            .await;

            return response;
        }

        let Some(outcome) = self.markets.outcome(request.outcome_id).await else {
            let response = self
                .bets
                .record(self.build_bet_intent_record(
                    &request,
                    Some(session.user_id),
                    false,
                    ACCEPTANCE_REJECTED_INVALID_MARKET,
                    None,
                ))
                .await;

            self.record_bet_audit(
                "bet_rejected",
                Some(&session),
                &request,
                ACCEPTANCE_REJECTED_INVALID_MARKET,
                None,
            )
            .await;

            return response;
        };

        if outcome.market_id != market.id || request.event_id != market.event_id {
            let response = self
                .bets
                .record(self.build_bet_intent_record(
                    &request,
                    Some(session.user_id),
                    false,
                    ACCEPTANCE_REJECTED_INVALID_MARKET,
                    None,
                ))
                .await;

            self.record_bet_audit(
                "bet_rejected",
                Some(&session),
                &request,
                ACCEPTANCE_REJECTED_INVALID_MARKET,
                None,
            )
            .await;

            return response;
        }

        if Utc::now() >= market.lock_at {
            let response = self
                .bets
                .record(self.build_bet_intent_record(
                    &request,
                    Some(session.user_id),
                    false,
                    ACCEPTANCE_REJECTED_TOO_LATE,
                    None,
                ))
                .await;

            self.record_bet_audit(
                "bet_rejected",
                Some(&session),
                &request,
                ACCEPTANCE_REJECTED_TOO_LATE,
                None,
            )
            .await;

            return response;
        }

        let accepted_odds_version_id = self.current_odds(market.id).await.map(|odds| odds.id);
        let response = self.bets
            .record(self.build_bet_intent_record(
                &request,
                Some(session.user_id),
                true,
                ACCEPTANCE_ACCEPTED,
                accepted_odds_version_id,
            ))
            .await;

        self.record_bet_audit(
            "bet_accepted",
            Some(&session),
            &request,
            ACCEPTANCE_ACCEPTED,
            accepted_odds_version_id,
        )
        .await;

        response
    }

    pub async fn bet_intent(&self, bet_intent_id: Uuid) -> Option<BetIntentResponse> {
        self.bets.get(bet_intent_id).await
    }

    pub async fn end_session(&self) -> Result<SessionSnapshot, AppError> {
        let session = self
            .sessions
            .end_session()
            .await
            .map_err(|_| AppError::not_found("No active session"))?;

        self.audit
            .record(
                "session_ended",
                Some(session.session_id),
                Some(session.user_id),
                Utc::now(),
                [
                    ("locale_code", session.locale_code.clone()),
                    ("device_platform", session.device_platform.clone()),
                    ("app_version", session.app_version.clone()),
                    ("experiment_variant", session.experiment_variant.clone()),
                ],
            )
            .await;

        Ok(session)
    }

    pub async fn admin_create_event_manifest(
        &self,
        manifest: EventManifestSnapshot,
    ) -> Result<EventManifestSnapshot, AppError> {
        let created = self.events.insert_manifest(manifest.clone()).await;

        for market in &created.markets {
            self.markets.insert_market(market.clone()).await;
        }

        self.audit
            .record(
                "event_created",
                None,
                None,
                Utc::now(),
                [
                    ("event_id", created.event.id.to_string()),
                    ("sport_type", created.event.sport_type.clone()),
                    ("status", created.event.status.clone()),
                ],
            )
            .await;

        Ok(created)
    }

    pub async fn admin_create_market(
        &self,
        market: MarketSnapshot,
    ) -> Result<MarketSnapshot, AppError> {
        let Some(_) = self.event(market.event_id).await else {
            return Err(AppError::not_found("Event not found"));
        };

        self.markets.insert_market(market.clone()).await;
        let _ = self.events.insert_market(market.event_id, market.clone()).await;

        self.audit
            .record(
                "market_created",
                None,
                None,
                Utc::now(),
                [
                    ("market_id", market.id.to_string()),
                    ("event_id", market.event_id.to_string()),
                    ("market_type", market.market_type.clone()),
                    ("status", market.status.clone()),
                ],
            )
            .await;

        Ok(market)
    }

    pub async fn admin_publish_odds(
        &self,
        odds: OddsVersionSnapshot,
    ) -> Result<OddsVersionSnapshot, AppError> {
        let Some(_) = self.markets.market(odds.market_id).await else {
            return Err(AppError::not_found("Market not found"));
        };

        let created = self.markets.publish_odds(odds).await;

        self.audit
            .record(
                "odds_version_published",
                None,
                None,
                Utc::now(),
                [
                    ("odds_version_id", created.id.to_string()),
                    ("market_id", created.market_id.to_string()),
                    ("version_no", created.version_no.to_string()),
                ],
            )
            .await;

        Ok(created)
    }

    pub async fn admin_publish_settlement(
        &self,
        settlement: SettlementSnapshot,
    ) -> Result<SettlementSnapshot, AppError> {
        let Some(market) = self.markets.market(settlement.market_id).await else {
            return Err(AppError::not_found("Market not found"));
        };

        let created = self.settlements.upsert(market.event_id, settlement).await;

        self.audit
            .record(
                "result_settled",
                None,
                None,
                Utc::now(),
                [
                    ("settlement_id", created.id.to_string()),
                    ("market_id", created.market_id.to_string()),
                    ("event_id", market.event_id.to_string()),
                    ("winning_outcome_id", created.winning_outcome_id.to_string()),
                ],
            )
            .await;

        Ok(created)
    }

    pub async fn next_event(&self) -> Option<EventSnapshot> {
        self.events.next_event().await
    }

    pub async fn event(&self, event_id: Uuid) -> Option<EventSnapshot> {
        self.events.get_event(event_id).await
    }

    pub async fn event_manifest(&self, event_id: Uuid) -> Option<EventManifestSnapshot> {
        self.events.get_manifest(event_id).await
    }

    pub async fn settlement_for_event(&self, event_id: Uuid) -> Option<SettlementSnapshot> {
        self.settlements.settlement_for_event(event_id).await
    }

    pub async fn experiment_config(&self) -> ExperimentConfigSnapshot {
        let Some(session) = self.current_session().await else {
            return self
                .experiments
                .config_for_variant(&self.config.default_experiment_variant);
        };

        if session.ended_at.is_some() {
            return self
                .experiments
                .config_for_variant(&self.config.default_experiment_variant);
        }

        self.experiments.config_for_variant(&session.experiment_variant)
    }

    pub async fn localization_bundle(
        &self,
        locale_code: &str,
    ) -> Option<LocalizationBundleSnapshot> {
        self.localization.bundle(locale_code)
    }

    pub async fn ingest_analytics_batch(
        &self,
        request: AnalyticsBatchRequest,
    ) -> Result<usize, AppError> {
        let Some(session) = self.current_session().await else {
            return Err(AppError::not_found("No active session"));
        };

        if session.ended_at.is_some() {
            return Err(AppError::not_found("No active session"));
        }

        Ok(self.analytics.ingest(&session, request).await)
    }

    pub async fn analytics_counts(&self) -> (usize, usize) {
        self.analytics.counts().await
    }

    pub async fn audit_counts(&self) -> (usize, usize) {
        self.audit.counts().await
    }

    pub async fn markets_for_event(&self, event_id: Uuid) -> Option<Vec<MarketSnapshot>> {
        self.markets.markets_for_event(event_id).await
    }

    pub async fn current_odds(&self, market_id: Uuid) -> Option<OddsVersionSnapshot> {
        self.markets.current_odds(market_id).await
    }

    pub fn database_ready(&self) -> bool {
        self.database_pool.is_some()
    }

    pub fn redis_ready(&self) -> bool {
        self.redis_client.is_some()
    }

    pub fn uptime_ms(&self) -> u128 {
        self.started_at.elapsed().as_millis()
    }
}
