//! Projection stores for role-based observability views.
//!
//! This module provides different views of session data:
//! - Full audit view (internal/compliance)
//! - Redacted view (user-facing, sensitive data removed)
//! - Summary view (quick status for UI)

use crate::events::{AgentEvent, AgentEventKind, DurableEvent, EventOrigin};
use crate::session::error::SessionResult;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use time::OffsetDateTime;
use typeshare::typeshare;

use super::domain::{Artifact, Decision, Delegation, IntentSnapshot, ProgressEntry, Task};

/// Full audit view with all details for internal/compliance use
#[typeshare]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuditView {
    pub session_id: String,
    pub events: Vec<AgentEvent>,
    pub tasks: Vec<Task>,
    pub intent_snapshots: Vec<IntentSnapshot>,
    pub decisions: Vec<Decision>,
    pub progress_entries: Vec<ProgressEntry>,
    pub artifacts: Vec<Artifact>,
    pub delegations: Vec<Delegation>,
    #[serde(with = "time::serde::rfc3339")]
    #[typeshare(serialized_as = "string")]
    pub generated_at: OffsetDateTime,
}

/// Redacted view for user-facing display (sensitive data removed)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RedactedView {
    pub session_id: String,
    pub current_intent: Option<String>,
    pub active_task: Option<RedactedTask>,
    pub recent_progress: Vec<RedactedProgress>,
    pub artifacts: Vec<RedactedArtifact>,
    #[serde(with = "time::serde::rfc3339")]
    pub generated_at: OffsetDateTime,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RedactedTask {
    pub id: String,
    pub status: String,
    pub expected_deliverable: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RedactedProgress {
    pub kind: String,
    pub summary: String,
    #[serde(with = "time::serde::rfc3339")]
    pub created_at: OffsetDateTime,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RedactedArtifact {
    pub kind: String,
    pub summary: Option<String>,
    #[serde(with = "time::serde::rfc3339")]
    pub created_at: OffsetDateTime,
}

/// Summary view for quick status display
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SummaryView {
    pub session_id: String,
    pub current_intent: Option<String>,
    pub active_task_status: Option<String>,
    pub progress_count: usize,
    pub artifact_count: usize,
    pub decision_count: usize,
    pub last_activity: Option<String>,
    #[serde(with = "time::serde::rfc3339")]
    pub generated_at: OffsetDateTime,
}

/// Redaction policy for controlling what information is shown
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RedactionPolicy {
    /// Show everything (internal use)
    None,
    /// Hide sensitive tool arguments and results
    Sensitive,
    /// Show only high-level summaries
    Minimal,
}

// ============================================================================
// EventJournal — durable event persistence trait
// ============================================================================

/// Input for appending a new durable event to the journal.
#[derive(Debug, Clone)]
pub struct NewDurableEvent {
    pub session_id: String,
    pub origin: EventOrigin,
    pub source_node: Option<String>,
    pub kind: AgentEventKind,
}

/// Durable event journal — single source of truth for event ordering.
///
/// Sequence assignment is the authority of the journal implementation.
/// Producers never set `stream_seq`; it is assigned atomically on append.
#[async_trait]
pub trait EventJournal: Send + Sync {
    /// Persist a durable event and return it with assigned `event_id` and `stream_seq`.
    async fn append_durable(&self, event: &NewDurableEvent) -> SessionResult<DurableEvent>;

    /// Load the durable event stream for a session, optionally starting after a cursor.
    async fn load_session_stream(
        &self,
        session_id: &str,
        after_seq: Option<i64>,
        limit: Option<usize>,
    ) -> SessionResult<Vec<DurableEvent>>;

    /// Load events across all sessions (global stream), after a given sequence.
    async fn load_global_stream(
        &self,
        after_seq: Option<i64>,
        limit: Option<usize>,
    ) -> SessionResult<Vec<DurableEvent>>;

    /// Delete the event at `from_seq` and all subsequent events for a session.
    /// Used by undo cleanup to prune the event journal when the user commits
    /// a new prompt after an undo so that the reverted turns do not reappear
    /// on page reload.
    ///
    /// Returns the number of deleted rows.
    async fn delete_session_events_from(
        &self,
        session_id: &str,
        from_seq: i64,
    ) -> SessionResult<usize>;
}

/// View generation (read-only projections)
#[async_trait]
pub trait ViewStore: Send + Sync {
    /// Generate a full audit view for a session
    ///
    /// # Arguments
    /// * `session_id` - The session to get the audit view for
    /// * `include_children` - Whether to include child session events (e.g., delegations)
    ///   Set to `true` for complete trajectory exports, `false` for UI rendering
    ///   (UI subscribes to child sessions separately)
    async fn get_audit_view(
        &self,
        session_id: &str,
        include_children: bool,
    ) -> SessionResult<AuditView>;

    /// Generate a redacted view for a session
    async fn get_redacted_view(
        &self,
        session_id: &str,
        policy: RedactionPolicy,
    ) -> SessionResult<RedactedView>;

    /// Generate a summary view for a session
    async fn get_summary_view(&self, session_id: &str) -> SessionResult<SummaryView>;

    /// Generate a session list view with optional filtering
    /// If filter is None, returns all sessions (up to default limit)
    /// Sessions are grouped by CWD and sorted by latest activity
    async fn get_session_list_view(
        &self,
        filter: Option<SessionListFilter>,
    ) -> SessionResult<SessionListView>;

    /// Browse workspace groups with preview sessions.
    async fn browse_session_groups(
        &self,
        cursor: Option<String>,
        group_limit: usize,
        session_limit_per_group: usize,
        session_scope: SessionScope,
    ) -> SessionResult<(Vec<SessionGroup>, Option<String>, usize)>;

    /// Page sessions for one workspace group.
    async fn list_group_sessions(
        &self,
        cwd: Option<String>,
        cursor: Option<String>,
        limit: usize,
        session_scope: SessionScope,
    ) -> SessionResult<(SessionGroup, usize)>;

    /// Search all sessions via FTS5 (DB-wide, not limited to loaded pages).
    async fn search_sessions(
        &self,
        query: String,
        cursor: Option<String>,
        limit: usize,
        session_scope: SessionScope,
    ) -> SessionResult<(Vec<SessionGroup>, Option<String>, usize)>;

    /// Page direct user-fork children for one parent/root session.
    async fn list_session_children(
        &self,
        parent_session_id: String,
        cursor: Option<String>,
        limit: usize,
    ) -> SessionResult<(SessionGroup, usize)>;

    /// Export session as ATIF (Agent Trajectory Interchange Format)
    async fn get_atif(
        &self,
        session_id: &str,
        options: &crate::export::AtifExportOptions,
    ) -> SessionResult<crate::export::ATIF>;

    /// Get recent models from full event history, grouped by workspace
    /// Returns all models ever used, sorted by last_used descending
    ///
    /// # Arguments
    /// * `limit_per_workspace` - Maximum number of recent models to return per workspace
    async fn get_recent_models_view(
        &self,
        limit_per_workspace: usize,
    ) -> SessionResult<RecentModelsView>;
}

/// Helper trait for redacting sensitive content
pub trait Redactor {
    /// Redact a string based on policy
    fn redact(&self, content: &str, policy: RedactionPolicy) -> String;

    /// Check if a field should be included based on policy
    fn should_include(&self, field_sensitivity: FieldSensitivity, policy: RedactionPolicy) -> bool;
}

/// Field sensitivity classification
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum FieldSensitivity {
    /// Public information
    Public,
    /// Internal details
    Internal,
    /// Sensitive data (credentials, PII, etc.)
    Sensitive,
}

/// Default redactor implementation
pub struct DefaultRedactor;

impl Redactor for DefaultRedactor {
    fn redact(&self, content: &str, policy: RedactionPolicy) -> String {
        match policy {
            RedactionPolicy::None => content.to_string(),
            RedactionPolicy::Sensitive => {
                // Simple redaction: replace with placeholder if looks sensitive
                if content.contains("password")
                    || content.contains("token")
                    || content.contains("secret")
                    || content.contains("api_key")
                {
                    "[REDACTED]".to_string()
                } else {
                    content.to_string()
                }
            }
            RedactionPolicy::Minimal => {
                // Only show first 100 chars as summary
                querymt_utils::str_utils::truncate_with_ellipsis(content, 100)
            }
        }
    }

    fn should_include(&self, field_sensitivity: FieldSensitivity, policy: RedactionPolicy) -> bool {
        match policy {
            RedactionPolicy::None => true,
            RedactionPolicy::Sensitive => field_sensitivity < FieldSensitivity::Sensitive,
            RedactionPolicy::Minimal => field_sensitivity == FieldSensitivity::Public,
        }
    }
}

// ============================================================================
// Session List View - for UI splash screen / session picker
// ============================================================================

/// Generic field predicate for filtering
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FieldPredicate {
    pub field: String,
    pub op: PredicateOp,
}

/// Predicate operations for filtering
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "op", content = "value", rename_all = "snake_case")]
pub enum PredicateOp {
    Eq(serde_json::Value),
    Ne(serde_json::Value),
    Gt(serde_json::Value),
    Gte(serde_json::Value),
    Lt(serde_json::Value),
    Lte(serde_json::Value),
    Contains(String),
    StartsWith(String),
    IsNull,
    IsNotNull,
    In(Vec<serde_json::Value>),
}

