//! Shared application state for the MCP server.
//!
//! Bundles the database pool, configuration, optional LLM provider,
//! and optional X API client so that all tool handlers can access
//! them through the server struct.
//!
//! Two state structs exist for four runtime profiles:
//! - [`AppState`] / [`SharedState`]: write / admin profiles (DB + LLM + X client).
//! - [`ReadonlyState`] / [`SharedReadonlyState`]: readonly / api-readonly profiles (X client only, no DB).

use std::fmt;
use std::str::FromStr;
use std::sync::Arc;

use tuitbot_core::config::Config;
use tuitbot_core::llm::LlmProvider;
use tuitbot_core::storage::DbPool;
use tuitbot_core::x_api::XApiClient;

use crate::tools::idempotency::IdempotencyStore;

// ── Runtime profile ─────────────────────────────────────────────────

/// MCP server runtime profile.
///
/// - **`Readonly`** — minimal read-only X tools. No DB, no LLM, no mutations.
/// - **`ApiReadonly`** — broader read-only X tools. No DB, no LLM, no mutations.
/// - **`Write`** — standard operating profile. All typed tools including mutations. Default.
/// - **`Admin`** — superset of Write. Adds universal request tools (`x_get`/`x_post`/`x_put`/`x_delete`)
///   for arbitrary X API endpoint access. Only when explicitly configured.
/// - **`UtilityReadonly`** — flat toolkit surface: stateless X reads + scoring + config. No workflow tools.
/// - **`UtilityWrite`** — flat toolkit surface: reads + writes + engage. Raw toolkit calls, no policy gate.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Profile {
    Readonly,
    ApiReadonly,
    Write,
    Admin,
    UtilityReadonly,
    UtilityWrite,
}

impl fmt::Display for Profile {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Readonly => write!(f, "readonly"),
            Self::ApiReadonly => write!(f, "api-readonly"),
            Self::Write => write!(f, "write"),
            Self::Admin => write!(f, "admin"),
            Self::UtilityReadonly => write!(f, "utility-readonly"),
            Self::UtilityWrite => write!(f, "utility-write"),
        }
    }
}

impl FromStr for Profile {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_ascii_lowercase().as_str() {
            "readonly" => Ok(Self::Readonly),
            "api-readonly" => Ok(Self::ApiReadonly),
            "write" | "full" => Ok(Self::Write),
            "admin" => Ok(Self::Admin),
            "utility-readonly" => Ok(Self::UtilityReadonly),
            "utility-write" => Ok(Self::UtilityWrite),
            other => Err(format!(
                "unknown profile '{other}'. Valid profiles: readonly, api-readonly, write, admin, \
                 utility-readonly, utility-write"
            )),
        }
    }
}

// ── Write / Admin profile state ─────────────────────────────────────

/// Shared state accessible by all MCP tool handlers (write / admin profiles).
pub struct AppState {
    /// SQLite connection pool.
    pub pool: DbPool,
    /// Loaded and validated configuration.
    pub config: Config,
    /// Optional LLM provider (None if not configured or creation failed).
    pub llm_provider: Option<Box<dyn LlmProvider>>,
    /// Optional X API client (None if tokens not available).
    pub x_client: Option<Box<dyn XApiClient>>,
    /// Authenticated user ID from X API (cached from get_me on startup).
    pub authenticated_user_id: Option<String>,
    /// OAuth scopes granted by the current token (empty if no token loaded).
    pub granted_scopes: Vec<String>,
    /// Idempotency guard for mutation dedup.
    pub idempotency: Arc<IdempotencyStore>,
}

/// Thread-safe reference to shared full-profile state.
pub type SharedState = Arc<AppState>;

// ── Readonly / api-readonly profile state ───────────────────────────

/// Lightweight state for readonly / api-readonly profiles (no DB, no LLM).
///
/// The X client is always present but may be a [`NullXApiClient`] when tokens
/// are missing or expired. Check `x_available` to know whether X tools will work.
/// No idempotency store — read-only profiles perform no mutations.
pub struct ReadonlyState {
    /// Loaded configuration.
    pub config: Config,
    /// X API client (may be a no-op `NullXApiClient` if tokens are unavailable).
    pub x_client: Box<dyn XApiClient>,
    /// Authenticated user ID from X API (empty if X client is unavailable).
    pub authenticated_user_id: String,
    /// Whether the X API client is authenticated and functional.
    pub x_available: bool,
}

/// Thread-safe reference to shared readonly-profile state.
pub type SharedReadonlyState = Arc<ReadonlyState>;

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn profile_display() {
        assert_eq!(Profile::Readonly.to_string(), "readonly");
        assert_eq!(Profile::ApiReadonly.to_string(), "api-readonly");
        assert_eq!(Profile::Write.to_string(), "write");
        assert_eq!(Profile::Admin.to_string(), "admin");
        assert_eq!(Profile::UtilityReadonly.to_string(), "utility-readonly");
        assert_eq!(Profile::UtilityWrite.to_string(), "utility-write");
    }

    #[test]
    fn profile_from_str_valid() {
        assert_eq!(Profile::from_str("readonly").unwrap(), Profile::Readonly);
        assert_eq!(
            Profile::from_str("api-readonly").unwrap(),
            Profile::ApiReadonly
        );
        assert_eq!(Profile::from_str("write").unwrap(), Profile::Write);
        assert_eq!(Profile::from_str("admin").unwrap(), Profile::Admin);
        assert_eq!(
            Profile::from_str("utility-readonly").unwrap(),
            Profile::UtilityReadonly
        );
        assert_eq!(
            Profile::from_str("utility-write").unwrap(),
            Profile::UtilityWrite
        );
        // Case-insensitive variants
        assert_eq!(Profile::from_str("WRITE").unwrap(), Profile::Write);
        assert_eq!(Profile::from_str("ReadOnly").unwrap(), Profile::Readonly);
        assert_eq!(
            Profile::from_str("API-READONLY").unwrap(),
            Profile::ApiReadonly
        );
        assert_eq!(Profile::from_str("ADMIN").unwrap(), Profile::Admin);
        assert_eq!(
            Profile::from_str("UTILITY-READONLY").unwrap(),
            Profile::UtilityReadonly
        );
        assert_eq!(
            Profile::from_str("UTILITY-WRITE").unwrap(),
            Profile::UtilityWrite
        );
    }

    #[test]
    fn profile_from_str_legacy_full_maps_to_write() {
        assert_eq!(Profile::from_str("full").unwrap(), Profile::Write);
        assert_eq!(Profile::from_str("FULL").unwrap(), Profile::Write);
    }

    #[test]
    fn profile_from_str_invalid() {
        let err = Profile::from_str("unknown").unwrap_err();
        assert!(err.contains("unknown profile"));
        assert!(err.contains("utility-readonly, utility-write"));
    }

    #[test]
    fn profile_roundtrip() {
        for variant in [
            Profile::Readonly,
            Profile::ApiReadonly,
            Profile::Write,
            Profile::Admin,
            Profile::UtilityReadonly,
            Profile::UtilityWrite,
        ] {
            let s = variant.to_string();
            let parsed: Profile = s.parse().unwrap();
            assert_eq!(parsed, variant, "roundtrip failed for {s}");
        }
    }
}
