//! Handler for agent decision tracing endpoint.
//!
//! The trace endpoint enables debugging agent decision-making by retrieving
//! query audits filtered by agent ID, time range, and optional subject pattern.

use axum::{
    extract::{Query as AxumQuery, State},
    Json,
};
use tracing::instrument;

use crate::{
    dto::{QueryAuditResponse, TraceParams, TraceResponse},
    error::{ApiError, Result},
    hex as hex_utils,
    state::AppState,
};

use stemedb_storage::{AuditStore, GenericAuditStore};

/// Query the audit log for agent decision tracing.
///
/// Returns query audits for a specific agent within a time range, optionally
/// filtered by subject pattern. This enables "Why did the agent think that?"
/// debugging for incident investigation.
///
/// # Query Parameters
///
/// - `agent_id` (required): Agent public key (hex-encoded, 32 bytes)
/// - `from` (required): Start of time range (Unix timestamp)
/// - `to` (optional): End of time range (Unix timestamp, defaults to now)
/// - `subject` (optional): Filter by subject pattern (e.g., "Tesla*", "auth/*", "*" for all)
/// - `limit` (optional): Maximum number of results (default: 100, max: 1000)
///
/// # Example
///
/// ```text
/// GET /v1/trace?agent_id=deadbeef...&from=1704067200&to=1704153600&subject=Tesla*&limit=50
/// ```
#[utoipa::path(
    get,
    path = "/v1/trace",
    params(TraceParams),
    responses(
        (status = 200, description = "Matching query audits for the agent", body = TraceResponse),
        (status = 400, description = "Invalid request parameters", body = crate::dto::ErrorResponse),
        (status = 500, description = "Internal server error", body = crate::dto::ErrorResponse)
    ),
    tag = "audit"
)]
#[instrument(skip(state), fields(
    agent_id = %params.agent_id,
    from = %params.from,
    to = ?params.to,
    subject = ?params.subject,
    limit = params.limit
))]
pub async fn trace(
    State(state): State<AppState>,
    AxumQuery(params): AxumQuery<TraceParams>,
) -> Result<Json<TraceResponse>> {
    // Validate and parse agent_id (required)
    if params.agent_id.is_empty() {
        return Err(ApiError::InvalidRequest("agent_id is required for trace queries".to_string()));
    }
    let agent_id = hex_utils::decode_agent_id(&params.agent_id)?;

    // Parse from timestamp (required)
    if params.from.is_empty() {
        return Err(ApiError::InvalidRequest(
            "from timestamp is required for trace queries".to_string(),
        ));
    }
    let from_timestamp = parse_timestamp(&params.from)?;

    // Parse to timestamp (defaults to now)
    let to_timestamp = if let Some(ref to_str) = params.to {
        parse_timestamp(to_str)?
    } else {
        std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .map_err(|e| ApiError::InvalidRequest(format!("System clock error: {}", e)))?
            .as_secs()
    };

    // Validate time range
    if from_timestamp > to_timestamp {
        return Err(ApiError::InvalidRequest(
            "from timestamp must be before to timestamp".to_string(),
        ));
    }

    // Query the audit store
    let audit_store = GenericAuditStore::new(state.store.clone());
    let audits = audit_store
        .get_audits_for_agent(&agent_id, from_timestamp, Some(to_timestamp), params.limit)
        .await?;

    // Apply subject pattern filter if specified
    let filtered_audits = if let Some(ref pattern) = params.subject {
        filter_by_subject_pattern(audits, pattern)
    } else {
        audits
    };

    let total_count = filtered_audits.len();

    // Convert to response DTOs
    let audit_responses: Vec<QueryAuditResponse> =
        filtered_audits.into_iter().map(QueryAuditResponse::from).collect();

    Ok(Json(TraceResponse {
        audits: audit_responses,
        total_count,
        agent_id: params.agent_id,
        from_timestamp,
        to_timestamp,
    }))
}

