use boom_core::provider::KeyAliasLookup;
use boom_core::DebugErrorStore;
use boom_flowcontrol::FlowController;
use boom_limiter::{PlanStore, SlidingWindowLimiter};
use boom_promptlog::PromptLogConfig;
use boom_promptlog::PromptLogQueryApi;
use boom_routing::{AliasStore, DeploymentStore, InFlightTracker, RebalanceMoveTracker, RequestRateTracker};
use boom_ctxaware::AgentStatsTracker;
use dashmap::DashMap;
use serde_json::Value;
use sqlx::PgPool;
use std::sync::Arc;
use std::time::Instant;
use tokio::sync::{mpsc, oneshot};
use uuid::Uuid;

use crate::handlers_admin::CreateDeploymentRequest;

/// Tracks login failure state per IP for brute-force protection.
#[derive(Debug)]
pub struct LoginAttempt {
    pub fail_count: u32,
    pub locked_until: Option<Instant>,
}

// ═══════════════════════════════════════════════════════════
// Admin Command channel (write operations → boom-main)
// ═══════════════════════════════════════════════════════════

/// Commands sent from dashboard to boom-main for state-mutating operations.
/// Model CRUD requires boom-provider + boom-config, which dashboard must not depend on.
pub enum AdminCommand {
    CreateModel {
        req: CreateDeploymentRequest,
        reply: oneshot::Sender<Result<Value, String>>,
    },
    UpdateModel {
        id: Uuid,
        req: CreateDeploymentRequest,
        reply: oneshot::Sender<Result<Value, String>>,
    },
    DeleteModel {
        id: Uuid,
        reply: oneshot::Sender<Result<Value, String>>,
    },
    /// Fire-and-forget: config changed, persist to YAML in place.
    ConfigChanged,
    /// Hot-reload config.yaml. Reply contains summary message.
    ReloadConfig {
        reply: oneshot::Sender<Result<String, String>>,
    },
    /// Read the live in-memory config as JSON (secrets masked).
    GetConfig {
        reply: oneshot::Sender<Result<Value, String>>,
    },
    /// Read the field manifest — declarative list of which config fields
    /// are editable from the dashboard UI. See boom-config `manifest` module
    /// and CLAUDE.md §9. Used by `GET /admin/config/schema` and (future)
    /// auto-rendering frontend code.
    GetConfigSchema {
        reply: oneshot::Sender<Result<Value, String>>,
    },
    /// Update a singleton config section (e.g., `server`, `router_settings`)
    /// by replacing it wholesale in the live `config.yaml`. Path is dotted
    /// (`router_settings.kvc_aware`); value is the new JSON-serializable content.
    /// Triggers a reload after writing.
    UpdateConfigSection {
        path: String,
        value: Value,
        reply: oneshot::Sender<Result<String, String>>,
    },
    /// Hot-swap the prompt-log config at runtime (toggle on/off, change
    /// exclusion lists, flip the otlp sub-config). Boom-main owns the
    /// `PromptLogWriter`; dashboard must not touch the writer handle directly
    /// (see CLAUDE.md §5 AdminCommand pattern).
    UpdatePromptLogConfig {
        config: PromptLogConfig,
        reply: oneshot::Sender<Result<(), String>>,
    },
    /// Probe a remote OTLP/HTTP collector. The dashboard passes the live
    /// endpoint/headers/timeout (read from the prompt-log card form — not the
    /// committed YAML, so the operator can type a new endpoint and test it
    /// before saving). Reply carries the round-trip latency in ms on success
    /// or a one-line error string on failure.
    PingOtlpEndpoint {
        endpoint: String,
        headers: std::collections::HashMap<String, String>,
        timeout_secs: u64,
        reply: oneshot::Sender<Result<u64, String>>,
    },
    /// Read the live OTLP exporter's state machine snapshot — Online/Offline
    /// status, endpoint, last_failure_ts, last_recovery_ts, episode counters,
    /// dropped counts. The dashboard polls this every 5s to drive the
    /// connectivity indicator (green=online, red=offline, gray=disabled).
    /// Returns `None` when OTLP is not configured (no exporter in the
    /// ArcSwap); the dashboard treats `None` as "disabled".
    GetOtlpStatus {
        reply: oneshot::Sender<Option<boom_promptlog::ExporterStatusSnapshot>>,
    },
    /// Manually trigger a probe on the live exporter. On success transitions
    /// Offline → Online; on failure records a probe failure (but does NOT
    /// drive Online → Offline — only repeated flush failures do that). Used
    /// by the dashboard's "Probe now" action when the operator wants to
    /// attempt recovery before the next periodic tick. Returns `None` when
    /// OTLP is not configured.
    ProbeOtlp {
        reply: oneshot::Sender<Option<boom_promptlog::ProbeResult>>,
    },
}

