use crate::core::WindowId;
use crate::reader::AgentReaderTool;
use anyhow::Context;
use chrono::{DateTime, Utc};
use directories::ProjectDirs;
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value, json};
use std::collections::BTreeMap;
use std::fmt;
use std::fs::{self, OpenOptions};
use std::io::Write;
use std::path::{Path, PathBuf};
use uuid::Uuid;

pub const AGENT_EVENT_SCHEMA: &str = "cephas.agent.event";
pub const AGENT_EVENT_SCHEMA_VERSION: u16 = 1;
pub const DEFAULT_MAX_AGENT_EVENTS: usize = 1_000;
pub const DEFAULT_MAX_AGENT_DELEGATIONS: usize = 64;
pub const DEFAULT_MAX_AGENT_LOG_BYTES: u64 = 5 * 1024 * 1024;
pub const DEFAULT_MAX_AGENT_LOG_ARCHIVES: usize = 5;
pub const DEFAULT_MAX_AGENT_TEXT_BYTES: usize = 4 * 1024;
pub const DEFAULT_MAX_AGENT_METADATA_BYTES: usize = 16 * 1024;
const DEFAULT_MAX_AGENT_METADATA_ITEMS: usize = 32;
const DEFAULT_MAX_AGENT_METADATA_DEPTH: usize = 6;

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct AgentId(Uuid);

impl AgentId {
    pub fn new_v7() -> Self {
        Self(Uuid::now_v7())
    }

    pub fn as_uuid(&self) -> Uuid {
        self.0
    }
}

