//! Markdown report generator.

use super::escape::{escape_markdown_inline, escape_markdown_list, escape_markdown_table, escape_md_opt};
use super::{ReportConfig, ReportError, ReportFormat, ReportGenerator, ReportType};
use crate::diff::{DiffResult, SlaStatus, VulnerabilityDetail};
use crate::model::NormalizedSbom;
use std::fmt::Write;

/// Markdown report generator
pub struct MarkdownReporter {
    /// Include table of contents
    include_toc: bool,
}

impl MarkdownReporter {
    /// Create a new Markdown reporter
    pub fn new() -> Self {
        Self { include_toc: true }
    }

    /// Set whether to include table of contents
    pub fn include_toc(mut self, include: bool) -> Self {
        self.include_toc = include;
        self
    }
}

impl Default for MarkdownReporter {
    fn default() -> Self {
        Self::new()
    }
}

impl ReportGenerator for MarkdownReporter {
    fn generate_diff_report(
        &self,
        result: &DiffResult,
        old_sbom: &NormalizedSbom,
        new_sbom: &NormalizedSbom,
        config: &ReportConfig,
    ) -> Result<String, ReportError> {
        let mut md = String::new();

        // Title
        let title = config
            .title
            .clone()
            .unwrap_or_else(|| "SBOM Diff Report".to_string());
        writeln!(md, "# {}\n", escape_markdown_inline(&title))?;

        // Metadata
        writeln!(
            md,
            "**Generated by:** sbom-tools v{}",
            env!("CARGO_PKG_VERSION")
        )?;
        writeln!(
            md,
            "**Date:** {}\n",
            chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC")
        )?;

        // Table of contents
        if self.include_toc {
            writeln!(md, "## Table of Contents\n")?;
            writeln!(md, "- [Summary](#summary)")?;
            if config.includes(ReportType::Components) {
                writeln!(md, "- [Component Changes](#component-changes)")?;
            }
            if config.includes(ReportType::Dependencies) {
                writeln!(md, "- [Dependency Changes](#dependency-changes)")?;
            }
            if config.includes(ReportType::Licenses) {
                writeln!(md, "- [License Changes](#license-changes)")?;
            }
            if config.includes(ReportType::Vulnerabilities) {
                writeln!(md, "- [Vulnerability Changes](#vulnerability-changes)")?;
            }
            writeln!(md)?;
        }

        // Summary section
        writeln!(md, "## Summary\n")?;
        writeln!(md, "| Metric | Old SBOM | New SBOM |")?;
        writeln!(md, "|--------|----------|----------|")?;
        writeln!(
            md,
            "| **Format** | {} | {} |",
            old_sbom.document.format, new_sbom.document.format
        )?;
        writeln!(
            md,
            "| **Components** | {} | {} |",
            old_sbom.component_count(),
            new_sbom.component_count()
        )?;
        writeln!(
            md,
            "| **Dependencies** | {} | {} |",
            old_sbom.edges.len(),
            new_sbom.edges.len()
        )?;
        writeln!(md)?;

        writeln!(md, "### Change Summary\n")?;
        writeln!(md, "| Category | Count |")?;
        writeln!(md, "|----------|-------|")?;
        writeln!(
            md,
            "| Components Added | {} |",
            result.summary.components_added
        )?;
        writeln!(
            md,
            "| Components Removed | {} |",
            result.summary.components_removed
        )?;
        writeln!(
            md,
            "| Components Modified | {} |",
            result.summary.components_modified
        )?;
        writeln!(
            md,
            "| Vulnerabilities Introduced | {} |",
            result.summary.vulnerabilities_introduced
        )?;
        writeln!(
            md,
            "| Vulnerabilities Resolved | {} |",
            result.summary.vulnerabilities_resolved
        )?;
        writeln!(md, "| **Semantic Score** | {:.1} |", result.semantic_score)?;
        writeln!(md)?;

        // Component changes section
        if config.includes(ReportType::Components) {
            writeln!(md, "## Component Changes\n")?;

            if !result.components.added.is_empty() {
                writeln!(md, "### Added Components\n")?;
                writeln!(md, "| Name | Version | Ecosystem |")?;
                writeln!(md, "|------|---------|-----------|")?;
                for comp in &result.components.added {
                    writeln!(
                        md,
                        "| {} | {} | {} |",
                        escape_markdown_table(&comp.name),
                        escape_md_opt(comp.new_version.as_deref()),
                        escape_md_opt(comp.ecosystem.as_deref())
                    )?;
                }
                writeln!(md)?;
            }

            if !result.components.removed.is_empty() {
                writeln!(md, "### Removed Components\n")?;
                writeln!(md, "| Name | Version | Ecosystem |")?;
                writeln!(md, "|------|---------|-----------|")?;
                for comp in &result.components.removed {
                    writeln!(
                        md,
                        "| {} | {} | {} |",
                        escape_markdown_table(&comp.name),
                        escape_md_opt(comp.old_version.as_deref()),
                        escape_md_opt(comp.ecosystem.as_deref())
                    )?;
                }
                writeln!(md)?;
            }

            if !result.components.modified.is_empty() {
                writeln!(md, "### Modified Components\n")?;
                writeln!(md, "| Name | Old Version | New Version | Changes |")?;
                writeln!(md, "|------|-------------|-------------|---------|")?;
                for comp in &result.components.modified {
                    let changes: Vec<String> = comp
                        .field_changes
                        .iter()
                        .map(|c| escape_markdown_table(&c.field))
                        .collect();
                    writeln!(
                        md,
                        "| {} | {} | {} | {} |",
                        escape_markdown_table(&comp.name),
                        escape_md_opt(comp.old_version.as_deref()),
                        escape_md_opt(comp.new_version.as_deref()),
                        changes.join(", ")
                    )?;
                }
                writeln!(md)?;
            }
        }

        // Dependency changes section
        if config.includes(ReportType::Dependencies) && !result.dependencies.is_empty() {
            writeln!(md, "## Dependency Changes\n")?;

            if !result.dependencies.added.is_empty() {
                writeln!(md, "### Added Dependencies\n")?;
                writeln!(md, "| From | To | Relationship |")?;
                writeln!(md, "|------|----|--------------|")?;
                for dep in &result.dependencies.added {
                    writeln!(
                        md,
                        "| {} | {} | {} |",
                        escape_markdown_table(&dep.from),
                        escape_markdown_table(&dep.to),
                        escape_markdown_table(&dep.relationship)
                    )?;
                }
                writeln!(md)?;
            }

            if !result.dependencies.removed.is_empty() {
                writeln!(md, "### Removed Dependencies\n")?;
                writeln!(md, "| From | To | Relationship |")?;
                writeln!(md, "|------|----|--------------|")?;
                for dep in &result.dependencies.removed {
                    writeln!(
                        md,
                        "| {} | {} | {} |",
                        escape_markdown_table(&dep.from),
                        escape_markdown_table(&dep.to),
                        escape_markdown_table(&dep.relationship)
                    )?;
                }
                writeln!(md)?;
            }
        }

        // License changes section
        if config.includes(ReportType::Licenses) {
            writeln!(md, "## License Changes\n")?;

            if !result.licenses.new_licenses.is_empty() {
                writeln!(md, "### New Licenses\n")?;
                for lic in &result.licenses.new_licenses {
                    let escaped_components: Vec<String> = lic
                        .components
                        .iter()
                        .map(|c| escape_markdown_list(c))
                        .collect();
                    writeln!(
                        md,
                        "- **{}**: {}",
                        escape_markdown_list(&lic.license),
                        escaped_components.join(", ")
                    )?;
                }
                writeln!(md)?;
            }

            if !result.licenses.removed_licenses.is_empty() {
                writeln!(md, "### Removed Licenses\n")?;
                for lic in &result.licenses.removed_licenses {
                    let escaped_components: Vec<String> = lic
                        .components
                        .iter()
                        .map(|c| escape_markdown_list(c))
                        .collect();
                    writeln!(
                        md,
                        "- **{}**: {}",
                        escape_markdown_list(&lic.license),
                        escaped_components.join(", ")
                    )?;
                }
                writeln!(md)?;
            }

            if !result.licenses.conflicts.is_empty() {
                writeln!(md, "### License Conflicts\n")?;
                writeln!(md, "| License A | License B | Component | Description |")?;
                writeln!(md, "|-----------|-----------|-----------|-------------|")?;
                for conflict in &result.licenses.conflicts {
                    writeln!(
                        md,
                        "| {} | {} | {} | {} |",
                        escape_markdown_table(&conflict.license_a),
                        escape_markdown_table(&conflict.license_b),
                        escape_markdown_table(&conflict.component),
                        escape_markdown_table(&conflict.description)
                    )?;
                }
                writeln!(md)?;
            }
        }

        // Vulnerability changes section
        if config.includes(ReportType::Vulnerabilities) {
            writeln!(md, "## Vulnerability Changes\n")?;

            if !result.vulnerabilities.introduced.is_empty() {
                writeln!(md, "### Introduced Vulnerabilities\n")?;
                writeln!(md, "| ID | Severity | CVSS | SLA | Type | Component | Version |")?;
                writeln!(md, "|----|----------|------|-----|------|-----------|---------|")?;
                for vuln in &result.vulnerabilities.introduced {
                    let depth_label = match vuln.component_depth {
                        Some(1) => "Direct",
                        Some(_) => "Transitive",
                        None => "-",
                    };
                    let sla_display = format_sla_display(vuln);
                    writeln!(
                        md,
                        "| {} | {} | {} | {} | {} | {} | {} |",
                        escape_markdown_table(&vuln.id),
                        escape_markdown_table(&vuln.severity),
                        vuln.cvss_score
                            .map(|s| format!("{:.1}", s))
                            .unwrap_or_else(|| "-".to_string()),
                        escape_markdown_table(&sla_display),
                        depth_label,
                        escape_markdown_table(&vuln.component_name),
                        escape_md_opt(vuln.version.as_deref())
                    )?;
                }
                writeln!(md)?;
            }

            if !result.vulnerabilities.resolved.is_empty() {
                writeln!(md, "### Resolved Vulnerabilities\n")?;
                writeln!(md, "| ID | Severity | SLA | Type | Component |")?;
                writeln!(md, "|----|----------|-----|------|-----------|")?;
                for vuln in &result.vulnerabilities.resolved {
                    let depth_label = match vuln.component_depth {
                        Some(1) => "Direct",
                        Some(_) => "Transitive",
                        None => "-",
                    };
                    let sla_display = format_sla_display(vuln);
                    writeln!(
                        md,
                        "| {} | {} | {} | {} | {} |",
                        escape_markdown_table(&vuln.id),
                        escape_markdown_table(&vuln.severity),
                        escape_markdown_table(&sla_display),
                        depth_label,
                        escape_markdown_table(&vuln.component_name)
                    )?;
                }
                writeln!(md)?;
            }
        }

        // Footer
        writeln!(md, "---\n")?;
        writeln!(md, "*Generated by sbom-tools*")?;

        Ok(md)
    }

