"""Unit tests for history exporters."""

import csv
import io
import json
import tempfile
from pathlib import Path

import pytest

from tools.view_history.exporters.csv_exporter import CsvExporter
from tools.view_history.exporters.json_exporter import JsonExporter
from tools.view_history.exporters.markdown_exporter import MarkdownExporter


@pytest.fixture
def sample_audits() -> list[dict]:
    """Sample audit data."""
    return [
        {
            "dir_name": "2026-01-29_14-30-45_security",
            "timestamp": "2026-01-29T14:30:45",
            "lens": "security",
            "confidence": "high",
            "summary": {
                "total_findings": 3,
                "files_examined": 42,
                "steps_completed": 6,
                "duration_seconds": 120,
                "by_severity": {
                    "critical": 0,
                    "high": 2,
                    "medium": 1,
                    "low": 0,
                    "info": 0,
                },
            },
            "findings": [
                {
                    "severity": "high",
                    "category": "SQL Injection",
                    "description": "Unsafe query in db.py",
                    "remediation": "Use parameterized queries",
                    "affected_files": ["src/db.py"],
                    "cwe_id": "CWE-89",
                },
                {
                    "severity": "high",
                    "category": "XSS",
                    "description": "Unescaped output",
                    "remediation": "Use html.escape()",
                    "affected_files": ["src/views.py", "src/templates/base.html"],
                    "cwe_id": "CWE-79",
                },
                {
                    "severity": "medium",
                    "category": "Hardcoded Secret",
                    "description": "API key in source",
                    "remediation": "Use environment variables",
                    "affected_files": [{"file_path": "src/config.py"}],
                },
            ],
        },
        {
            "dir_name": "2026-01-28_10-00-00_a11y",
            "timestamp": "2026-01-28T10:00:00",
            "lens": "a11y",
            "confidence": "medium",
            "total_findings": 2,
            "files_examined": 15,
            "by_severity": {"critical": 1, "high": 0, "medium": 1, "low": 0, "info": 0},
            "findings": [
                {
                    "severity": "critical",
                    "category": "Missing Alt Text",
                    "description": "Images without alt attributes",
                    "affected_files": ["src/components/Image.tsx"],
                },
                {
                    "severity": "medium",
                    "category": "Color Contrast",
                    "description": "Insufficient contrast ratio",
                    "affected_files": ["src/styles/main.css"],
                },
            ],
        },
    ]


@pytest.fixture
def empty_findings_audit() -> list[dict]:
    """Audit with no findings."""
    return [
        {
            "dir_name": "2026-01-27_08-00-00_security",
            "timestamp": "2026-01-27T08:00:00",
            "lens": "security",
            "confidence": "high",
            "total_findings": 0,
            "files_examined": 50,
            "by_severity": {},
            "findings": [],
        }
    ]


class TestJsonExporter:
    def test_to_dict_structure(self, sample_audits):
        exporter = JsonExporter("my-project", sample_audits)
        data = exporter.to_dict()

        assert "exported_at" in data
        assert data["project"] == "my-project"
        assert data["total_audits"] == 2
        assert len(data["audits"]) == 2

    def test_single_audit_export(self, sample_audits):
        exporter = JsonExporter("my-project", sample_audits)
        data = exporter.to_dict()

        audit = data["audits"][0]
        assert audit["id"] == "2026-01-29_14-30-45_security"
        assert audit["lens"] == "security"
        assert audit["confidence"] == "high"
        assert audit["findings_count"] == 3
        assert audit["files_examined"] == 42
        assert audit["steps_completed"] == 6
        assert audit["duration_seconds"] == 120

    def test_findings_normalized(self, sample_audits):
        exporter = JsonExporter("my-project", sample_audits)
        data = exporter.to_dict()

        finding = data["audits"][0]["findings"][0]
        assert finding["severity"] == "high"
        assert finding["category"] == "SQL Injection"
        assert finding["cwe_id"] == "CWE-89"
        assert finding["affected_files"] == ["src/db.py"]

    def test_dict_affected_files_normalized(self, sample_audits):
        exporter = JsonExporter("my-project", sample_audits)
        data = exporter.to_dict()

        finding = data["audits"][0]["findings"][2]
        assert finding["affected_files"] == ["src/config.py"]

    def test_to_json_string(self, sample_audits):
        exporter = JsonExporter("my-project", sample_audits)
        json_str = exporter.to_json()

        parsed = json.loads(json_str)
        assert parsed["project"] == "my-project"
        assert len(parsed["audits"]) == 2

    def test_export_to_file(self, sample_audits):
        exporter = JsonExporter("my-project", sample_audits)

        with tempfile.NamedTemporaryFile(
            mode="w", suffix=".json", delete=False
        ) as f:
            output_path = Path(f.name)

        try:
            exporter.export(output_path)
            content = output_path.read_text()
            data = json.loads(content)
            assert data["project"] == "my-project"
        finally:
            output_path.unlink()

    def test_empty_audits(self):
        exporter = JsonExporter("my-project", [])
        data = exporter.to_dict()

        assert data["total_audits"] == 0
        assert data["audits"] == []

    def test_flat_audit_structure(self):
        """Test audits with flat structure (no summary object)."""
        audits = [
            {
                "dir_name": "test-audit",
                "timestamp": "2026-01-29T14:30:45",
                "lens": "security",
                "total_findings": 5,
                "files_examined": 10,
                "by_severity": {"high": 2, "medium": 3},
                "findings": [],
            }
        ]

        exporter = JsonExporter("my-project", audits)
        data = exporter.to_dict()

        assert data["audits"][0]["findings_count"] == 5
        assert data["audits"][0]["files_examined"] == 10