/// Filter expression with boolean logic
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum FilterExpr {
    Predicate(FieldPredicate),
    And(Vec<FilterExpr>),
    Or(Vec<FilterExpr>),
    Not(Box<FilterExpr>),
}

/// Filter for session list queries
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct SessionListFilter {
    pub filter: Option<FilterExpr>,
    pub limit: Option<usize>,
    pub offset: Option<usize>,
}

#[typeshare]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SessionScope {
    #[default]
    All,
    Root,
    Forks,
    Delegates,
    Children,
}

impl SessionScope {
    pub fn from_option(value: Option<String>) -> Self {
        match value.as_deref() {
            Some("root") => Self::Root,
            Some("forks") => Self::Forks,
            Some("delegates") => Self::Delegates,
            Some("children") => Self::Children,
            _ => Self::All,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionListItem {
    pub session_id: String,
    pub name: Option<String>,
    pub cwd: Option<String>,
    pub title: Option<String>,
    #[serde(with = "time::serde::rfc3339::option")]
    pub created_at: Option<OffsetDateTime>,
    #[serde(with = "time::serde::rfc3339::option")]
    pub updated_at: Option<OffsetDateTime>,
    /// Public ID of the parent session (if this is a child session)
    pub parent_session_id: Option<String>,
    /// Fork origin: "user" or "delegation"
    pub fork_origin: Option<String>,
    /// Session kind (e.g. "recurring") for specialized UI badges.
    pub session_kind: Option<String>,
    /// Whether this session has child sessions
    pub has_children: bool,
    pub fork_count: usize,
}

/// Group of sessions by CWD
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionGroup {
    pub cwd: Option<String>,
    pub sessions: Vec<SessionListItem>,
    #[serde(with = "time::serde::rfc3339::option")]
    pub latest_activity: Option<OffsetDateTime>,
    pub total_count: Option<usize>,
    pub next_cursor: Option<String>,
}

/// Session list view with grouping
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionListView {
    pub groups: Vec<SessionGroup>,
    pub total_count: usize,
    #[serde(with = "time::serde::rfc3339")]
    pub generated_at: OffsetDateTime,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionGroupTotal {
    pub cwd: Option<String>,
    pub total_count: usize,
    #[serde(with = "time::serde::rfc3339::option")]
    pub latest_activity: Option<OffsetDateTime>,
}

/// Recent model usage entry from event history
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RecentModelEntry {
    pub provider: String,
    pub model: String,
    #[serde(with = "time::serde::rfc3339")]
    pub last_used: OffsetDateTime,
    pub use_count: u32,
}

/// Recent models view grouped by workspace
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RecentModelsView {
    /// Models grouped by workspace (cwd path string, or None for no-workspace sessions)
    pub by_workspace: std::collections::HashMap<Option<String>, Vec<RecentModelEntry>>,
    #[serde(with = "time::serde::rfc3339")]
    pub generated_at: OffsetDateTime,
}

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

    fn now() -> OffsetDateTime {
        OffsetDateTime::now_utc()
    }

    // ── RedactionPolicy ──────────────────────────────────────────────────────

    #[test]
    fn test_redaction_policy_variants_compare() {
        assert_eq!(RedactionPolicy::None, RedactionPolicy::None);
        assert_ne!(RedactionPolicy::None, RedactionPolicy::Sensitive);
        assert_ne!(RedactionPolicy::Sensitive, RedactionPolicy::Minimal);
    }

    // ── DefaultRedactor ──────────────────────────────────────────────────────

    #[test]
    fn test_default_redactor_none_policy_passes_through() {
        let r = DefaultRedactor;
        assert_eq!(
            r.redact("hello world", RedactionPolicy::None),
            "hello world"
        );
    }

    #[test]
    fn test_default_redactor_sensitive_redacts_keywords() {
        let r = DefaultRedactor;
        assert_eq!(
            r.redact("my password is 123", RedactionPolicy::Sensitive),
            "[REDACTED]"
        );
        assert_eq!(
            r.redact("token: abc123", RedactionPolicy::Sensitive),
            "[REDACTED]"
        );
        assert_eq!(
            r.redact("api_key=xyz", RedactionPolicy::Sensitive),
            "[REDACTED]"
        );
        assert_eq!(
            r.redact("secret stuff", RedactionPolicy::Sensitive),
            "[REDACTED]"
        );
    }

    #[test]
    fn test_default_redactor_sensitive_passes_non_sensitive() {
        let r = DefaultRedactor;
        let safe = "this is fine content";
        assert_eq!(r.redact(safe, RedactionPolicy::Sensitive), safe);
    }

    #[test]
    fn test_default_redactor_minimal_truncates_long_content() {
        let r = DefaultRedactor;
        let long_content = "a".repeat(200);
        let result = r.redact(&long_content, RedactionPolicy::Minimal);
        assert!(result.ends_with("..."));
        assert!(result.len() <= 103);
    }

    #[test]
    fn test_default_redactor_minimal_passes_short_content() {
        let r = DefaultRedactor;
        let short = "short text";
        assert_eq!(r.redact(short, RedactionPolicy::Minimal), short);
    }

    #[test]
    fn test_default_redactor_should_include_none_policy() {
        let r = DefaultRedactor;
        assert!(r.should_include(FieldSensitivity::Public, RedactionPolicy::None));
        assert!(r.should_include(FieldSensitivity::Internal, RedactionPolicy::None));
        assert!(r.should_include(FieldSensitivity::Sensitive, RedactionPolicy::None));
    }

    #[test]
    fn test_default_redactor_should_include_sensitive_policy() {
        let r = DefaultRedactor;
        assert!(r.should_include(FieldSensitivity::Public, RedactionPolicy::Sensitive));
        assert!(r.should_include(FieldSensitivity::Internal, RedactionPolicy::Sensitive));
        assert!(!r.should_include(FieldSensitivity::Sensitive, RedactionPolicy::Sensitive));
    }

    #[test]
    fn test_default_redactor_should_include_minimal_policy() {
        let r = DefaultRedactor;
        assert!(r.should_include(FieldSensitivity::Public, RedactionPolicy::Minimal));
        assert!(!r.should_include(FieldSensitivity::Internal, RedactionPolicy::Minimal));
        assert!(!r.should_include(FieldSensitivity::Sensitive, RedactionPolicy::Minimal));
    }

    // ── FieldSensitivity ordering ─────────────────────────────────────────────

    #[test]
    fn test_field_sensitivity_ordering() {
        assert!(FieldSensitivity::Public < FieldSensitivity::Internal);
        assert!(FieldSensitivity::Internal < FieldSensitivity::Sensitive);
        assert!(FieldSensitivity::Public < FieldSensitivity::Sensitive);
    }

    // ── View struct construction ──────────────────────────────────────────────
    // NOTE: AuditView is intentionally not tested here.
    // AuditView contains Vec<AgentEvent> whose serde impl chain (30+ variant enum
    // with deeply nested domain types) overflows the rustc trait-solver (E0275)
    // when evaluated in a lib-test compilation unit.  Coverage via integration tests.

    #[test]
    fn test_summary_view_construction() {
        let view = SummaryView {
            session_id: "sess-2".to_string(),
            current_intent: Some("Build X".to_string()),
            active_task_status: Some("active".to_string()),
            progress_count: 5,
            artifact_count: 2,
            decision_count: 1,
            last_activity: None,
            generated_at: now(),
        };
        assert_eq!(view.session_id, "sess-2");
        assert_eq!(view.progress_count, 5);
        assert_eq!(view.artifact_count, 2);
    }

    #[test]
    fn test_session_list_filter_default() {
        let filter = SessionListFilter::default();
        assert!(filter.filter.is_none());
        assert!(filter.limit.is_none());
        assert!(filter.offset.is_none());
    }

    #[test]
    fn test_session_scope_from_option_defaults_and_parses_known_values() {
        assert_eq!(SessionScope::from_option(None), SessionScope::All);
        assert_eq!(
            SessionScope::from_option(Some("all".to_string())),
            SessionScope::All
        );
        assert_eq!(
            SessionScope::from_option(Some("root".to_string())),
            SessionScope::Root
        );
        assert_eq!(
            SessionScope::from_option(Some("forks".to_string())),
            SessionScope::Forks
        );
        assert_eq!(
            SessionScope::from_option(Some("delegates".to_string())),
            SessionScope::Delegates
        );
        assert_eq!(
            SessionScope::from_option(Some("children".to_string())),
            SessionScope::Children
        );
        assert_eq!(
            SessionScope::from_option(Some("unknown".to_string())),
            SessionScope::All
        );
    }

    #[test]
    fn test_session_scope_serde_uses_snake_case() {
        assert_eq!(
            serde_json::to_string(&SessionScope::Root).unwrap(),
            "\"root\""
        );
        assert_eq!(
            serde_json::to_string(&SessionScope::Delegates).unwrap(),
            "\"delegates\""
        );
        assert_eq!(
            serde_json::from_str::<SessionScope>("\"children\"").unwrap(),
            SessionScope::Children
        );
    }

    #[test]
    fn test_session_list_item_construction() {
        let item = SessionListItem {
            session_id: "sess-x".to_string(),
            name: Some("My Session".to_string()),
            cwd: Some("/home/user/project".to_string()),
            title: None,
            created_at: Some(now()),
            updated_at: None,
            parent_session_id: None,
            fork_origin: None,
            session_kind: None,
            has_children: false,
            fork_count: 0,
        };
        assert_eq!(item.session_id, "sess-x");
        assert!(!item.has_children);
        assert_eq!(item.fork_count, 0);
    }

    #[test]
    fn test_session_list_view_construction() {
        let view = SessionListView {
            groups: vec![],
            total_count: 0,
            generated_at: now(),
        };
        assert_eq!(view.total_count, 0);
        assert!(view.groups.is_empty());
    }

    // ── PredicateOp serialization ─────────────────────────────────────────────
    // NOTE: FilterExpr is a recursive type (And(Vec<FilterExpr>), Not(Box<FilterExpr>)).
    // Calling serde_json::to_string on FilterExpr overflows the rustc trait-solver
    // (E0275) due to infinite recursion in Serialize bound evaluation.
    // We test construction and field access only; FilterExpr serialization is a
    // property of the derive macro, not our logic.

    #[test]
    fn test_predicate_op_serialization() {
        let eq_op = PredicateOp::Eq(serde_json::Value::String("test".to_string()));
        let json = serde_json::to_string(&eq_op).unwrap();
        assert!(json.contains("eq"));

        let contains_op = PredicateOp::Contains("hello".to_string());
        let json2 = serde_json::to_string(&contains_op).unwrap();
        assert!(json2.contains("contains"));
    }

    #[test]
    fn test_filter_expr_construction() {
        // Construction only — no serde_json::to_string (see note above).
        let expr = FilterExpr::And(vec![
            FilterExpr::Predicate(FieldPredicate {
                field: "status".to_string(),
                op: PredicateOp::Eq(serde_json::json!("active")),
            }),
            FilterExpr::Not(Box::new(FilterExpr::Predicate(FieldPredicate {
                field: "name".to_string(),
                op: PredicateOp::IsNull,
            }))),
        ]);
        // Verify the structure is as expected
        if let FilterExpr::And(items) = &expr {
            assert_eq!(items.len(), 2);
        } else {
            panic!("expected FilterExpr::And");
        }
    }

    #[test]
    fn test_redacted_view_construction() {
        let view = RedactedView {
            session_id: "sess-r".to_string(),
            current_intent: Some("doing X".to_string()),
            active_task: Some(RedactedTask {
                id: "task-1".to_string(),
                status: "active".to_string(),
                expected_deliverable: Some("output.txt".to_string()),
            }),
            recent_progress: vec![RedactedProgress {
                kind: "tool_call".to_string(),
                summary: "ran shell".to_string(),
                created_at: now(),
            }],
            artifacts: vec![],
            generated_at: now(),
        };
        assert_eq!(view.session_id, "sess-r");
        assert!(view.active_task.is_some());
        assert_eq!(view.recent_progress.len(), 1);
    }
}
