use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};

use super::finding::Severity;

/// Type of DAST target application
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum DastTargetType {
    WebApp,
    RestApi,
    GraphQl,
}

impl std::fmt::Display for DastTargetType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::WebApp => write!(f, "webapp"),
            Self::RestApi => write!(f, "rest_api"),
            Self::GraphQl => write!(f, "graphql"),
        }
    }
}

/// Authentication configuration for DAST target
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DastAuthConfig {
    /// Authentication method: "none", "basic", "bearer", "cookie", "form"
    pub method: String,
    /// Login URL for form-based auth
    pub login_url: Option<String>,
    /// Username or token
    pub username: Option<String>,
    /// Password (stored encrypted in practice)
    pub password: Option<String>,
    /// Bearer token
    pub token: Option<String>,
    /// Custom headers for auth
    pub headers: Option<std::collections::HashMap<String, String>>,
}

/// A target for DAST scanning
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DastTarget {
    #[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
    pub id: Option<bson::oid::ObjectId>,
    pub name: String,
    pub base_url: String,
    pub target_type: DastTargetType,
    pub auth_config: Option<DastAuthConfig>,
    /// Linked repository ID (for SAST correlation)
    pub repo_id: Option<String>,
    /// URL paths to exclude from scanning
    pub excluded_paths: Vec<String>,
    /// Maximum crawl depth
    pub max_crawl_depth: u32,
    /// Rate limit (requests per second)
    pub rate_limit: u32,
    /// Whether destructive tests (DELETE, PUT) are allowed
    pub allow_destructive: bool,
    #[serde(with = "super::serde_helpers::bson_datetime")]
    pub created_at: DateTime<Utc>,
    #[serde(with = "super::serde_helpers::bson_datetime")]
    pub updated_at: DateTime<Utc>,
}

impl DastTarget {
    pub fn new(name: String, base_url: String, target_type: DastTargetType) -> Self {
        let now = Utc::now();
        Self {
            id: None,
            name,
            base_url,
            target_type,
            auth_config: None,
            repo_id: None,
            excluded_paths: Vec::new(),
            max_crawl_depth: 5,
            rate_limit: 10,
            allow_destructive: false,
            created_at: now,
            updated_at: now,
        }
    }
}

/// Phase of a DAST scan
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum DastScanPhase {
    Reconnaissance,
    Crawling,
    VulnerabilityAnalysis,
    Exploitation,
    Reporting,
    Completed,
}

impl std::fmt::Display for DastScanPhase {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Reconnaissance => write!(f, "reconnaissance"),
            Self::Crawling => write!(f, "crawling"),
            Self::VulnerabilityAnalysis => write!(f, "vulnerability_analysis"),
            Self::Exploitation => write!(f, "exploitation"),
            Self::Reporting => write!(f, "reporting"),
            Self::Completed => write!(f, "completed"),
        }
    }
}

/// Status of a DAST scan run
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum DastScanStatus {
    Running,
    Completed,
    Failed,
    Cancelled,
}

/// A DAST scan run
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DastScanRun {
    #[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
    pub id: Option<bson::oid::ObjectId>,
    pub target_id: String,
    pub status: DastScanStatus,
    pub current_phase: DastScanPhase,
    pub phases_completed: Vec<DastScanPhase>,
    /// Number of endpoints discovered during crawling
    pub endpoints_discovered: u32,
    /// Number of findings
    pub findings_count: u32,
    /// Number of confirmed exploitable findings
    pub exploitable_count: u32,
    pub error_message: Option<String>,
    /// Linked SAST scan run ID (if triggered as part of pipeline)
    pub sast_scan_run_id: Option<String>,
    #[serde(with = "super::serde_helpers::bson_datetime")]
    pub started_at: DateTime<Utc>,
    #[serde(default, with = "super::serde_helpers::opt_bson_datetime")]
    pub completed_at: Option<DateTime<Utc>>,
}

impl DastScanRun {
    pub fn new(target_id: String) -> Self {
        Self {
            id: None,
            target_id,
            status: DastScanStatus::Running,
            current_phase: DastScanPhase::Reconnaissance,
            phases_completed: Vec::new(),
            endpoints_discovered: 0,
            findings_count: 0,
            exploitable_count: 0,
            error_message: None,
            sast_scan_run_id: None,
            started_at: Utc::now(),
            completed_at: None,
        }
    }
}