class TestCsvExporter:
    def test_headers(self, sample_audits):
        exporter = CsvExporter("my-project", sample_audits)
        csv_str = exporter.to_csv()

        reader = csv.reader(io.StringIO(csv_str))
        headers = next(reader)

        assert headers == CsvExporter.HEADERS

    def test_row_per_finding(self, sample_audits):
        exporter = CsvExporter("my-project", sample_audits)
        csv_str = exporter.to_csv()

        reader = csv.reader(io.StringIO(csv_str))
        rows = list(reader)

        assert len(rows) == 6

    def test_row_content(self, sample_audits):
        exporter = CsvExporter("my-project", sample_audits)
        csv_str = exporter.to_csv()

        reader = csv.DictReader(io.StringIO(csv_str))
        rows = list(reader)

        first_finding = rows[0]
        assert first_finding["audit_id"] == "2026-01-29_14-30-45_security"
        assert first_finding["audit_date"] == "2026-01-29"
        assert first_finding["audit_time"] == "14:30:45"
        assert first_finding["lens"] == "security"
        assert first_finding["severity"] == "high"
        assert first_finding["category"] == "SQL Injection"
        assert first_finding["cwe_id"] == "CWE-89"

    def test_multiple_affected_files_pipe_separated(self, sample_audits):
        exporter = CsvExporter("my-project", sample_audits)
        csv_str = exporter.to_csv()

        reader = csv.DictReader(io.StringIO(csv_str))
        rows = list(reader)

        xss_finding = rows[1]
        assert xss_finding["affected_files"] == "src/views.py|src/templates/base.html"

    def test_dict_affected_files_normalized(self, sample_audits):
        exporter = CsvExporter("my-project", sample_audits)
        csv_str = exporter.to_csv()

        reader = csv.DictReader(io.StringIO(csv_str))
        rows = list(reader)

        secret_finding = rows[2]
        assert secret_finding["affected_files"] == "src/config.py"

    def test_audit_without_findings(self, empty_findings_audit):
        exporter = CsvExporter("my-project", empty_findings_audit)
        csv_str = exporter.to_csv()

        reader = csv.DictReader(io.StringIO(csv_str))
        rows = list(reader)

        assert len(rows) == 1
        assert rows[0]["audit_id"] == "2026-01-27_08-00-00_security"
        assert rows[0]["severity"] == ""
        assert rows[0]["category"] == ""

    def test_export_to_file(self, sample_audits):
        exporter = CsvExporter("my-project", sample_audits)

        with tempfile.NamedTemporaryFile(
            mode="w", suffix=".csv", delete=False
        ) as f:
            output_path = Path(f.name)

        try:
            exporter.export(output_path)
            content = output_path.read_text(encoding="utf-8")
            assert content.startswith("\ufeff")
            assert "SQL Injection" in content
        finally:
            output_path.unlink()

    def test_special_characters_escaped(self):
        audits = [
            {
                "dir_name": "test",
                "timestamp": "2026-01-29T14:30:45",
                "lens": "security",
                "findings": [
                    {
                        "severity": "high",
                        "category": 'Test "quotes" and, commas',
                        "description": "Line1\nLine2",
                        "affected_files": ["file.py"],
                    }
                ],
            }
        ]

        exporter = CsvExporter("my-project", audits)
        csv_str = exporter.to_csv()

        reader = csv.DictReader(io.StringIO(csv_str))
        rows = list(reader)

        assert rows[0]["category"] == 'Test "quotes" and, commas'
        assert rows[0]["description"] == "Line1\nLine2"

    def test_empty_audits(self):
        exporter = CsvExporter("my-project", [])
        csv_str = exporter.to_csv()

        reader = csv.reader(io.StringIO(csv_str))
        rows = list(reader)

        assert len(rows) == 1


