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

use tidaldb::TidalDb;
use tidaldb::query::retrieve::Retrieve;
use tidaldb::query::search::Search;
use tidaldb::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 {
        Self {
            db: Arc::new(db),
            shutting_down: Arc::new(AtomicBool::new(false)),
        }
    }

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

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

    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)
    }

    pub fn write_embedding(&self, entity_id: EntityId, embedding: &[f32]) -> Result<()> {
        self.db
            .write_item_embedding(entity_id, embedding)
            .map_err(ServerError::from)
    }

    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)
        }
    }

    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.
    pub fn reload_text_index(&self, region_name: Option<&str>) -> Result<()> {
        ensure_standalone(region_name)?;
        self.db.reload_text_index().map_err(ServerError::from)
    }

    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)
    }

    pub fn item_count(&self, region_name: Option<&str>) -> Result<u64> {
        ensure_standalone(region_name)?;
        Ok(self.db.item_count())
    }
}
