// SPDX-License-Identifier: MIT
// Copyright (c) 2025-2026 SenWeaverCoding
// Licensed under the MIT License.

use async_trait::async_trait;
use serde_json::json;
use std::path::PathBuf;

use super::traits::{Tool, ToolResult, json_verbose_flag, render_json_output};
use crate::config::SecurityOpsConfig;
use crate::security::playbook::{
    Playbook, StepStatus, evaluate_step, load_playbooks, severity_level,
};
use crate::security::vulnerability::{generate_summary, parse_vulnerability_json};

pub struct SecurityOpsTool {
    config: SecurityOpsConfig,
    playbooks: Vec<Playbook>,
}

impl SecurityOpsTool {
    pub fn new(config: SecurityOpsConfig) -> Self {
        let playbooks_dir = expand_tilde(&config.playbooks_dir);
        let playbooks = load_playbooks(&playbooks_dir);
        Self { config, playbooks }
    }

    fn triage_alert(&self, args: &serde_json::Value) -> anyhow::Result<ToolResult> {
        let alert = args
            .get("alert")
            .ok_or_else(|| anyhow::anyhow!("Missing required 'alert' parameter"))?;

        let alert_type = alert
            .get("type")
            .and_then(|v| v.as_str())
            .unwrap_or("unknown");
        let source = alert
            .get("source")
            .and_then(|v| v.as_str())
            .unwrap_or("unknown");
        let severity = alert
            .get("severity")
            .and_then(|v| v.as_str())
            .unwrap_or("medium");
        let description = alert
            .get("description")
            .and_then(|v| v.as_str())
            .unwrap_or("");

        let matching_playbooks: Vec<&Playbook> = self
            .playbooks
            .iter()
            .filter(|pb| {
                severity_level(severity) >= severity_level(&pb.severity_filter)
                    && (pb.name.contains(alert_type)
                        || alert_type.contains(&pb.name)
                        || description
                            .to_lowercase()
                            .contains(&pb.name.replace('_', " ")))
            })
            .collect();

        let playbook_names: Vec<&str> =
            matching_playbooks.iter().map(|p| p.name.as_str()).collect();

        let output = json!({
            "classification": {
                "alert_type": alert_type,
                "source": source,
                "severity": severity,
                "severity_level": severity_level(severity),
                "priority": if severity_level(severity) >= 3 { "immediate" } else { "standard" },
            },
            "recommended_playbooks": playbook_names,
            "recommended_action": if matching_playbooks.is_empty() {
                "Manual investigation required  -  no matching playbook found"
            } else {
                "Execute recommended playbook(s)"
            },
            "auto_triage": self.config.auto_triage,
        });

        Ok(ToolResult {
            success: true,
            output: render_json_output(&output, json_verbose_flag(args)),
            error: None,
        })
    }

    fn run_playbook(&self, args: &serde_json::Value) -> anyhow::Result<ToolResult> {
        let playbook_name = args
            .get("playbook")
            .and_then(|v| v.as_str())
            .ok_or_else(|| anyhow::anyhow!("Missing required 'playbook' parameter"))?;

        let step_index =
            usize::try_from(args.get("step").and_then(|v| v.as_u64()).ok_or_else(|| {
                anyhow::anyhow!("Missing required 'step' parameter (0-based index)")
            })?)
            .map_err(|_| anyhow::anyhow!("'step' parameter value too large for this platform"))?;

        let alert_severity = args
            .get("alert_severity")
            .and_then(|v| v.as_str())
            .unwrap_or("medium");

        let playbook = self
            .playbooks
            .iter()
            .find(|p| p.name == playbook_name)
            .ok_or_else(|| anyhow::anyhow!("Playbook '{}' not found", playbook_name))?;

        let result = evaluate_step(
            playbook,
            step_index,
            alert_severity,
            &self.config.max_auto_severity,
            self.config.require_approval_for_actions,
        );

        let output = json!({
            "playbook": playbook_name,
            "step_index": result.step_index,
            "action": result.action,
            "status": result.status.to_string(),
            "message": result.message,
            "requires_manual_approval": result.status == StepStatus::PendingApproval,
        });

        Ok(ToolResult {
            success: result.status != StepStatus::Failed,
            output: render_json_output(&output, json_verbose_flag(args)),
            error: if result.status == StepStatus::Failed {
                Some(result.message)
            } else {
                None
            },
        })
    }