impl fmt::Display for AgentId {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.0.fmt(formatter)
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AgentStatus {
    Tasked,
    Active,
    Completed,
    Failed,
    Revoked,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AgentActionState {
    Running,
    Completed,
    Failed,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AgentEventType {
    AgentTasked,
    AgentActive,
    AgentLog,
    AgentCommandQueued,
    AgentCommandCompleted,
    AgentCommandFailed,
    AgentCompleted,
    AgentFailed,
    AgentRevoked,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum AgentCommandKind {
    Navigate,
    OpenTab,
    Reload,
    Back,
    Forward,
    CloseTab,
    CopyCleanLink,
    StartRecording,
    StopRecording,
    CaptureSnapshot,
    CaptureWebsiteScreenshot,
    EnableHumanControl,
    DisableHumanControl,
    HumanMovePointer,
    HumanClick,
    HumanTypeText,
    HumanPressKey,
    HumanScroll,
    HumanWait,
    ReaderBrief,
    ReaderFullText,
    ReaderOutline,
    ReaderLinkMap,
    ReaderDataSignals,
    ReaderJsonArtifact,
}

impl AgentCommandKind {
    pub const ALL: [Self; 25] = [
        Self::Navigate,
        Self::OpenTab,
        Self::Reload,
        Self::Back,
        Self::Forward,
        Self::CloseTab,
        Self::CopyCleanLink,
        Self::StartRecording,
        Self::StopRecording,
        Self::CaptureSnapshot,
        Self::CaptureWebsiteScreenshot,
        Self::EnableHumanControl,
        Self::DisableHumanControl,
        Self::HumanMovePointer,
        Self::HumanClick,
        Self::HumanTypeText,
        Self::HumanPressKey,
        Self::HumanScroll,
        Self::HumanWait,
        Self::ReaderBrief,
        Self::ReaderFullText,
        Self::ReaderOutline,
        Self::ReaderLinkMap,
        Self::ReaderDataSignals,
        Self::ReaderJsonArtifact,
    ];

    pub fn id(&self) -> &'static str {
        match self {
            Self::Navigate => "navigate",
            Self::OpenTab => "open-tab",
            Self::Reload => "reload",
            Self::Back => "back",
            Self::Forward => "forward",
            Self::CloseTab => "close-tab",
            Self::CopyCleanLink => "copy-clean-link",
            Self::StartRecording => "start-recording",
            Self::StopRecording => "stop-recording",
            Self::CaptureSnapshot => "capture-snapshot",
            Self::CaptureWebsiteScreenshot => "capture-website-screenshot",
            Self::EnableHumanControl => "enable-human-control",
            Self::DisableHumanControl => "disable-human-control",
            Self::HumanMovePointer => "human-move-pointer",
            Self::HumanClick => "human-click",
            Self::HumanTypeText => "human-type-text",
            Self::HumanPressKey => "human-press-key",
            Self::HumanScroll => "human-scroll",
            Self::HumanWait => "human-wait",
            Self::ReaderBrief => "reader-brief",
            Self::ReaderFullText => "reader-full-text",
            Self::ReaderOutline => "reader-outline",
            Self::ReaderLinkMap => "reader-link-map",
            Self::ReaderDataSignals => "reader-data-signals",
            Self::ReaderJsonArtifact => "reader-json-artifact",
        }
    }

    pub fn label(&self) -> &'static str {
        match self {
            Self::Navigate => "Navigate active tab",
            Self::OpenTab => "Open tab",
            Self::Reload => "Reload",
            Self::Back => "Back",
            Self::Forward => "Forward",
            Self::CloseTab => "Close tab",
            Self::CopyCleanLink => "Copy clean link",
            Self::StartRecording => "Start recording",
            Self::StopRecording => "Stop recording",
            Self::CaptureSnapshot => "Capture snapshot",
            Self::CaptureWebsiteScreenshot => "Capture website screenshot",
            Self::EnableHumanControl => "Enable human control",
            Self::DisableHumanControl => "Disable human control",
            Self::HumanMovePointer => "Move pointer",
            Self::HumanClick => "Click page",
            Self::HumanTypeText => "Type text",
            Self::HumanPressKey => "Press key",
            Self::HumanScroll => "Scroll page",
            Self::HumanWait => "Wait",
            Self::ReaderBrief => "Reader brief",
            Self::ReaderFullText => "Reader full text",
            Self::ReaderOutline => "Reader outline",
            Self::ReaderLinkMap => "Reader link map",
            Self::ReaderDataSignals => "Reader data signals",
            Self::ReaderJsonArtifact => "Reader JSON artifact",
        }
    }

    pub fn description(&self) -> &'static str {
        match self {
            Self::Navigate => "Navigate the active tab to a URL or search query.",
            Self::OpenTab => "Open a new tab with a URL or search query.",
            Self::Reload => "Reload the active tab.",
            Self::Back => "Go back in the active tab if possible.",
            Self::Forward => "Go forward in the active tab if possible.",
            Self::CloseTab => "Close the active tab using normal tab lifecycle rules.",
            Self::CopyCleanLink => "Copy a tracking-stripped link for the active page.",
            Self::StartRecording => "Start visual recording for the active browser window.",
            Self::StopRecording => "Stop visual recording and write the replay bundle.",
            Self::CaptureSnapshot => "Capture a visible-frame snapshot into the active recording.",
            Self::CaptureWebsiteScreenshot => {
                "Render a URL or internal browser page and write a PNG screenshot."
            }
            Self::EnableHumanControl => {
                "Enable opt-in browser-window human control for the active Cephas window."
            }
            Self::DisableHumanControl => "Disable browser-window human control.",
            Self::HumanMovePointer => {
                "Dispatch a mouse move at viewport coordinates in the active page."
            }
            Self::HumanClick => "Dispatch a click at viewport coordinates in the active page.",
            Self::HumanTypeText => "Type text into the focused element in the active page.",
            Self::HumanPressKey => "Press a key in the active page.",
            Self::HumanScroll => "Scroll the active page by viewport deltas.",
            Self::HumanWait => "Pause an agent workflow without changing the page.",
            Self::ReaderBrief => "Extract compact source metadata, headings, and leading text.",
            Self::ReaderFullText => "Extract the full agent reader text packet.",
            Self::ReaderOutline => "Extract the page heading outline and structure counts.",
            Self::ReaderLinkMap => "Extract canonicalized page links.",
            Self::ReaderDataSignals => {
                "Extract emails, dates, money, percentages, and compact tables."
            }
            Self::ReaderJsonArtifact => "Extract a machine-readable JSON reader artifact.",
        }
    }

    pub fn requires_target(&self) -> bool {
        matches!(
            self,
            Self::Navigate
                | Self::OpenTab
                | Self::CaptureWebsiteScreenshot
                | Self::HumanMovePointer
                | Self::HumanClick
                | Self::HumanTypeText
                | Self::HumanPressKey
                | Self::HumanScroll
                | Self::HumanWait
        )
    }

    pub fn reader_tool(&self) -> Option<AgentReaderTool> {
        match self {
            Self::ReaderBrief => Some(AgentReaderTool::AgentBrief),
            Self::ReaderFullText => Some(AgentReaderTool::FullText),
            Self::ReaderOutline => Some(AgentReaderTool::Outline),
            Self::ReaderLinkMap => Some(AgentReaderTool::LinkMap),
            Self::ReaderDataSignals => Some(AgentReaderTool::DataSignals),
            Self::ReaderJsonArtifact => Some(AgentReaderTool::JsonArtifact),
            _ => None,
        }
    }

    pub fn from_id(id: &str) -> Option<Self> {
        Self::ALL.into_iter().find(|command| command.id() == id)
    }
}

impl fmt::Display for AgentCommandKind {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter.write_str(self.id())
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct AgentCommandManifestEntry {
    pub id: &'static str,
    pub label: &'static str,
    pub description: &'static str,
    pub requires_target: bool,
}

pub fn agent_command_manifest() -> Vec<AgentCommandManifestEntry> {
    AgentCommandKind::ALL
        .into_iter()
        .map(|command| AgentCommandManifestEntry {
            id: command.id(),
            label: command.label(),
            description: command.description(),
            requires_target: command.requires_target(),
        })
        .collect()
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct AgentCommandRequest {
    pub command_id: Uuid,
    pub kind: AgentCommandKind,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub target: Option<String>,
    pub created_at: DateTime<Utc>,
}

impl AgentCommandRequest {
    fn new(
        kind: AgentCommandKind,
        target: Option<String>,
        created_at: DateTime<Utc>,
    ) -> Result<Self, AgentControlError> {
        let target = target
            .map(|target| bounded_agent_text(target.trim()))
            .filter(|target| !target.is_empty());
        if kind.requires_target() && target.is_none() {
            return Err(AgentControlError::InvalidCommandTarget(kind));
        }
        Ok(Self {
            command_id: Uuid::now_v7(),
            kind,
            target,
            created_at,
        })
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentTask {
    pub task_id: Uuid,
    pub description: String,
    pub created_at: DateTime<Utc>,
}

impl AgentTask {
    fn new(description: impl Into<String>, created_at: DateTime<Utc>) -> Self {
        Self {
            task_id: Uuid::now_v7(),
            description: bounded_agent_text(description),
            created_at,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentAction {
    pub action_id: Uuid,
    pub name: String,
    pub state: AgentActionState,
    pub started_at: DateTime<Utc>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub finished_at: Option<DateTime<Utc>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub details: Option<String>,
}

impl AgentAction {
    fn running(
        name: impl Into<String>,
        details: Option<String>,
        started_at: DateTime<Utc>,
    ) -> Self {
        Self {
            action_id: Uuid::now_v7(),
            name: bounded_agent_text(name),
            state: AgentActionState::Running,
            started_at,
            finished_at: None,
            details: details.map(bounded_agent_text),
        }
    }

    fn finish(&mut self, state: AgentActionState, finished_at: DateTime<Utc>) {
        self.state = state;
        self.finished_at = Some(finished_at);
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentStateSnapshot {
    pub status: AgentStatus,
    pub task: AgentTask,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub active_action: Option<AgentAction>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub last_action: Option<AgentAction>,
    pub updated_at: DateTime<Utc>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub completed_at: Option<DateTime<Utc>>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentDelegation {
    pub agent_id: AgentId,
    pub agent_label: String,
    pub window_id: WindowId,
    pub task: AgentTask,
    pub status: AgentStatus,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub active_action: Option<AgentAction>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub last_action: Option<AgentAction>,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub completed_at: Option<DateTime<Utc>>,
}

impl AgentDelegation {
    fn new(
        agent_label: impl Into<String>,
        window_id: WindowId,
        task_description: impl Into<String>,
        created_at: DateTime<Utc>,
    ) -> Self {
        Self {
            agent_id: AgentId::new_v7(),
            agent_label: bounded_agent_text(agent_label),
            window_id,
            task: AgentTask::new(task_description, created_at),
            status: AgentStatus::Tasked,
            active_action: None,
            last_action: None,
            created_at,
            updated_at: created_at,
            completed_at: None,
        }
    }

    fn snapshot(&self) -> AgentStateSnapshot {
        AgentStateSnapshot {
            status: self.status,
            task: self.task.clone(),
            active_action: self.active_action.clone(),
            last_action: self.last_action.clone(),
            updated_at: self.updated_at,
            completed_at: self.completed_at,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentLogEvent {
    pub schema: &'static str,
    pub schema_version: u16,
    pub event_id: Uuid,
    pub agent_id: AgentId,
    pub window_id: WindowId,
    pub sequence: u64,
    pub occurred_at: DateTime<Utc>,
    pub event_type: AgentEventType,
    pub message: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub task: Option<AgentTask>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub action: Option<AgentAction>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub command: Option<AgentCommandRequest>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub metadata: Option<Value>,
    pub state: AgentStateSnapshot,
}

#[derive(Debug, Clone, Default)]
struct AgentEventDetails {
    task: Option<AgentTask>,
    action: Option<AgentAction>,
    command: Option<AgentCommandRequest>,
    metadata: Option<Value>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AgentControlError {
    AiModeDisabled,
    UnknownAgent(AgentId),
    InvalidCommandTarget(AgentCommandKind),
}

impl fmt::Display for AgentControlError {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::AiModeDisabled => formatter.write_str("AI browsing mode is disabled"),
            Self::UnknownAgent(agent_id) => write!(formatter, "unknown agent {agent_id}"),
            Self::InvalidCommandTarget(command) => {
                write!(formatter, "command {command} requires a target")
            }
        }
    }
}

impl std::error::Error for AgentControlError {}

#[derive(Debug, Clone)]
pub struct AgentControl {
    delegations: BTreeMap<AgentId, AgentDelegation>,
    events: Vec<AgentLogEvent>,
    next_sequence: u64,
    max_events: usize,
    max_delegations: usize,
}

impl Default for AgentControl {
    fn default() -> Self {
        Self::new(DEFAULT_MAX_AGENT_EVENTS)
    }
}

impl AgentControl {
    pub fn new(max_events: usize) -> Self {
        Self::with_limits(max_events, DEFAULT_MAX_AGENT_DELEGATIONS)
    }

    pub fn with_limits(max_events: usize, max_delegations: usize) -> Self {
        Self {
            delegations: BTreeMap::new(),
            events: Vec::new(),
            next_sequence: 1,
            max_events: max_events.max(1),
            max_delegations: max_delegations.max(1),
        }
    }

    pub fn delegate_window(
        &mut self,
        ai_mode_enabled: bool,
        window_id: WindowId,
        agent_label: impl Into<String>,
        task_description: impl Into<String>,
    ) -> Result<AgentLogEvent, AgentControlError> {
        if !ai_mode_enabled {
            return Err(AgentControlError::AiModeDisabled);
        }

        let now = Utc::now();
        let delegation = AgentDelegation::new(agent_label, window_id, task_description, now);
        let event = self.event_for(
            &delegation,
            AgentEventType::AgentTasked,
            "agent tasked for browser window",
            AgentEventDetails {
                task: Some(delegation.task.clone()),
                ..AgentEventDetails::default()
            },
            now,
        );
        self.delegations.insert(delegation.agent_id, delegation);
        self.enforce_delegation_limit();
        self.record(event.clone());
        Ok(event)
    }

    pub fn start_action(
        &mut self,
        agent_id: AgentId,
        action_name: impl Into<String>,
        details: Option<String>,
    ) -> Result<AgentLogEvent, AgentControlError> {
        let now = Utc::now();
        let action = AgentAction::running(action_name, details, now);
        let (delegation, action) = {
            let delegation = self.delegation_mut(agent_id)?;
            delegation.status = AgentStatus::Active;
            delegation.active_action = Some(action.clone());
            delegation.updated_at = now;
            (delegation.clone(), action)
        };
        let event = self.event_for(
            &delegation,
            AgentEventType::AgentActive,
            "agent action active",
            AgentEventDetails {
                action: Some(action),
                ..AgentEventDetails::default()
            },
            now,
        );
        self.record(event.clone());
        Ok(event)
    }

    pub fn append_log(
        &mut self,
        agent_id: AgentId,
        message: impl Into<String>,
        metadata: Option<Value>,
    ) -> Result<AgentLogEvent, AgentControlError> {
        let now = Utc::now();
        let (delegation, action) = {
            let delegation = self.delegation_mut(agent_id)?;
            delegation.updated_at = now;
            (delegation.clone(), delegation.active_action.clone())
        };
        let event = self.event_for(
            &delegation,
            AgentEventType::AgentLog,
            message,
            AgentEventDetails {
                action,
                metadata,
                ..AgentEventDetails::default()
            },
            now,
        );
        self.record(event.clone());
        Ok(event)
    }

    pub fn queue_command(
        &mut self,
        ai_mode_enabled: bool,
        agent_id: AgentId,
        kind: AgentCommandKind,
        target: Option<String>,
    ) -> Result<AgentLogEvent, AgentControlError> {
        if !ai_mode_enabled {
            return Err(AgentControlError::AiModeDisabled);
        }
        let now = Utc::now();
        let command = AgentCommandRequest::new(kind, target, now)?;
        let action = AgentAction::running(
            format!("agent_command:{}", kind.id()),
            Some(format!("command_id={}", command.command_id)),
            now,
        );
        let (delegation, action, command) = {
            let delegation = self.delegation_mut(agent_id)?;
            delegation.status = AgentStatus::Active;
            delegation.active_action = Some(action.clone());
            delegation.updated_at = now;
            (delegation.clone(), action, command)
        };
        let event = self.event_for(
            &delegation,
            AgentEventType::AgentCommandQueued,
            format!("agent command queued: {}", command.kind.id()),
            AgentEventDetails {
                action: Some(action),
                command: Some(command),
                ..AgentEventDetails::default()
            },
            now,
        );
        self.record(event.clone());
        Ok(event)
    }

    pub fn complete_command(
        &mut self,
        agent_id: AgentId,
        command: AgentCommandRequest,
        outcome: impl Into<String>,
        metadata: Option<Value>,
    ) -> Result<AgentLogEvent, AgentControlError> {
        let now = Utc::now();
        let (delegation, action, command) = {
            let delegation = self.delegation_mut(agent_id)?;
            if let Some(mut action) = delegation.active_action.take() {
                action.finish(AgentActionState::Completed, now);
                delegation.last_action = Some(action);
            }
            delegation.status = AgentStatus::Active;
            delegation.updated_at = now;
            (delegation.clone(), delegation.last_action.clone(), command)
        };
        let event = self.event_for(
            &delegation,
            AgentEventType::AgentCommandCompleted,
            outcome,
            AgentEventDetails {
                action,
                command: Some(command),
                metadata,
                ..AgentEventDetails::default()
            },
            now,
        );
        self.record(event.clone());
        Ok(event)
    }

    pub fn fail_command(
        &mut self,
        agent_id: AgentId,
        command: AgentCommandRequest,
        error: impl Into<String>,
        metadata: Option<Value>,
    ) -> Result<AgentLogEvent, AgentControlError> {
        let now = Utc::now();
        let (delegation, action, command) = {
            let delegation = self.delegation_mut(agent_id)?;
            if let Some(mut action) = delegation.active_action.take() {
                action.finish(AgentActionState::Failed, now);
                delegation.last_action = Some(action);
            }
            delegation.status = AgentStatus::Active;
            delegation.updated_at = now;
            (delegation.clone(), delegation.last_action.clone(), command)
        };
        let event = self.event_for(
            &delegation,
            AgentEventType::AgentCommandFailed,
            error,
            AgentEventDetails {
                action,
                command: Some(command),
                metadata,
                ..AgentEventDetails::default()
            },
            now,
        );
        self.record(event.clone());
        Ok(event)
    }

    pub fn complete_agent(
        &mut self,
        agent_id: AgentId,
        outcome: impl Into<String>,
    ) -> Result<AgentLogEvent, AgentControlError> {
        let now = Utc::now();
        let (delegation, action) = {
            let delegation = self.delegation_mut(agent_id)?;
            if let Some(mut action) = delegation.active_action.take() {
                action.finish(AgentActionState::Completed, now);
                delegation.last_action = Some(action);
            }
            delegation.status = AgentStatus::Completed;
            delegation.updated_at = now;
            delegation.completed_at = Some(now);
            (delegation.clone(), delegation.last_action.clone())
        };
        let event = self.event_for(
            &delegation,
            AgentEventType::AgentCompleted,
            outcome,
            AgentEventDetails {
                action,
                ..AgentEventDetails::default()
            },
            now,
        );
        self.record(event.clone());
        Ok(event)
    }

    pub fn fail_agent(
        &mut self,
        agent_id: AgentId,
        error: impl Into<String>,
    ) -> Result<AgentLogEvent, AgentControlError> {
        let now = Utc::now();
        let (delegation, action) = {
            let delegation = self.delegation_mut(agent_id)?;
            if let Some(mut action) = delegation.active_action.take() {
                action.finish(AgentActionState::Failed, now);
                delegation.last_action = Some(action);
            }
            delegation.status = AgentStatus::Failed;
            delegation.updated_at = now;
            delegation.completed_at = Some(now);
            (delegation.clone(), delegation.last_action.clone())
        };
        let event = self.event_for(
            &delegation,
            AgentEventType::AgentFailed,
            error,
            AgentEventDetails {
                action,
                ..AgentEventDetails::default()
            },
            now,
        );
        self.record(event.clone());
        Ok(event)
    }

    pub fn revoke_agent(
        &mut self,
        agent_id: AgentId,
        reason: impl Into<String>,
    ) -> Result<AgentLogEvent, AgentControlError> {
        let now = Utc::now();
        let (delegation, action) = {
            let delegation = self.delegation_mut(agent_id)?;
            delegation.active_action = None;
            delegation.status = AgentStatus::Revoked;
            delegation.updated_at = now;
            delegation.completed_at = Some(now);
            (delegation.clone(), delegation.last_action.clone())
        };
        let event = self.event_for(
            &delegation,
            AgentEventType::AgentRevoked,
            reason,
            AgentEventDetails {
                action,
                ..AgentEventDetails::default()
            },
            now,
        );
        self.record(event.clone());
        Ok(event)
    }

    pub fn delegation(&self, agent_id: AgentId) -> Option<&AgentDelegation> {
        self.delegations.get(&agent_id)
    }

    pub fn active_for_window(&self, window_id: WindowId) -> Vec<&AgentDelegation> {
        self.delegations
            .values()
            .filter(|delegation| {
                delegation.window_id == window_id && delegation.status == AgentStatus::Active
            })
            .collect()
    }

    pub fn events(&self) -> &[AgentLogEvent] {
        &self.events
    }

    pub fn delegation_count(&self) -> usize {
        self.delegations.len()
    }

    pub fn max_delegations(&self) -> usize {
        self.max_delegations
    }

    pub fn max_events(&self) -> usize {
        self.max_events
    }

    fn delegation_mut(
        &mut self,
        agent_id: AgentId,
    ) -> Result<&mut AgentDelegation, AgentControlError> {
        self.delegations
            .get_mut(&agent_id)
            .ok_or(AgentControlError::UnknownAgent(agent_id))
    }

    fn event_for(
        &mut self,
        delegation: &AgentDelegation,
        event_type: AgentEventType,
        message: impl Into<String>,
        details: AgentEventDetails,
        occurred_at: DateTime<Utc>,
    ) -> AgentLogEvent {
        let sequence = self.next_sequence;
        self.next_sequence += 1;
        AgentLogEvent {
            schema: AGENT_EVENT_SCHEMA,
            schema_version: AGENT_EVENT_SCHEMA_VERSION,
            event_id: Uuid::now_v7(),
            agent_id: delegation.agent_id,
            window_id: delegation.window_id,
            sequence,
            occurred_at,
            event_type,
            message: bounded_agent_text(message),
            task: details.task,
            action: details.action,
            command: details.command,
            metadata: details.metadata.map(bounded_agent_metadata),
            state: delegation.snapshot(),
        }
    }

    fn record(&mut self, event: AgentLogEvent) {
        self.events.push(event);
        let overflow = self.events.len().saturating_sub(self.max_events);
        if overflow > 0 {
            self.events.drain(0..overflow);
        }
    }

    fn enforce_delegation_limit(&mut self) {
        while self.delegations.len() > self.max_delegations {
            let removable = self
                .delegations
                .iter()
                .filter(|(_, delegation)| {
                    matches!(
                        delegation.status,
                        AgentStatus::Completed | AgentStatus::Failed | AgentStatus::Revoked
                    )
                })
                .min_by_key(|(_, delegation)| delegation.updated_at)
                .map(|(agent_id, _)| *agent_id)
                .or_else(|| self.delegations.keys().next().copied());
            let Some(agent_id) = removable else {
                break;
            };
            self.delegations.remove(&agent_id);
        }
    }
}

fn bounded_agent_text(input: impl Into<String>) -> String {
    truncate_utf8(input.into(), DEFAULT_MAX_AGENT_TEXT_BYTES)
}

fn truncate_utf8(input: String, max_bytes: usize) -> String {
    if input.len() <= max_bytes {
        return input;
    }
    let mut end = max_bytes;
    while !input.is_char_boundary(end) {
        end -= 1;
    }
    format!("{}...[truncated]", &input[..end])
}

fn bounded_agent_metadata(value: Value) -> Value {
    let bounded = bounded_agent_metadata_value(value, 0);
    let Ok(serialized) = serde_json::to_vec(&bounded) else {
        return json!({ "truncated": true, "reason": "metadata serialization failed" });
    };
    if serialized.len() <= DEFAULT_MAX_AGENT_METADATA_BYTES {
        return bounded;
    }
    json!({
        "truncated": true,
        "reason": "metadata byte cap exceeded",
        "bounded_bytes": serialized.len(),
        "max_bytes": DEFAULT_MAX_AGENT_METADATA_BYTES
    })
}

fn bounded_agent_metadata_value(value: Value, depth: usize) -> Value {
    if depth >= DEFAULT_MAX_AGENT_METADATA_DEPTH {
        return json!({ "truncated": true, "reason": "metadata depth cap exceeded" });
    }
    match value {
        Value::String(value) => Value::String(bounded_agent_text(value)),
        Value::Array(values) => {
            let original_len = values.len();
            let mut values = values
                .into_iter()
                .take(DEFAULT_MAX_AGENT_METADATA_ITEMS)
                .map(|value| bounded_agent_metadata_value(value, depth + 1))
                .collect::<Vec<_>>();
            if original_len > DEFAULT_MAX_AGENT_METADATA_ITEMS {
                values.push(json!({
                    "truncated": true,
                    "omitted_items": original_len - DEFAULT_MAX_AGENT_METADATA_ITEMS
                }));
            }
            Value::Array(values)
        }
        Value::Object(values) => {
            let original_len = values.len();
            let mut bounded = Map::new();
            for (key, value) in values.into_iter().take(DEFAULT_MAX_AGENT_METADATA_ITEMS) {
                bounded.insert(
                    truncate_utf8(key, 128),
                    bounded_agent_metadata_value(value, depth + 1),
                );
            }
            if original_len > DEFAULT_MAX_AGENT_METADATA_ITEMS {
                bounded.insert(
                    "truncated".to_string(),
                    json!({ "omitted_items": original_len - DEFAULT_MAX_AGENT_METADATA_ITEMS }),
                );
            }
            Value::Object(bounded)
        }
        value => value,
    }
}

#[derive(Debug, Clone)]
pub struct AgentLogStore {
    path: PathBuf,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AgentLogDiagnostics {
    pub bytes: u64,
    pub max_bytes: u64,
    pub archives: usize,
    pub max_archives: usize,
    pub total_archive_bytes: u64,
    pub max_total_archive_bytes: u64,
    pub largest_archive_bytes: u64,
    pub max_archive_bytes: u64,
}

impl AgentLogStore {
    pub fn new(path: Option<PathBuf>) -> anyhow::Result<Self> {
        let path = match path {
            Some(path) => path,
            None => default_agent_log_path()?,
        };
        Ok(Self { path })
    }

    pub fn path(&self) -> &Path {
        &self.path
    }

    pub fn diagnostics(&self) -> anyhow::Result<AgentLogDiagnostics> {
        let archive_paths = rotated_log_paths(&self.path)?;
        let mut total_archive_bytes = 0_u64;
        let mut largest_archive_bytes = 0_u64;
        for path in &archive_paths {
            let bytes = fs::metadata(path)
                .with_context(|| format!("failed to inspect agent log archive {}", path.display()))?
                .len();
            total_archive_bytes = total_archive_bytes.saturating_add(bytes);
            largest_archive_bytes = largest_archive_bytes.max(bytes);
        }

        Ok(AgentLogDiagnostics {
            bytes: fs::metadata(&self.path)
                .map(|metadata| metadata.len())
                .unwrap_or(0),
            max_bytes: DEFAULT_MAX_AGENT_LOG_BYTES,
            archives: archive_paths.len(),
            max_archives: DEFAULT_MAX_AGENT_LOG_ARCHIVES,
            total_archive_bytes,
            max_total_archive_bytes: DEFAULT_MAX_AGENT_LOG_BYTES
                .saturating_mul(DEFAULT_MAX_AGENT_LOG_ARCHIVES as u64),
            largest_archive_bytes,
            max_archive_bytes: DEFAULT_MAX_AGENT_LOG_BYTES,
        })
    }

    pub fn append(&self, event: &AgentLogEvent) -> anyhow::Result<()> {
        self.append_all(std::slice::from_ref(event))
    }

    pub fn append_all(&self, events: &[AgentLogEvent]) -> anyhow::Result<()> {
        if events.is_empty() {
            return Ok(());
        }
        if let Some(parent) = self.path.parent() {
            fs::create_dir_all(parent).with_context(|| {
                format!("failed to create agent log directory {}", parent.display())
            })?;
        }
        for event in events {
            let line = serialized_agent_event_line(event)?;
            self.rotate_before_append(line.len() as u64)?;
            let mut file = OpenOptions::new()
                .create(true)
                .append(true)
                .open(&self.path)
                .with_context(|| format!("failed to open agent log {}", self.path.display()))?;
            file.write_all(&line)
                .context("failed to write agent event")?;
        }
        self.rotate_if_needed()?;
        Ok(())
    }

    fn rotate_before_append(&self, additional_bytes: u64) -> anyhow::Result<()> {
        let current = fs::metadata(&self.path)
            .map(|metadata| metadata.len())
            .unwrap_or(0);
        if current > 0 && current.saturating_add(additional_bytes) > DEFAULT_MAX_AGENT_LOG_BYTES {
            self.rotate_active_log()?;
        }
        Ok(())
    }

    fn rotate_if_needed(&self) -> anyhow::Result<()> {
        let Ok(metadata) = fs::metadata(&self.path) else {
            return Ok(());
        };
        if metadata.len() <= DEFAULT_MAX_AGENT_LOG_BYTES {
            return Ok(());
        }

        self.rotate_active_log()
    }

    fn rotate_active_log(&self) -> anyhow::Result<()> {
        let rotated = rotated_log_path(&self.path);
        fs::rename(&self.path, &rotated).with_context(|| {
            format!(
                "failed to rotate agent log {} to {}",
                self.path.display(),
                rotated.display()
            )
        })?;
        prune_rotated_logs(&self.path)
    }
}

fn serialized_agent_event_line(event: &AgentLogEvent) -> anyhow::Result<Vec<u8>> {
    let mut line = serde_json::to_vec(event).context("failed to serialize agent event")?;
    if line.len().saturating_add(1) > DEFAULT_MAX_AGENT_LOG_BYTES as usize {
        line = serde_json::to_vec(&json!({
            "schema": event.schema,
            "schema_version": event.schema_version,
            "event_id": event.event_id,
            "agent_id": event.agent_id,
            "window_id": event.window_id,
            "sequence": event.sequence,
            "occurred_at": event.occurred_at,
            "event_type": event.event_type,
            "message": "agent event exceeded log byte cap and was summarized",
            "truncated": true,
            "original_bytes": line.len()
        }))
        .context("failed to serialize summarized agent event")?;
    }
    line.push(b'\n');
    Ok(line)
}

fn rotated_log_path(path: &Path) -> PathBuf {
    let parent = path.parent().unwrap_or_else(|| Path::new("."));
    let stem = path
        .file_stem()
        .and_then(|stem| stem.to_str())
        .unwrap_or("agent-events");
    let extension = path.extension().and_then(|extension| extension.to_str());
    let suffix = Uuid::now_v7();
    let timestamp = Utc::now().format("%Y%m%dT%H%M%SZ");
    let file_name = match extension {
        Some(extension) if !extension.is_empty() => {
            format!("{stem}-{timestamp}-{suffix}.{extension}")
        }
        _ => format!("{stem}-{timestamp}-{suffix}"),
    };
    parent.join(file_name)
}

fn rotated_log_paths(path: &Path) -> anyhow::Result<Vec<PathBuf>> {
    let Some(parent) = path.parent() else {
        return Ok(Vec::new());
    };
    if !parent.exists() {
        return Ok(Vec::new());
    }
    let stem = path
        .file_stem()
        .and_then(|stem| stem.to_str())
        .unwrap_or("agent-events");
    let extension = path.extension().and_then(|extension| extension.to_str());
    let prefix = format!("{stem}-");
    let suffix = extension.map(|extension| format!(".{extension}"));
    let mut paths = Vec::new();
    for entry in fs::read_dir(parent)
        .with_context(|| format!("failed to read agent log directory {}", parent.display()))?
    {
        let entry = entry?;
        let path = entry.path();
        if !path.is_file() {
            continue;
        }
        let Some(name) = path.file_name().and_then(|name| name.to_str()) else {
            continue;
        };
        if !name.starts_with(&prefix) {
            continue;
        }
        if let Some(suffix) = &suffix
            && !name.ends_with(suffix)
        {
            continue;
        }
        paths.push(path);
    }
    paths.sort();
    Ok(paths)
}

fn prune_rotated_logs(path: &Path) -> anyhow::Result<()> {
    let mut paths = rotated_log_paths(path)?;
    let overflow = paths.len().saturating_sub(DEFAULT_MAX_AGENT_LOG_ARCHIVES);
    for path in paths.drain(0..overflow) {
        fs::remove_file(&path)
            .with_context(|| format!("failed to remove old agent log {}", path.display()))?;
    }
    Ok(())
}

fn default_agent_log_path() -> anyhow::Result<PathBuf> {
    let dirs = ProjectDirs::from("dev", "Cephas", "Cephas")
        .context("could not determine platform agent log directory")?;
    Ok(dirs.data_local_dir().join("agent-events.jsonl"))
}

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

    #[test]
    fn delegation_requires_ai_mode() {
        let mut control = AgentControl::new(10);
        let err = control
            .delegate_window(false, WindowId(1), "test-agent", "Handle window")
            .unwrap_err();

        assert_eq!(err, AgentControlError::AiModeDisabled);
        assert!(control.events().is_empty());
    }

    #[test]
    fn agent_lifecycle_logs_state_snapshots() {
        let mut control = AgentControl::new(10);
        let tasked = control
            .delegate_window(true, WindowId(1), "test-agent", "Handle window")
            .unwrap();
        let agent_id = tasked.agent_id;

        assert_eq!(agent_id.as_uuid().get_version_num(), 7);
        assert_eq!(tasked.event_id.get_version_num(), 7);
        assert_eq!(tasked.event_type, AgentEventType::AgentTasked);
        assert_eq!(tasked.state.status, AgentStatus::Tasked);

        let active = control
            .start_action(
                agent_id,
                "navigate",
                Some("Agent may control this window".to_string()),
            )
            .unwrap();
        assert_eq!(active.event_type, AgentEventType::AgentActive);
        assert_eq!(active.state.status, AgentStatus::Active);
        assert_eq!(
            active.state.active_action.as_ref().unwrap().state,
            AgentActionState::Running
        );

        let log = control
            .append_log(
                agent_id,
                "agent is waiting for backend instructions",
                Some(json!({ "backend": "elixir" })),
            )
            .unwrap();
        assert_eq!(log.event_type, AgentEventType::AgentLog);
        assert_eq!(log.state.status, AgentStatus::Active);

        let completed = control.complete_agent(agent_id, "done").unwrap();
        assert_eq!(completed.event_type, AgentEventType::AgentCompleted);
        assert_eq!(completed.state.status, AgentStatus::Completed);
        assert!(completed.state.active_action.is_none());
        assert_eq!(
            completed.state.last_action.as_ref().unwrap().state,
            AgentActionState::Completed
        );
        assert_eq!(control.events().len(), 4);
    }

    #[test]
    fn delegations_are_capped() {
        let mut control = AgentControl::with_limits(10, 2);
        let first = control
            .delegate_window(true, WindowId(1), "agent-1", "Handle window")
            .unwrap()
            .agent_id;
        control
            .delegate_window(true, WindowId(1), "agent-2", "Handle window")
            .unwrap();
        control
            .delegate_window(true, WindowId(1), "agent-3", "Handle window")
            .unwrap();

        assert_eq!(control.delegation_count(), 2);
        assert_eq!(control.max_delegations(), 2);
        assert!(control.delegation(first).is_none());
    }

    #[test]
    fn agent_payloads_are_bounded_before_retention() {
        let mut control = AgentControl::new(10);
        let large = "x".repeat(DEFAULT_MAX_AGENT_TEXT_BYTES * 3);
        let tasked = control
            .delegate_window(true, WindowId(1), large.clone(), large.clone())
            .unwrap();
        let agent_id = tasked.agent_id;

        assert!(tasked.state.task.description.len() < large.len());
        assert!(
            control
                .delegation(agent_id)
                .unwrap()
                .agent_label
                .contains("[truncated]")
        );

        let log = control
            .append_log(
                agent_id,
                large.clone(),
                Some(json!({
                    "large": large,
                    "items": (0..(DEFAULT_MAX_AGENT_METADATA_ITEMS + 8)).collect::<Vec<_>>()
                })),
            )
            .unwrap();

        assert!(log.message.contains("[truncated]"));
        let metadata = log.metadata.unwrap();
        assert!(serde_json::to_vec(&metadata).unwrap().len() <= DEFAULT_MAX_AGENT_METADATA_BYTES);
        assert!(metadata["large"].as_str().unwrap().contains("[truncated]"));
        assert!(
            metadata["items"].as_array().unwrap().len() <= DEFAULT_MAX_AGENT_METADATA_ITEMS + 1
        );
    }

    #[test]
    fn agent_command_manifest_is_unique_and_resolvable() {
        let mut ids = std::collections::BTreeSet::new();
        for entry in agent_command_manifest() {
            assert!(ids.insert(entry.id));
            let command = AgentCommandKind::from_id(entry.id).unwrap();
            assert_eq!(command.label(), entry.label);
            assert_eq!(command.description(), entry.description);
            assert_eq!(command.requires_target(), entry.requires_target);
        }
    }

    #[test]
    fn agent_commands_validate_targets_and_log_lifecycle() {
        let mut control = AgentControl::new(10);
        let tasked = control
            .delegate_window(true, WindowId(1), "test-agent", "Handle window")
            .unwrap();
        let agent_id = tasked.agent_id;

        let missing = control
            .queue_command(true, agent_id, AgentCommandKind::Navigate, None)
            .unwrap_err();
        assert_eq!(
            missing,
            AgentControlError::InvalidCommandTarget(AgentCommandKind::Navigate)
        );

        let queued = control
            .queue_command(
                true,
                agent_id,
                AgentCommandKind::Navigate,
                Some("https://example.com".to_string()),
            )
            .unwrap();
        assert_eq!(queued.event_type, AgentEventType::AgentCommandQueued);
        assert_eq!(
            queued.command.as_ref().unwrap().kind,
            AgentCommandKind::Navigate
        );
        assert_eq!(
            queued
                .command
                .as_ref()
                .unwrap()
                .command_id
                .get_version_num(),
            7
        );
        assert_eq!(queued.state.status, AgentStatus::Active);
        assert_eq!(
            queued.state.active_action.as_ref().unwrap().state,
            AgentActionState::Running
        );

        let completed = control
            .complete_command(
                agent_id,
                queued.command.clone().unwrap(),
                "navigation executed",
                Some(json!({ "result": "ok" })),
            )
            .unwrap();
        assert_eq!(completed.event_type, AgentEventType::AgentCommandCompleted);
        assert_eq!(completed.state.status, AgentStatus::Active);
        assert!(completed.state.active_action.is_none());
        assert_eq!(
            completed.state.last_action.as_ref().unwrap().state,
            AgentActionState::Completed
        );
    }

    #[test]
    fn jsonl_store_writes_parseable_events() {
        let dir = tempfile::tempdir().unwrap();
        let store = AgentLogStore::new(Some(dir.path().join("agents.jsonl"))).unwrap();
        let mut control = AgentControl::new(10);
        let event = control
            .delegate_window(true, WindowId(7), "test-agent", "Handle window")
            .unwrap();

        store.append(&event).unwrap();

        let raw = fs::read_to_string(store.path()).unwrap();
        let lines = raw.lines().collect::<Vec<_>>();
        assert_eq!(lines.len(), 1);
        let json: Value = serde_json::from_str(lines[0]).unwrap();
        assert_eq!(json["schema"], AGENT_EVENT_SCHEMA);
        assert_eq!(json["schema_version"], AGENT_EVENT_SCHEMA_VERSION);
        assert_eq!(json["event_type"], "agent_tasked");
        assert_eq!(json["window_id"], 7);
        assert_eq!(json["state"]["status"], "tasked");
    }

    #[test]
    fn jsonl_store_rotates_oversized_logs() {
        let dir = tempfile::tempdir().unwrap();
        let store = AgentLogStore::new(Some(dir.path().join("agents.jsonl"))).unwrap();
        fs::write(
            store.path(),
            vec![b'x'; DEFAULT_MAX_AGENT_LOG_BYTES as usize + 1],
        )
        .unwrap();
        let mut control = AgentControl::new(10);
        let event = control
            .delegate_window(true, WindowId(7), "test-agent", "Handle window")
            .unwrap();

        store.append(&event).unwrap();

        let diagnostics = store.diagnostics().unwrap();
        assert!(diagnostics.bytes < DEFAULT_MAX_AGENT_LOG_BYTES);
        assert_eq!(diagnostics.archives, 1);
        assert_eq!(diagnostics.max_archives, DEFAULT_MAX_AGENT_LOG_ARCHIVES);
        assert!(diagnostics.total_archive_bytes > DEFAULT_MAX_AGENT_LOG_BYTES);
        assert_eq!(
            diagnostics.max_total_archive_bytes,
            DEFAULT_MAX_AGENT_LOG_BYTES * DEFAULT_MAX_AGENT_LOG_ARCHIVES as u64
        );
        assert_eq!(
            diagnostics.largest_archive_bytes,
            diagnostics.total_archive_bytes
        );
        assert_eq!(diagnostics.max_archive_bytes, DEFAULT_MAX_AGENT_LOG_BYTES);
    }

    #[test]
    fn jsonl_store_summarizes_single_events_over_byte_cap() {
        let dir = tempfile::tempdir().unwrap();
        let store = AgentLogStore::new(Some(dir.path().join("agents.jsonl"))).unwrap();
        let mut control = AgentControl::new(10);
        let mut event = control
            .delegate_window(true, WindowId(7), "test-agent", "Handle window")
            .unwrap();
        event.message = "x".repeat(DEFAULT_MAX_AGENT_LOG_BYTES as usize + 1);

        store.append(&event).unwrap();

        let diagnostics = store.diagnostics().unwrap();
        assert!(diagnostics.bytes < DEFAULT_MAX_AGENT_LOG_BYTES);
        let raw = fs::read_to_string(store.path()).unwrap();
        let json: Value = serde_json::from_str(raw.lines().next().unwrap()).unwrap();
        assert_eq!(json["truncated"], true);
        assert!(json["original_bytes"].as_u64().unwrap() > DEFAULT_MAX_AGENT_LOG_BYTES);
    }
}
