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

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

pub const ACCEPTANCE_ACCEPTED: &str = "accepted";
pub const ACCEPTANCE_REJECTED_TOO_LATE: &str = "rejected_too_late";
pub const ACCEPTANCE_REJECTED_INVALID_MARKET: &str = "rejected_invalid_market";
pub const ACCEPTANCE_REJECTED_INVALID_SESSION: &str = "rejected_invalid_session";
pub const ACCEPTANCE_REJECTED_DUPLICATE: &str = "rejected_duplicate";

#[derive(Clone, Debug, Deserialize)]
pub struct BetIntentRequest {
    pub session_id: Uuid,
    pub event_id: Uuid,
    pub market_id: Uuid,
    pub outcome_id: Uuid,
    pub idempotency_key: String,
    pub client_sent_at: DateTime<Utc>,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct BetIntentResponse {
    pub id: Uuid,
    pub accepted: bool,
    pub acceptance_code: String,
    pub accepted_odds_version_id: Option<Uuid>,
    pub server_received_at: DateTime<Utc>,
}

#[derive(Clone, Debug)]
pub struct BetIntentRecord {
    pub id: Uuid,
    pub session_id: Uuid,
    pub user_id: Option<Uuid>,
    pub event_id: Uuid,
    pub market_id: Uuid,
    pub outcome_id: Uuid,
    pub idempotency_key: String,
    pub client_sent_at: DateTime<Utc>,
    pub server_received_at: DateTime<Utc>,
    pub accepted: bool,
    pub acceptance_code: String,
    pub accepted_odds_version_id: Option<Uuid>,
}

impl BetIntentRecord {
    pub fn response(&self) -> BetIntentResponse {
        BetIntentResponse {
            id: self.id,
            accepted: self.accepted,
            acceptance_code: self.acceptance_code.clone(),
            accepted_odds_version_id: self.accepted_odds_version_id,
            server_received_at: self.server_received_at,
        }
    }
}

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

#[derive(Default)]
struct BetsState {
    intents_by_id: HashMap<Uuid, BetIntentRecord>,
    intents_by_idempotency_key: HashMap<String, Uuid>,
}

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

    pub async fn get(&self, bet_intent_id: Uuid) -> Option<BetIntentResponse> {
        let state = self.inner.read().await;
        let Some(record) = state.intents_by_id.get(&bet_intent_id) else {
            return None;
        };
        Some(record.response())
    }

    pub async fn get_by_idempotency_key(&self, idempotency_key: &str) -> Option<BetIntentResponse> {
        let state = self.inner.read().await;
        let Some(bet_intent_id) = state.intents_by_idempotency_key.get(idempotency_key).copied() else {
            return None;
        };
        let Some(record) = state.intents_by_id.get(&bet_intent_id) else {
            return None;
        };
        Some(record.response())
    }

    pub async fn record(&self, record: BetIntentRecord) -> BetIntentResponse {
        let mut state = self.inner.write().await;

        if let Some(existing_id) = state.intents_by_idempotency_key.get(&record.idempotency_key).copied() {
            if let Some(existing) = state.intents_by_id.get(&existing_id) {
                return existing.response();
            }
        }

        let response = record.response();
        state
            .intents_by_idempotency_key
            .insert(record.idempotency_key.clone(), record.id);
        state.intents_by_id.insert(record.id, record);
        response
    }

    pub async fn counts(&self) -> usize {
        self.inner.read().await.intents_by_id.len()
    }
}
