use std::{
    collections::HashMap,
    sync::{
        Arc,
        atomic::{AtomicBool, Ordering},
    },
};

use tidaldb::{
    TidalDb,
    query::{retrieve::Retrieve, search::Search},
    schema::EntityId,
};

use crate::error::{Result, ServerError};

/// Return an error if a region name was provided.
///
/// Region routing is a cluster-mode concept. In standalone mode any non-None
/// region param is a client mistake; return a clear error rather than silently
/// ignoring it.
fn ensure_standalone(region_name: Option<&str>) -> Result<()> {
    if region_name.is_some() {
        return Err(ServerError::BadRequest(
            "region routing requires cluster mode; this server runs standalone".into(),
        ));
    }
    Ok(())
}

#[derive(Clone)]
pub struct ServerState {
    db: Arc<TidalDb>,
    shutting_down: Arc<AtomicBool>,
}

impl ServerState {
    pub fn new(db: TidalDb) -> Self {
        // Wrap the db in the Arc that lives for the whole server lifetime, then
        // start the session-TTL sweeper against THAT Arc. This is the long-lived
        // handle the sweeper's `Weak` must upgrade to — `builder.open()` cannot
        // start it (a by-value `TidalDb` has no stable self-address), so without
        // this the sweeper was dead in every persistent deployment
        // (CONCURRENCY-1). `start_sweeper` only spawns a thread in persistent
        // mode; ephemeral standalone never persists sessions, so the call is a
        // cheap no-op there.
        let db = Arc::new(db);
        TidalDb::start_sweeper(&db);
        Self {
            db,
            shutting_down: Arc::new(AtomicBool::new(false)),
        }
    }

    pub fn set_shutting_down(&self) {
        self.shutting_down.store(true, Ordering::SeqCst);
    }

    #[must_use]
    pub fn is_shutting_down(&self) -> bool {
        self.shutting_down.load(Ordering::SeqCst)
    }

    /// Write an item and its metadata to the embedded DB.
    ///
    /// # Errors
    ///
    /// Returns [`ServerError`] if the underlying DB write fails.
    pub fn write_item(
        &self,
        entity_id: EntityId,
        metadata: &HashMap<String, String>,
    ) -> Result<()> {
        self.db
            .write_item_with_metadata(entity_id, metadata)
            .map_err(ServerError::from)
    }

    /// Write an item embedding to the embedded DB.
    ///
    /// # Errors
    ///
    /// Returns [`ServerError`] if the underlying DB write fails.
    pub fn write_embedding(&self, entity_id: EntityId, embedding: &[f32]) -> Result<()> {
        self.db
            .write_item_embedding(entity_id, embedding)
            .map_err(ServerError::from)
    }

    /// Record a signal, with optional user/creator context.
    ///
    /// # Errors
    ///
    /// Returns [`ServerError`] if the underlying DB signal write fails.
    pub fn signal(
        &self,
        signal_name: &str,
        entity_id: EntityId,
        weight: f64,
        user_id: Option<u64>,
        creator_id: Option<u64>,
    ) -> Result<()> {
        if user_id.is_some() || creator_id.is_some() {
            self.db
                .signal_with_context(
                    signal_name,
                    entity_id,
                    weight,
                    tidaldb::schema::Timestamp::now(),
                    user_id,
                    creator_id,
                )
                .map_err(ServerError::from)
        } else {
            self.db
                .signal(
                    signal_name,
                    entity_id,
                    weight,
                    tidaldb::schema::Timestamp::now(),
                )
                .map_err(ServerError::from)
        }
    }

    /// Retrieve and rank items for the (standalone) region.
    ///
    /// # Errors
    ///
    /// Returns [`ServerError`] if a region is specified (standalone mode is
    /// single-region) or the underlying DB retrieve fails.
    pub fn retrieve(
        &self,
        region_name: Option<&str>,
        query: &Retrieve,
    ) -> Result<tidaldb::query::retrieve::Results> {
        ensure_standalone(region_name)?;
        self.db.retrieve(query).map_err(ServerError::from)
    }

    /// Reload the Tantivy text index reader so the next search sees recently
    /// committed documents.
    ///
    /// On-disk indexes auto-reload via `OnCommitWithDelay`; ephemeral indexes
    /// (the default for standalone mode) use `ReloadPolicy::Manual` and require
    /// an explicit reload before each search.
    ///
    /// # Errors
    ///
    /// Returns [`ServerError`] if a region is specified (standalone mode is
    /// single-region) or the reader reload fails.
    pub fn reload_text_index(&self, region_name: Option<&str>) -> Result<()> {
        ensure_standalone(region_name)?;
        self.db.reload_text_index().map_err(ServerError::from)
    }

    /// Full-text search within the (standalone) region.
    ///
    /// # Errors
    ///
    /// Returns [`ServerError`] if a region is specified (standalone mode is
    /// single-region) or the underlying DB search fails.
    pub fn search(
        &self,
        region_name: Option<&str>,
        query: &Search,
    ) -> Result<tidaldb::query::search::SearchResults> {
        ensure_standalone(region_name)?;
        self.db.search(query).map_err(ServerError::from)
    }

    /// Count items in the (standalone) region.
    ///
    /// # Errors
    ///
    /// Returns [`ServerError`] if a region is specified (standalone mode is
    /// single-region).
    pub fn item_count(&self, region_name: Option<&str>) -> Result<u64> {
        ensure_standalone(region_name)?;
        Ok(self.db.item_count())
    }
}