    fn parse_vulnerability(&self, args: &serde_json::Value) -> anyhow::Result<ToolResult> {
        let scan_data = args
            .get("scan_data")
            .ok_or_else(|| anyhow::anyhow!("Missing required 'scan_data' parameter"))?;

        let json_str = if let Some(text) = scan_data.as_str() {
            text.to_string()
        } else {
            serde_json::to_string(scan_data)?
        };

        let report = parse_vulnerability_json(&json_str)?;
        let summary = generate_summary(&report);

        let output = json!({
            "scanner": report.scanner,
            "scan_date": report.scan_date.to_rfc3339(),
            "total_findings": report.findings.len(),
            "by_severity": {
                "critical": report.findings.iter().filter(|f| f.severity == "critical").count(),
                "high": report.findings.iter().filter(|f| f.severity == "high").count(),
                "medium": report.findings.iter().filter(|f| f.severity == "medium").count(),
                "low": report.findings.iter().filter(|f| f.severity == "low").count(),
            },
            "summary": summary,
        });

        Ok(ToolResult {
            success: true,
            output: render_json_output(&output, json_verbose_flag(args)),
            error: None,
        })
    }

    fn generate_report(&self, args: &serde_json::Value) -> anyhow::Result<ToolResult> {
        let client_name = args
            .get("client_name")
            .and_then(|v| v.as_str())
            .unwrap_or("Client");
        let period = args
            .get("period")
            .and_then(|v| v.as_str())
            .unwrap_or("current");
        let alert_stats = args.get("alert_stats");
        let vuln_summary = args
            .get("vuln_summary")
            .and_then(|v| v.as_str())
            .unwrap_or("");

        let report = format!(
            "# Security Posture Report  -  {client_name}\n\
             **Period:** {period}\n\
             **Generated:** {}\n\n\
             ## Executive Summary\n\n\
             This report provides an overview of the security posture for {client_name} \
             during the {period} period.\n\n\
             ## Alert Summary\n\n\
             {}\n\n\
             ## Vulnerability Assessment\n\n\
             {}\n\n\
             ## Recommendations\n\n\
             1. Address all critical and high-severity findings immediately\n\
             2. Review and update incident response playbooks quarterly\n\
             3. Conduct regular vulnerability scans on all internet-facing assets\n\
             4. Ensure all endpoints have current security patches\n\n\
             ---\n\
             *Report generated by SenWeaverCoding MCSS Agent*\n",
            chrono::Utc::now().format("%Y-%m-%d %H:%M UTC"),
            alert_stats
                .map(|s| serde_json::to_string_pretty(s).unwrap_or_default())
                .unwrap_or_else(|| "No alert statistics provided.".into()),
            if vuln_summary.is_empty() {
                "No vulnerability data provided."
            } else {
                vuln_summary
            },
        );

        Ok(ToolResult {
            success: true,
            output: report,
            error: None,
        })
    }

    fn list_playbooks(&self) -> anyhow::Result<ToolResult> {
        if self.playbooks.is_empty() {
            return Ok(ToolResult {
                success: true,
                output: "No playbooks available.".into(),
                error: None,
            });
        }

        let playbook_list: Vec<serde_json::Value> = self
            .playbooks
            .iter()
            .map(|pb| {
                json!({
                    "name": pb.name,
                    "description": pb.description,
                    "steps": pb.steps.len(),
                    "severity_filter": pb.severity_filter,
                    "auto_approve_steps": pb.auto_approve_steps,
                })
            })
            .collect();

        Ok(ToolResult {
            success: true,
            output: render_json_output(&serde_json::Value::Array(playbook_list), false),
            error: None,
        })
    }