/// Parse a timestamp string (Unix timestamp).
///
/// Supports:
/// - Unix timestamps: "1704067200"
///
/// Note: ISO8601 support planned for future release.
fn parse_timestamp(s: &str) -> Result<u64> {
    s.parse::<u64>().map_err(|_| {
        ApiError::InvalidRequest(format!(
            "Invalid timestamp format: '{}'. Expected Unix timestamp (e.g., 1704067200)",
            s
        ))
    })
}

/// Filter query audits by subject pattern.
///
/// Supports simple glob patterns:
/// - Exact match: "Tesla"
/// - Prefix match: "Tesla*"
/// - Suffix match: "*Inc"
/// - Contains: "*Tesla*"
/// - Wildcard: "*" matches everything including queries without subject filter
fn filter_by_subject_pattern(
    audits: Vec<stemedb_core::types::QueryAudit>,
    pattern: &str,
) -> Vec<stemedb_core::types::QueryAudit> {
    // Wildcard matches everything, including queries without subject filter
    if pattern == "*" {
        return audits;
    }

    audits
        .into_iter()
        .filter(|audit| {
            audit.params.subject.as_ref().is_some_and(|subject| matches_pattern(subject, pattern))
        })
        .collect()
}

/// Check if a subject matches a glob pattern.
///
/// Supports:
/// - Exact: "Tesla" matches "Tesla"
/// - Prefix: "Tesla*" matches "Tesla_Inc"
/// - Suffix: "*Inc" matches "Tesla_Inc"
/// - Contains: "*Tesla*" matches "My_Tesla_Stock"
fn matches_pattern(subject: &str, pattern: &str) -> bool {
    if pattern == "*" {
        return true;
    }

    let starts_with_wildcard = pattern.starts_with('*');
    let ends_with_wildcard = pattern.ends_with('*');

    match (starts_with_wildcard, ends_with_wildcard) {
        (false, false) => {
            // Exact match
            subject == pattern
        }
        (false, true) => {
            // Prefix match: "Tesla*"
            let prefix = &pattern[..pattern.len() - 1];
            subject.starts_with(prefix)
        }
        (true, false) => {
            // Suffix match: "*Inc"
            let suffix = &pattern[1..];
            subject.ends_with(suffix)
        }
        (true, true) => {
            // Contains match: "*Tesla*"
            let substring = &pattern[1..pattern.len() - 1];
            subject.contains(substring)
        }
    }
}

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

    #[test]
    fn test_matches_pattern_exact() {
        assert!(matches_pattern("Tesla", "Tesla"));
        assert!(!matches_pattern("Tesla_Inc", "Tesla"));
    }

    #[test]
    fn test_matches_pattern_prefix() {
        assert!(matches_pattern("Tesla_Inc", "Tesla*"));
        assert!(matches_pattern("Tesla", "Tesla*"));
        assert!(!matches_pattern("My_Tesla", "Tesla*"));
    }

    #[test]
    fn test_matches_pattern_suffix() {
        assert!(matches_pattern("Tesla_Inc", "*Inc"));
        assert!(matches_pattern("Inc", "*Inc"));
        assert!(!matches_pattern("Inc_Tesla", "*Inc"));
    }

    #[test]
    fn test_matches_pattern_contains() {
        assert!(matches_pattern("My_Tesla_Stock", "*Tesla*"));
        assert!(matches_pattern("Tesla", "*Tesla*"));
        assert!(matches_pattern("Tesla_Inc", "*Tesla*"));
        assert!(!matches_pattern("MyStock", "*Tesla*"));
    }

    #[test]
    fn test_matches_pattern_wildcard() {
        assert!(matches_pattern("anything", "*"));
        assert!(matches_pattern("", "*"));
    }

    #[test]
    fn test_parse_timestamp_unix() {
        assert_eq!(parse_timestamp("1704067200").unwrap(), 1704067200);
        assert_eq!(parse_timestamp("0").unwrap(), 0);
    }

    #[test]
    fn test_parse_timestamp_invalid() {
        assert!(parse_timestamp("not-a-number").is_err());
        assert!(parse_timestamp("").is_err());
    }
}