/// Type of DAST vulnerability
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum DastVulnType {
    SqlInjection,
    Xss,
    AuthBypass,
    Ssrf,
    ApiMisconfiguration,
    OpenRedirect,
    Idor,
    InformationDisclosure,
    SecurityMisconfiguration,
    BrokenAuth,
    DnsMisconfiguration,
    EmailSecurity,
    TlsMisconfiguration,
    CookieSecurity,
    CspIssue,
    CorsMisconfiguration,
    RateLimitAbsent,
    ConsoleLogLeakage,
    SecurityHeaderMissing,
    KnownCveExploit,
    Other,
}

impl std::fmt::Display for DastVulnType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::SqlInjection => write!(f, "sql_injection"),
            Self::Xss => write!(f, "xss"),
            Self::AuthBypass => write!(f, "auth_bypass"),
            Self::Ssrf => write!(f, "ssrf"),
            Self::ApiMisconfiguration => write!(f, "api_misconfiguration"),
            Self::OpenRedirect => write!(f, "open_redirect"),
            Self::Idor => write!(f, "idor"),
            Self::InformationDisclosure => write!(f, "information_disclosure"),
            Self::SecurityMisconfiguration => write!(f, "security_misconfiguration"),
            Self::BrokenAuth => write!(f, "broken_auth"),
            Self::DnsMisconfiguration => write!(f, "dns_misconfiguration"),
            Self::EmailSecurity => write!(f, "email_security"),
            Self::TlsMisconfiguration => write!(f, "tls_misconfiguration"),
            Self::CookieSecurity => write!(f, "cookie_security"),
            Self::CspIssue => write!(f, "csp_issue"),
            Self::CorsMisconfiguration => write!(f, "cors_misconfiguration"),
            Self::RateLimitAbsent => write!(f, "rate_limit_absent"),
            Self::ConsoleLogLeakage => write!(f, "console_log_leakage"),
            Self::SecurityHeaderMissing => write!(f, "security_header_missing"),
            Self::KnownCveExploit => write!(f, "known_cve_exploit"),
            Self::Other => write!(f, "other"),
        }
    }
}

/// Evidence collected during DAST testing
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DastEvidence {
    /// HTTP request that triggered the finding
    pub request_method: String,
    pub request_url: String,
    pub request_headers: Option<std::collections::HashMap<String, String>>,
    pub request_body: Option<String>,
    /// HTTP response
    pub response_status: u16,
    pub response_headers: Option<std::collections::HashMap<String, String>>,
    /// Relevant snippet of response body
    pub response_snippet: Option<String>,
    /// Path to screenshot file (if captured)
    pub screenshot_path: Option<String>,
    /// The payload that triggered the vulnerability
    pub payload: Option<String>,
    /// Timing information (for timing-based attacks)
    pub response_time_ms: Option<u64>,
}

/// A finding from DAST scanning
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DastFinding {
    #[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
    pub id: Option<bson::oid::ObjectId>,
    pub scan_run_id: String,
    pub target_id: String,
    pub vuln_type: DastVulnType,
    pub title: String,
    pub description: String,
    pub severity: Severity,
    pub cwe: Option<String>,
    /// The URL endpoint where the vulnerability was found
    pub endpoint: String,
    /// HTTP method
    pub method: String,
    /// Parameter that is vulnerable
    pub parameter: Option<String>,
    /// Whether exploitability was confirmed with a working payload
    pub exploitable: bool,
    /// Evidence chain
    pub evidence: Vec<DastEvidence>,
    /// Remediation guidance
    pub remediation: Option<String>,
    /// Linked SAST finding ID (if correlated)
    pub linked_sast_finding_id: Option<String>,
    /// Pentest session that produced this finding (if AI-driven)
    pub session_id: Option<String>,
    #[serde(with = "super::serde_helpers::bson_datetime")]
    pub created_at: DateTime<Utc>,
}

impl DastFinding {
    #[allow(clippy::too_many_arguments)]
    pub fn new(
        scan_run_id: String,
        target_id: String,
        vuln_type: DastVulnType,
        title: String,
        description: String,
        severity: Severity,
        endpoint: String,
        method: String,
    ) -> Self {
        Self {
            id: None,
            scan_run_id,
            target_id,
            vuln_type,
            title,
            description,
            severity,
            cwe: None,
            endpoint,
            method,
            parameter: None,
            exploitable: false,
            evidence: Vec::new(),
            remediation: None,
            linked_sast_finding_id: None,
            session_id: None,
            created_at: Utc::now(),
        }
    }
}
