use std::collections::HashMap;

use of_core::SymbolId;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CqgSessionState {
    Disconnected,
    Connecting,
    LogonPending,
    ResolvingSymbols,
    Subscribing,
    Streaming,
    Degraded,
    BackoffWait,
}

#[derive(Debug)]
pub struct CqgSession {
    state: CqgSessionState,
    next_request_id: u64,
    pub symbol_to_contract: HashMap<SymbolId, i64>,
    pending_symbol_resolution: HashMap<u64, SymbolId>,
    pending_subscription_ack: HashMap<u64, (SymbolId, i64)>,
    pub requested_depth: HashMap<SymbolId, u16>,
}

impl CqgSession {
    pub fn new() -> Self {
        Self {
            state: CqgSessionState::Disconnected,
            next_request_id: 1,
            symbol_to_contract: HashMap::new(),
            pending_symbol_resolution: HashMap::new(),
            pending_subscription_ack: HashMap::new(),
            requested_depth: HashMap::new(),
        }
    }

    pub fn state(&self) -> CqgSessionState {
        self.state
    }

    pub fn set_state(&mut self, state: CqgSessionState) {
        self.state = state;
    }

    pub fn next_request_id(&mut self) -> u64 {
        let id = self.next_request_id;
        self.next_request_id = self.next_request_id.saturating_add(1);
        id
    }

    pub fn queue_symbol_resolution(&mut self, symbol: SymbolId, depth: u16) -> u64 {
        let req_id = self.next_request_id();
        self.pending_symbol_resolution
            .insert(req_id, symbol.clone());
        self.requested_depth.insert(symbol, depth);
        req_id
    }

    pub fn on_symbol_resolved(
        &mut self,
        request_id: u64,
        contract_id: i64,
    ) -> Option<(SymbolId, u16)> {
        let symbol = self.pending_symbol_resolution.remove(&request_id)?;
        let depth = self.requested_depth.get(&symbol).copied().unwrap_or(1);
        self.symbol_to_contract.insert(symbol.clone(), contract_id);
        Some((symbol, depth))
    }

    pub fn queue_subscription_ack(&mut self, request_id: u64, symbol: SymbolId, contract_id: i64) {
        self.pending_subscription_ack
            .insert(request_id, (symbol, contract_id));
    }

    pub fn on_subscription_ack(&mut self, request_id: u64) -> Option<(SymbolId, i64)> {
        self.pending_subscription_ack.remove(&request_id)
    }

    pub fn has_pending_work(&self) -> bool {
        !self.pending_symbol_resolution.is_empty() || !self.pending_subscription_ack.is_empty()
    }

    pub fn clear_transient(&mut self) {
        self.pending_symbol_resolution.clear();
        self.pending_subscription_ack.clear();
    }

    pub fn upsert_requested_depth(&mut self, symbol: SymbolId, depth: u16) {
        self.requested_depth.insert(symbol, depth);
    }

    pub fn remove_symbol(&mut self, symbol: &SymbolId) {
        self.requested_depth.remove(symbol);
        self.symbol_to_contract.remove(symbol);
        self.pending_symbol_resolution.retain(|_, s| s != symbol);
        self.pending_subscription_ack
            .retain(|_, (s, _)| s != symbol);
    }
}