    fn generate_view_report(
        &self,
        sbom: &NormalizedSbom,
        config: &ReportConfig,
    ) -> Result<String, ReportError> {
        let mut md = String::new();

        // Title
        let title = config
            .title
            .clone()
            .unwrap_or_else(|| "SBOM Report".to_string());
        writeln!(md, "# {}\n", escape_markdown_inline(&title))?;

        // Metadata
        writeln!(md, "**Format:** {}", sbom.document.format)?;
        writeln!(md, "**Version:** {}", sbom.document.format_version)?;
        if let Some(name) = &sbom.document.name {
            writeln!(md, "**Name:** {}", escape_markdown_inline(name))?;
        }
        writeln!(md)?;

        // Summary
        writeln!(md, "## Summary\n")?;
        writeln!(md, "| Metric | Value |")?;
        writeln!(md, "|--------|-------|")?;
        writeln!(md, "| Total Components | {} |", sbom.component_count())?;
        writeln!(md, "| Total Dependencies | {} |", sbom.edges.len())?;

        let vuln_counts = sbom.vulnerability_counts();
        writeln!(md, "| Total Vulnerabilities | {} |", vuln_counts.total())?;
        writeln!(md, "| Critical | {} |", vuln_counts.critical)?;
        writeln!(md, "| High | {} |", vuln_counts.high)?;
        writeln!(md, "| Medium | {} |", vuln_counts.medium)?;
        writeln!(md, "| Low | {} |", vuln_counts.low)?;
        writeln!(md)?;

        // Components
        writeln!(md, "## Components\n")?;
        writeln!(
            md,
            "| Name | Version | Ecosystem | License | Vulnerabilities |"
        )?;
        writeln!(
            md,
            "|------|---------|-----------|---------|-----------------|"
        )?;

        for comp in sbom.components.values() {
            let license = comp
                .licenses
                .declared
                .first()
                .map(|l| escape_markdown_table(&l.expression))
                .unwrap_or_else(|| "-".to_string());
            writeln!(
                md,
                "| {} | {} | {} | {} | {} |",
                escape_markdown_table(&comp.name),
                escape_md_opt(comp.version.as_deref()),
                comp.ecosystem
                    .as_ref()
                    .map(|e| escape_markdown_table(&e.to_string()))
                    .unwrap_or_else(|| "-".to_string()),
                license,
                comp.vulnerabilities.len()
            )?;
        }

        Ok(md)
    }

    fn format(&self) -> ReportFormat {
        ReportFormat::Markdown
    }
}

/// Format SLA status for display in reports
fn format_sla_display(vuln: &VulnerabilityDetail) -> String {
    match vuln.sla_status() {
        SlaStatus::Overdue(days) => format!("{}d late", days),
        SlaStatus::DueSoon(days) => format!("{}d left", days),
        SlaStatus::OnTrack(days) => format!("{}d left", days),
        SlaStatus::NoDueDate => vuln
            .days_since_published
            .map(|d| format!("{}d old", d))
            .unwrap_or_else(|| "-".to_string()),
    }
}
