//! HTTP handlers for red team mesh operations.
//!
//! These endpoints are used by red team tasks (via the makima CLI) to notify
//! supervisors of potential issues and query their own status.

use axum::{
    extract::State,
    http::{HeaderMap, StatusCode},
    response::IntoResponse,
    Json,
};
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
use uuid::Uuid;

use crate::db::repository;
use crate::server::handlers::mesh::{extract_auth, AuthSource};
use crate::server::messages::ApiError;
use crate::server::state::{DaemonCommand, SharedState};

// =============================================================================
// Request/Response Types
// =============================================================================

/// Severity level for red team notifications.
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "lowercase")]
pub enum RedTeamSeverity {
    /// Informational notice - minor issue or suggestion
    Info,
    /// Warning - potential problem that should be reviewed
    Warning,
    /// Critical - serious issue requiring immediate attention
    Critical,
}

impl Default for RedTeamSeverity {
    fn default() -> Self {
        Self::Warning
    }
}

impl std::fmt::Display for RedTeamSeverity {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Info => write!(f, "INFO"),
            Self::Warning => write!(f, "WARNING"),
            Self::Critical => write!(f, "CRITICAL"),
        }
    }
}

/// Request to notify the supervisor of a potential issue.
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct RedTeamNotifyRequest {
    /// The issue description/message to send to the supervisor
    pub message: String,
    /// Severity level of the issue
    #[serde(default)]
    pub severity: RedTeamSeverity,
    /// ID of the task being reviewed (optional - if not provided, assumes general contract concern)
    pub related_task_id: Option<Uuid>,
    /// File path related to the issue (optional)
    pub file_path: Option<String>,
    /// Additional context about the issue
    pub context: Option<String>,
}

/// Response from the notify endpoint.
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct RedTeamNotifyResponse {
    /// Unique ID for this notification
    pub notification_id: Uuid,
    /// Whether the notification was successfully delivered to the supervisor
    pub delivered: bool,
    /// The supervisor task ID that received the notification
    pub supervisor_task_id: Option<Uuid>,
}

/// Response from the status endpoint.
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct RedTeamStatusResponse {
    /// Contract ID being monitored
    pub contract_id: Uuid,
    /// Red team task ID
    pub red_team_task_id: Uuid,
    /// Current task status
    pub status: String,
    /// Number of notifications sent so far
    pub notifications_sent: i64,
}

/// Red team notification record stored in database.
#[derive(Debug, Clone, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct RedTeamNotification {
    pub id: Uuid,
    pub red_team_task_id: Uuid,
    pub contract_id: Uuid,
    pub message: String,
    pub severity: String,
    pub related_task_id: Option<Uuid>,
    pub file_path: Option<String>,
    pub context: Option<String>,
    pub delivered: bool,
    pub created_at: chrono::DateTime<chrono::Utc>,
}

// =============================================================================
// Helper Functions
// =============================================================================

/// Verify the request comes from a red team task and extract ownership info.
///
/// Returns (task_id, owner_id, contract_id) on success.
async fn verify_red_team_auth(
    state: &SharedState,
    headers: &HeaderMap,
) -> Result<(Uuid, Uuid, Uuid), (StatusCode, Json<ApiError>)> {
    let auth = extract_auth(state, headers);

    let task_id = match auth {
        AuthSource::ToolKey(task_id) => task_id,
        _ => {
            return Err((
                StatusCode::UNAUTHORIZED,
                Json(ApiError::new(
                    "UNAUTHORIZED",
                    "Red team endpoints require tool key auth",
                )),
            ));
        }
    };

    // Get the task to verify it's a red team task and get owner_id
    let pool = state.db_pool.as_ref().ok_or_else(|| {
        (
            StatusCode::SERVICE_UNAVAILABLE,
            Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
        )
    })?;

    let task = repository::get_task(pool, task_id)
        .await
        .map_err(|e| {
            tracing::error!(error = %e, "Failed to get red team task");
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", "Failed to verify red team task")),
            )
        })?
        .ok_or_else(|| {
            (
                StatusCode::NOT_FOUND,
                Json(ApiError::new("NOT_FOUND", "Task not found")),
            )
        })?;

    // Verify task is a red team task
    // NOTE: This requires the is_red_team field to be added to the Task struct.
    // For now, we check if the task name contains "red-team" or "red_team" as a fallback.
    let is_red_team = task.name.to_lowercase().contains("red-team")
        || task.name.to_lowercase().contains("red_team")
        || task.name.to_lowercase().contains("redteam");

    if !is_red_team {
        return Err((
            StatusCode::FORBIDDEN,
            Json(ApiError::new(
                "NOT_RED_TEAM",
                "Only red team tasks can use these endpoints",
            )),
        ));
    }

    // Red team tasks must be associated with a contract
    let contract_id = task.contract_id.ok_or_else(|| {
        (
            StatusCode::BAD_REQUEST,
            Json(ApiError::new(
                "NO_CONTRACT",
                "Red team task must be associated with a contract",
            )),
        )
    })?;

    Ok((task_id, task.owner_id, contract_id))
}

