//! Application context — shared state (`AppContext`) threaded through all HTTP handlers.
use crate::agents::ReconciliationEvent;
use crate::io::watcher::WatcherEvent;
use crate::rebuild::{BotShutdownNotifier, ShutdownReason};
use crate::service::event_triggers::store::EventTriggerStore;
use crate::service::timer::{ScheduledTimerStore, TimerStore};
use crate::services::Services;
use crate::state::SessionState;
use crate::store::JsonFileStore;
use crate::workflow::WorkflowState;
use std::sync::Arc;
use tokio::sync::{broadcast, mpsc, oneshot};

/// The user's decision when responding to a permission dialog.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PermissionDecision {
    /// One-time denial.
    Deny,
    /// One-time approval.
    Approve,
    /// Approve and persist the rule to `.claude/settings.json` so Claude Code's
    /// built-in permission system handles future checks without prompting.
    AlwaysAllow,
}

/// A permission request forwarded from the MCP `prompt_permission` tool to the
/// active WebSocket session. The MCP handler blocks on `response_tx` until the
/// user approves or denies via the frontend dialog.
pub struct PermissionForward {
    pub request_id: String,
    pub tool_name: String,
    pub tool_input: serde_json::Value,
    pub response_tx: oneshot::Sender<PermissionDecision>,
}

#[derive(Clone)]
/// Shared application state threaded through all HTTP handlers via Poem's `Data` extractor.
pub struct AppContext {
    pub state: Arc<SessionState>,
    pub store: Arc<JsonFileStore>,
    pub workflow: Arc<std::sync::Mutex<WorkflowState>>,
    /// Shared services bundle (agent pool, bot identity, permissions, etc.).
    pub services: Arc<Services>,
    /// Broadcast channel for filesystem watcher events. WebSocket handlers
    /// subscribe to this to push lifecycle notifications to connected clients.
    pub watcher_tx: broadcast::Sender<WatcherEvent>,
    /// Broadcast channel for startup reconciliation progress events.
    /// WebSocket handlers subscribe to this to push real-time reconciliation
    /// updates to connected clients.
    pub reconciliation_tx: broadcast::Sender<ReconciliationEvent>,
    /// Sender for permission requests originating from the MCP
    /// `prompt_permission` tool. The MCP handler sends a [`PermissionForward`]
    /// and awaits the oneshot response.
    pub perm_tx: mpsc::UnboundedSender<PermissionForward>,
    /// Child process of the QA app launched for manual testing.
    /// Only one instance runs at a time.
    pub qa_app_process: Arc<std::sync::Mutex<Option<std::process::Child>>>,
    /// Best-effort shutdown notifier for active bot channels (Slack / WhatsApp).
    ///
    /// When set, restart-inducing paths use this to announce the shutdown to
    /// configured channels before the process exits.
    /// `None` when no webhook-based bot transport is configured.
    pub bot_shutdown: Option<Arc<BotShutdownNotifier>>,
    /// Watch sender used to signal the Matrix bot task that the server is
    /// shutting down (rebuild path).  The bot task listens for this signal and
    /// sends a shutdown announcement to all configured rooms.
    ///
    /// Wrapped in `Arc` so `AppContext` can implement `Clone`.
    /// `None` when no Matrix bot is configured.
    pub matrix_shutdown_tx: Option<Arc<tokio::sync::watch::Sender<Option<ShutdownReason>>>>,
    /// Shared rate-limit retry timer store.
    ///
    /// Used by MCP tools (`move_story`, `stop_agent`) to cancel pending timers
    /// when the user manually intervenes (bug 501).  Shared with the tick loop
    /// spawned by the bot so that cancellations take effect in-memory rather
    /// than only on disk.
    pub timer_store: Arc<TimerStore>,
    /// Generic scheduled-timer store for `schedule_timer` / `list_timers` /
    /// `cancel_timer` MCP tools.  Persists to `.huskies/scheduled_timers.json`.
    pub scheduled_timer_store: Arc<ScheduledTimerStore>,
    /// Persistent store for event-based pipeline triggers.
    ///
    /// Shared with the background subscriber so that triggers registered via
    /// MCP are immediately visible to the subscriber without a disk round-trip.
    pub event_trigger_store: Arc<EventTriggerStore>,
}

#[cfg(test)]
impl AppContext {
    /// Build a minimal `AppContext` for unit tests with an in-memory store.
    pub fn new_test(project_root: std::path::PathBuf) -> Self {
        use crate::agents::AgentPool;
        let state = SessionState::default();
        *state.project_root.lock().unwrap() = Some(project_root.clone());
        let store_path = project_root.join(".huskies_store.json");
        let (watcher_tx, _) = broadcast::channel(64);
        let (reconciliation_tx, _) = broadcast::channel(64);
        let (perm_tx, perm_rx) = mpsc::unbounded_channel();
        let permission_registry = crate::service::permission_router::ResponderRegistry::new();
        // Plain `#[test]` fns (no tokio runtime) construct `AppContext` too;
        // skip spawning when there's no reactor to spawn onto since those
        // tests never exercise the permission plumbing.
        if tokio::runtime::Handle::try_current().is_ok() {
            crate::service::permission_router::spawn_permission_router(
                perm_rx,
                Arc::clone(&permission_registry),
            );
        }
        let timer_store = Arc::new(TimerStore::load(
            project_root.join(".huskies").join("timers.json"),
        ));
        let event_trigger_store = Arc::new(EventTriggerStore::load(
            project_root.join(".huskies").join("event_triggers.json"),
        ));
        let scheduled_timer_store = Arc::new(ScheduledTimerStore::load(
            project_root.join(".huskies").join("scheduled_timers.json"),
        ));
        let agents = Arc::new(AgentPool::new(3001, watcher_tx.clone()));
        let services = Arc::new(Services {
            project_root: project_root.clone(),
            agents: Arc::clone(&agents),
            bot_name: "Assistant".to_string(),
            bot_user_id: String::new(),
            ambient_rooms: Arc::new(std::sync::Mutex::new(std::collections::HashSet::new())),
            permission_registry,
            pending_perm_replies: crate::service::permission_router::PendingPermReplies::new(),
            permission_timeout_secs: 120,
            status: agents.status_broadcaster(),
            chat_dispatcher: Arc::new(crate::chat::dispatcher::ChatDispatcher::new(1_500)),
        });
        Self {
            state: Arc::new(state),
            store: Arc::new(JsonFileStore::new(store_path).unwrap()),
            workflow: Arc::new(std::sync::Mutex::new(WorkflowState::default())),
            services,
            watcher_tx,
            reconciliation_tx,
            perm_tx,
            qa_app_process: Arc::new(std::sync::Mutex::new(None)),
            bot_shutdown: None,
            matrix_shutdown_tx: None,
            timer_store,
            scheduled_timer_store,
            event_trigger_store,
        }
    }
}

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

    #[test]
    fn permission_decision_equality() {
        assert_eq!(PermissionDecision::Deny, PermissionDecision::Deny);
        assert_eq!(PermissionDecision::Approve, PermissionDecision::Approve);
        assert_eq!(
            PermissionDecision::AlwaysAllow,
            PermissionDecision::AlwaysAllow
        );
        assert_ne!(PermissionDecision::Deny, PermissionDecision::Approve);
        assert_ne!(PermissionDecision::Approve, PermissionDecision::AlwaysAllow);
    }
}