pub type AdminTx = mpsc::Sender<AdminCommand>;

// ═══════════════════════════════════════════════════════════
// Dashboard state
// ═══════════════════════════════════════════════════════════

/// Dashboard-specific state, injected via Extension layer.
/// Independent from boom-gateway's AppState to avoid type coupling.
#[derive(Clone)]
pub struct DashboardState {
    /// Dashboard-only DB pool (max=3), isolated from the forwarding path's
    /// pool (max=30). Heavy stats aggregations cannot starve request forwarding.
    pub db_pool: Option<PgPool>,
    pub plan_store: Arc<PlanStore>,
    pub limiter: Arc<SlidingWindowLimiter>,
    /// Deployment store for model reads.
    pub deployment_store: Arc<DeploymentStore>,
    /// Alias store for alias reads.
    pub alias_store: Arc<AliasStore>,
    /// In-flight request tracker for real-time stats.
    pub inflight: Arc<InFlightTracker>,
    /// Per-deployment flow controller for real-time stats.
    pub flow_controller: Arc<FlowController>,
    /// Channel for model write operations (handled by boom-main).
    pub admin_tx: AdminTx,
    /// JWT signing key (derived from master_key at startup).
    pub jwt_secret: String,
    /// Master key for admin login (constant-time comparison).
    pub master_key: Option<String>,
    /// Login rate-limit state per client IP.
    pub login_attempts: Arc<DashMap<String, LoginAttempt>>,
    /// Debug error store — shared with boom-main for recording upstream errors.
    pub debug_store: Arc<DebugErrorStore>,
    /// Read-only prompt-log query API — entry lookup + config snapshot.
    /// Boom-main owns the writer; writes go through `AdminCommand::UpdatePromptLogConfig`.
    pub prompt_log_query: Arc<dyn PromptLogQueryApi>,
    /// Per-deployment rebalance move tracker (in/out) for dashboard debug page.
    pub rebalance_move_tracker: Arc<RebalanceMoveTracker>,
    /// Per-deployment request rate tracker for dashboard stats.
    pub request_rate: Arc<RequestRateTracker>,
    /// Agent (client-type) statistics tracker for dashboard stats.
    pub agent_stats: Arc<AgentStatsTracker>,
    /// Authenticator — used for key alias lookups (reads boom_verification_token).
    pub auth: Arc<dyn KeyAliasLookup>,
    /// Audit-log drop counter (channel full or batch INSERT failures).
    /// None when DB not configured (no LogWriter). Surfaced on the debug page.
    pub log_dropped: Option<Arc<dyn boom_core::LogDroppedCounter>>,
    /// Real-time pressure metrics (CPU, RSS, tokio worker queue depth,
    /// blocking pool queue, inflight). Polled every 1.5s by the admin
    /// stats page's top sparkline chart.
    pub stressmon: Arc<dyn boom_core::StressmonApi>,
}

impl DashboardState {
    pub fn new(
        db_pool: Option<PgPool>,
        plan_store: Arc<PlanStore>,
        limiter: Arc<SlidingWindowLimiter>,
        deployment_store: Arc<DeploymentStore>,
        alias_store: Arc<AliasStore>,
        inflight: Arc<InFlightTracker>,
        flow_controller: Arc<FlowController>,
        admin_tx: AdminTx,
        master_key: Option<String>,
        debug_store: Arc<DebugErrorStore>,
        prompt_log_query: Arc<dyn PromptLogQueryApi>,
        rebalance_move_tracker: Arc<RebalanceMoveTracker>,
        request_rate: Arc<RequestRateTracker>,
        agent_stats: Arc<AgentStatsTracker>,
        auth: Arc<dyn KeyAliasLookup>,
        log_dropped: Option<Arc<dyn boom_core::LogDroppedCounter>>,
        stressmon: Arc<dyn boom_core::StressmonApi>,
    ) -> Self {
        // Derive JWT secret from master_key, or use a random fallback.
        let jwt_secret = master_key
            .as_deref()
            .unwrap_or("boom-dashboard-default-secret")
            .to_string();
        Self {
            db_pool,
            plan_store,
            limiter,
            deployment_store,
            alias_store,
            inflight,
            flow_controller,
            admin_tx,
            jwt_secret,
            master_key,
            login_attempts: Arc::new(DashMap::new()),
            debug_store,
            prompt_log_query,
            rebalance_move_tracker,
            request_rate,
            agent_stats,
            auth,
            log_dropped,
            stressmon,
        }
    }
}