/// Format an alert message for the supervisor.
///
/// Creates a formatted alert with clear visual markers to grab attention.
fn format_alert_message(
    severity: &RedTeamSeverity,
    message: &str,
    related_task_id: Option<Uuid>,
    file_path: Option<&str>,
    context: Option<&str>,
) -> String {
    let severity_marker = match severity {
        RedTeamSeverity::Info => "ℹ️",
        RedTeamSeverity::Warning => "⚠️",
        RedTeamSeverity::Critical => "🚨",
    };

    let border = match severity {
        RedTeamSeverity::Info => "─".repeat(60),
        RedTeamSeverity::Warning => "━".repeat(60),
        RedTeamSeverity::Critical => "═".repeat(60),
    };

    let mut alert = format!(
        r#"
{}
{} [RED TEAM ALERT] - {}
{}

Issue: {}
"#,
        border, severity_marker, severity, border, message
    );

    if let Some(task_id) = related_task_id {
        alert.push_str(&format!("\nRelated Task: {}\n", task_id));
    }

    if let Some(path) = file_path {
        alert.push_str(&format!("File: {}\n", path));
    }

    if let Some(ctx) = context {
        alert.push_str(&format!("\nContext:\n{}\n", ctx));
    }

    // Add action suggestions based on severity
    let actions = match severity {
        RedTeamSeverity::Info => {
            "Suggested Actions:\n- Review when convenient\n- Consider if changes are needed"
        }
        RedTeamSeverity::Warning => {
            "Suggested Actions:\n- Review the flagged item soon\n- Check if this deviates from the contract\n- Consider pausing related work until reviewed"
        }
        RedTeamSeverity::Critical => {
            "Suggested Actions:\n- STOP related work immediately\n- Review the flagged item urgently\n- Verify compliance with contract requirements\n- Consider reverting recent changes if necessary"
        }
    };

    alert.push_str(&format!("\n{}\n{}\n", actions, border));

    alert
}

// =============================================================================
// Handlers
// =============================================================================