class TestMarkdownExporter:
    def test_header_structure(self, sample_audits):
        exporter = MarkdownExporter("my-project", sample_audits)
        md = exporter.to_markdown()

        assert md.startswith("# Audit Summary: my-project")
        assert "**Generated:**" in md
        assert "**Total Audits:** 2" in md
        assert "**Total Findings:** 5" in md

    def test_audit_sections(self, sample_audits):
        exporter = MarkdownExporter("my-project", sample_audits)
        md = exporter.to_markdown()

        assert "### Security Audit" in md
        assert "### A11y Audit" in md

    def test_findings_grouped_by_severity(self, sample_audits):
        exporter = MarkdownExporter("my-project", sample_audits)
        md = exporter.to_markdown()

        assert "#### High Severity" in md
        assert "#### Medium Severity" in md
        assert "#### Critical Severity" in md

    def test_finding_format(self, sample_audits):
        exporter = MarkdownExporter("my-project", sample_audits)
        md = exporter.to_markdown()

        assert "**SQL Injection**" in md
        assert "Unsafe query in db.py" in md
        assert "`src/db.py`" in md
        assert "Fix: Use parameterized queries" in md

    def test_multiple_files_formatted(self, sample_audits):
        exporter = MarkdownExporter("my-project", sample_audits)
        md = exporter.to_markdown()

        assert "`src/views.py`" in md
        assert "`src/templates/base.html`" in md

    def test_dict_affected_files_normalized(self, sample_audits):
        exporter = MarkdownExporter("my-project", sample_audits)
        md = exporter.to_markdown()

        assert "`src/config.py`" in md

    def test_sorted_by_timestamp_desc(self, sample_audits):
        exporter = MarkdownExporter("my-project", sample_audits)
        md = exporter.to_markdown()

        security_pos = md.find("### Security Audit")
        a11y_pos = md.find("### A11y Audit")

        assert security_pos < a11y_pos

    def test_audit_without_findings(self, empty_findings_audit):
        exporter = MarkdownExporter("my-project", empty_findings_audit)
        md = exporter.to_markdown()

        assert "### Security Audit" in md
        assert "**Findings:** 0" in md

    def test_export_to_file(self, sample_audits):
        exporter = MarkdownExporter("my-project", sample_audits)

        with tempfile.NamedTemporaryFile(
            mode="w", suffix=".md", delete=False
        ) as f:
            output_path = Path(f.name)

        try:
            exporter.export(output_path)
            content = output_path.read_text(encoding="utf-8")
            assert "# Audit Summary: my-project" in content
        finally:
            output_path.unlink()

    def test_empty_audits(self):
        exporter = MarkdownExporter("my-project", [])
        md = exporter.to_markdown()

        assert "# Audit Summary: my-project" in md
        assert "**Total Audits:** 0" in md
        assert "**Total Findings:** 0" in md

    def test_date_formatting(self, sample_audits):
        exporter = MarkdownExporter("my-project", sample_audits)
        md = exporter.to_markdown()

        assert "Jan 29, 2026" in md
        assert "Jan 28, 2026" in md

    def test_finding_without_remediation(self):
        audits = [
            {
                "dir_name": "test",
                "timestamp": "2026-01-29T14:30:45",
                "lens": "security",
                "total_findings": 1,
                "by_severity": {"high": 1},
                "findings": [
                    {
                        "severity": "high",
                        "category": "Test Issue",
                        "description": "Description",
                        "affected_files": ["file.py"],
                    }
                ],
            }
        ]

        exporter = MarkdownExporter("my-project", audits)
        md = exporter.to_markdown()

        assert "**Test Issue**" in md
        assert "Fix:" not in md