    fn alert_stats(&self, args: &serde_json::Value) -> anyhow::Result<ToolResult> {
        let alerts = args
            .get("alerts")
            .and_then(|v| v.as_array())
            .ok_or_else(|| anyhow::anyhow!("Missing required 'alerts' array parameter"))?;

        let total = alerts.len();
        let mut by_severity = std::collections::HashMap::new();
        let mut by_category = std::collections::HashMap::new();
        let mut resolved_count = 0u64;
        let mut total_resolution_secs = 0u64;

        for alert in alerts {
            let severity = alert
                .get("severity")
                .and_then(|v| v.as_str())
                .unwrap_or("unknown");
            *by_severity.entry(severity.to_string()).or_insert(0u64) += 1;

            let category = alert
                .get("category")
                .and_then(|v| v.as_str())
                .unwrap_or("uncategorized");
            *by_category.entry(category.to_string()).or_insert(0u64) += 1;

            if let Some(resolution_secs) = alert.get("resolution_secs").and_then(|v| v.as_u64()) {
                resolved_count += 1;
                total_resolution_secs += resolution_secs;
            }
        }

        let avg_resolution = if resolved_count > 0 {
            total_resolution_secs as f64 / resolved_count as f64
        } else {
            0.0
        };

        #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
        let avg_resolution_secs_u64 = avg_resolution.max(0.0) as u64;

        let output = json!({
            "total_alerts": total,
            "resolved": resolved_count,
            "unresolved": total as u64 - resolved_count,
            "by_severity": by_severity,
            "by_category": by_category,
            "avg_resolution_secs": avg_resolution,
            "avg_resolution_human": format_duration_secs(avg_resolution_secs_u64),
        });

        Ok(ToolResult {
            success: true,
            output: render_json_output(&output, json_verbose_flag(args)),
            error: None,
        })
    }
}

fn format_duration_secs(secs: u64) -> String {
    if secs < 60 {
        format!("{secs}s")
    } else if secs < 3600 {
        format!("{}m {}s", secs / 60, secs % 60)
    } else {
        format!("{}h {}m", secs / 3600, (secs % 3600) / 60)
    }
}

fn expand_tilde(path: &str) -> PathBuf {
    if let Some(rest) = path.strip_prefix("~/") {
        if let Some(user_dirs) = directories::UserDirs::new() {
            return user_dirs.home_dir().join(rest);
        }
    }
    PathBuf::from(path)
}

#[async_trait]
impl Tool for SecurityOpsTool {
    fn name(&self) -> &str {
        "security_ops"
    }

    fn description(&self) -> &str {
        "Security operations tool for managed cybersecurity services. Actions: \
         triage_alert (classify/prioritize alerts), run_playbook (execute incident response steps), \
         parse_vulnerability (parse scan results), generate_report (create security posture reports), \
         list_playbooks (list available playbooks), alert_stats (summarize alert metrics)."
    }

    fn parameters_schema(&self) -> serde_json::Value {
        json!({
            "type": "object",
            "required": ["action"],
            "properties": {
                "action": {
                    "type": "string",
                    "enum": ["triage_alert", "run_playbook", "parse_vulnerability", "generate_report", "list_playbooks", "alert_stats"],
                    "description": "The security operation to perform"
                },
                "alert": {
                    "type": "object",
                    "description": "Alert JSON for triage_alert (requires: type, severity; optional: source, description)"
                },
                "playbook": {
                    "type": "string",
                    "description": "Playbook name for run_playbook"
                },
                "step": {
                    "type": "integer",
                    "description": "0-based step index for run_playbook"
                },
                "alert_severity": {
                    "type": "string",
                    "description": "Alert severity context for run_playbook"
                },
                "scan_data": {
                    "description": "Vulnerability scan data (JSON string or object) for parse_vulnerability"
                },
                "client_name": {
                    "type": "string",
                    "description": "Client name for generate_report"
                },
                "period": {
                    "type": "string",
                    "description": "Reporting period for generate_report"
                },
                "alert_stats": {
                    "type": "object",
                    "description": "Alert statistics to include in generate_report"
                },
                "vuln_summary": {
                    "type": "string",
                    "description": "Vulnerability summary to include in generate_report"
                },
                "alerts": {
                    "type": "array",
                    "description": "Array of alert objects for alert_stats"
                }
            }
        })
    }

    async fn execute(&self, args: serde_json::Value) -> anyhow::Result<ToolResult> {
        let action = args
            .get("action")
            .and_then(|v| v.as_str())
            .ok_or_else(|| anyhow::anyhow!("Missing required 'action' parameter"))?;

        match action {
            "triage_alert" => self.triage_alert(&args),
            "run_playbook" => self.run_playbook(&args),
            "parse_vulnerability" => self.parse_vulnerability(&args),
            "generate_report" => self.generate_report(&args),
            "list_playbooks" => self.list_playbooks(),
            "alert_stats" => self.alert_stats(&args),
            _ => Ok(ToolResult {
                success: false,
                output: String::new(),
                error: Some(format!(
                    "Unknown action '{action}'. Valid: triage_alert, run_playbook, \
                     parse_vulnerability, generate_report, list_playbooks, alert_stats"
                )),
            }),
        }
    }
}