/// Notify the supervisor of a potential issue.
///
/// POST /api/v1/mesh/red-team/notify
///
/// This endpoint allows red team tasks to alert supervisors about issues they've
/// identified during code review. The notification is sent as a message to the
/// supervisor task.
#[utoipa::path(
    post,
    path = "/api/v1/mesh/red-team/notify",
    request_body = RedTeamNotifyRequest,
    responses(
        (status = 200, description = "Notification sent", body = RedTeamNotifyResponse),
        (status = 401, description = "Unauthorized - tool key required"),
        (status = 403, description = "Forbidden - not a red team task"),
        (status = 404, description = "Task not found"),
        (status = 503, description = "Database not available"),
        (status = 500, description = "Internal server error"),
    ),
    security(
        ("tool_key" = [])
    ),
    tag = "Mesh Red Team"
)]
pub async fn notify_supervisor(
    State(state): State<SharedState>,
    headers: HeaderMap,
    Json(request): Json<RedTeamNotifyRequest>,
) -> impl IntoResponse {
    let (red_team_task_id, owner_id, contract_id) =
        match verify_red_team_auth(&state, &headers).await {
            Ok(ids) => ids,
            Err(e) => return e.into_response(),
        };

    let pool = state.db_pool.as_ref().unwrap();

    // Generate notification ID
    let notification_id = Uuid::new_v4();

    // Get the contract to find the supervisor task
    let contract = match repository::get_contract_for_owner(pool, contract_id, owner_id).await {
        Ok(Some(c)) => c,
        Ok(None) => {
            return (
                StatusCode::NOT_FOUND,
                Json(ApiError::new("NOT_FOUND", "Contract not found")),
            )
                .into_response();
        }
        Err(e) => {
            tracing::error!(error = %e, "Failed to get contract");
            return (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", "Failed to get contract")),
            )
                .into_response();
        }
    };

    let supervisor_task_id = contract.supervisor_task_id;

    // Format the alert message
    let alert_message = format_alert_message(
        &request.severity,
        &request.message,
        request.related_task_id,
        request.file_path.as_deref(),
        request.context.as_deref(),
    );

    // Record the notification in the database as a history event
    let event_data = serde_json::json!({
        "notification_id": notification_id.to_string(),
        "red_team_task_id": red_team_task_id.to_string(),
        "severity": request.severity.to_string(),
        "message": request.message,
        "related_task_id": request.related_task_id.map(|id| id.to_string()),
        "file_path": request.file_path,
        "context": request.context,
    });

    let _ = repository::record_history_event(
        pool,
        owner_id,
        Some(contract_id),
        Some(red_team_task_id),
        "red_team_alert",
        Some(&request.severity.to_string().to_lowercase()),
        Some(&request.message),
        event_data,
    )
    .await;

    // Try to send the message to the supervisor
    let mut delivered = false;
    if let Some(sup_task_id) = supervisor_task_id {
        // Get the supervisor task to find its daemon
        if let Ok(Some(supervisor_task)) = repository::get_task(pool, sup_task_id).await {
            if let Some(daemon_id) = supervisor_task.daemon_id {
                // Send the alert message to the supervisor
                let cmd = DaemonCommand::SendMessage {
                    task_id: sup_task_id,
                    message: alert_message.clone(),
                };

                if let Err(e) = state.send_daemon_command(daemon_id, cmd).await {
                    tracing::warn!(
                        error = %e,
                        supervisor_task_id = %sup_task_id,
                        daemon_id = %daemon_id,
                        "Failed to send red team alert to supervisor"
                    );
                } else {
                    delivered = true;
                    tracing::info!(
                        notification_id = %notification_id,
                        red_team_task_id = %red_team_task_id,
                        supervisor_task_id = %sup_task_id,
                        severity = %request.severity,
                        "Red team alert delivered to supervisor"
                    );
                }
            } else {
                tracing::warn!(
                    supervisor_task_id = %sup_task_id,
                    "Supervisor task has no assigned daemon - alert not delivered"
                );
            }
        }
    } else {
        tracing::warn!(
            contract_id = %contract_id,
            "Contract has no supervisor task - alert not delivered"
        );
    }

    (
        StatusCode::OK,
        Json(RedTeamNotifyResponse {
            notification_id,
            delivered,
            supervisor_task_id,
        }),
    )
        .into_response()
}

/// Get the status of the red team task.
///
/// GET /api/v1/mesh/red-team/status
///
/// Returns information about the current red team task including the contract
/// being monitored and notification statistics.
#[utoipa::path(
    get,
    path = "/api/v1/mesh/red-team/status",
    responses(
        (status = 200, description = "Red team status", body = RedTeamStatusResponse),
        (status = 401, description = "Unauthorized - tool key required"),
        (status = 403, description = "Forbidden - not a red team task"),
        (status = 404, description = "Task not found"),
        (status = 503, description = "Database not available"),
        (status = 500, description = "Internal server error"),
    ),
    security(
        ("tool_key" = [])
    ),
    tag = "Mesh Red Team"
)]
pub async fn get_status(
    State(state): State<SharedState>,
    headers: HeaderMap,
) -> impl IntoResponse {
    let (red_team_task_id, owner_id, contract_id) =
        match verify_red_team_auth(&state, &headers).await {
            Ok(ids) => ids,
            Err(e) => return e.into_response(),
        };

    let pool = state.db_pool.as_ref().unwrap();

    // Get the red team task status
    let task = match repository::get_task(pool, red_team_task_id).await {
        Ok(Some(t)) => t,
        Ok(None) => {
            return (
                StatusCode::NOT_FOUND,
                Json(ApiError::new("NOT_FOUND", "Red team task not found")),
            )
                .into_response();
        }
        Err(e) => {
            tracing::error!(error = %e, "Failed to get red team task");
            return (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", "Failed to get task")),
            )
                .into_response();
        }
    };

    // Count notifications sent by this red team task
    // Query history_events for red_team_alert events from this task
    let notifications_sent = match sqlx::query_scalar::<_, i64>(
        r#"
        SELECT COUNT(*)
        FROM history_events
        WHERE owner_id = $1
          AND contract_id = $2
          AND task_id = $3
          AND event_type = 'red_team_alert'
        "#,
    )
    .bind(owner_id)
    .bind(contract_id)
    .bind(red_team_task_id)
    .fetch_one(pool)
    .await
    {
        Ok(count) => count,
        Err(e) => {
            tracing::warn!(error = %e, "Failed to count red team notifications");
            0
        }
    };

    (
        StatusCode::OK,
        Json(RedTeamStatusResponse {
            contract_id,
            red_team_task_id,
            status: task.status,
            notifications_sent,
        }),
    )
        .into_response()
}
