use std::{collections::HashMap, sync::Arc};

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use tokio::sync::RwLock;
use uuid::Uuid;

#[derive(Clone, Debug, Deserialize)]
pub struct SessionStartRequest {
    pub locale_code: Option<String>,
    pub device_platform: Option<String>,
    pub device_model: Option<String>,
    pub os_version: Option<String>,
    pub app_version: Option<String>,
    pub experiment_variant: Option<String>,
    pub external_ref: Option<String>,
}

#[derive(Clone, Debug)]
pub struct SessionDefaults {
    pub default_locale: String,
    pub default_device_platform: String,
    pub default_app_version: String,
    pub default_experiment_variant: String,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SessionSnapshot {
    pub session_id: Uuid,
    pub user_id: Uuid,
    pub started_at: DateTime<Utc>,
    pub ended_at: Option<DateTime<Utc>>,
    pub experiment_variant: String,
    pub app_version: String,
    pub device_model: Option<String>,
    pub os_version: Option<String>,
    pub locale_code: String,
    pub device_platform: String,
}

#[derive(Clone, Default)]
pub struct SessionsStore {
    inner: Arc<RwLock<SessionsState>>,
}

#[derive(Default)]
struct SessionsState {
    sessions_by_id: HashMap<Uuid, SessionSnapshot>,
    current_session_id: Option<Uuid>,
}

#[derive(Debug, thiserror::Error)]
pub enum SessionStoreError {
    #[error("no active session")]
    NoActiveSession,
}

impl SessionsStore {
    pub fn new() -> Self {
        Self::default()
    }

    pub async fn start_session(
        &self,
        user_id: Uuid,
        request: SessionStartRequest,
        defaults: SessionDefaults,
    ) -> SessionSnapshot {
        let snapshot = SessionSnapshot {
            session_id: Uuid::new_v4(),
            user_id,
            started_at: Utc::now(),
            ended_at: None,
            experiment_variant: request
                .experiment_variant
                .unwrap_or(defaults.default_experiment_variant),
            app_version: request.app_version.unwrap_or(defaults.default_app_version),
            device_model: request.device_model,
            os_version: request.os_version,
            locale_code: request.locale_code.unwrap_or(defaults.default_locale),
            device_platform: request
                .device_platform
                .unwrap_or(defaults.default_device_platform),
        };

        let mut state = self.inner.write().await;
        state.current_session_id = Some(snapshot.session_id);
        state.sessions_by_id.insert(snapshot.session_id, snapshot.clone());
        snapshot
    }

    pub async fn current_session(&self) -> Option<SessionSnapshot> {
        let state = self.inner.read().await;
        let Some(session_id) = state.current_session_id else {
            return None;
        };
        state.sessions_by_id.get(&session_id).cloned()
    }

    pub async fn end_session(&self) -> Result<SessionSnapshot, SessionStoreError> {
        let mut state = self.inner.write().await;
        let Some(session_id) = state.current_session_id else {
            return Err(SessionStoreError::NoActiveSession);
        };

        let Some(snapshot) = state.sessions_by_id.get_mut(&session_id) else {
            return Err(SessionStoreError::NoActiveSession);
        };
        snapshot.ended_at = Some(Utc::now());
        Ok(snapshot.clone())
    }
}
