"""Unit tests for Semgrep report normalization."""

from __future__ import annotations

import json
from pathlib import Path

from ci.scripts import semgrep_report


def test_build_report_counts_severities_and_blocks_errors() -> None:
    """ERROR findings should fail the report while lower severities remain visible."""

    report = semgrep_report.build_report(
        {
            "results": [
                {"extra": {"severity": "ERROR"}},
                {"extra": {"severity": "WARNING"}},
                {"extra": {"severity": "INFO"}},
                {"extra": {"severity": "unknown-ish"}},
            ],
            "errors": [],
        },
        semgrep_exit_code=1,
    )

    assert report["status"] == "failed"
    assert report["findings_total"] == 4
    assert report["blocking_findings"] == 1
    assert report["severity_counts"] == {
        "ERROR": 1,
        "WARNING": 1,
        "INFO": 1,
        "UNKNOWN": 1,
    }


def test_build_report_marks_clean_scan_ok() -> None:
    """Clean Semgrep JSON with exit code zero should publish an OK report."""

    report = semgrep_report.build_report(
        {"results": [], "errors": []},
        semgrep_exit_code=0,
    )

    assert report["status"] == "ok"
    assert report["blocking_findings"] == 0
    assert report["errors_total"] == 0


def test_build_sonar_issues_converts_findings() -> None:
    """Semgrep findings should be importable by SonarQube generic issues."""

    issues = semgrep_report.build_sonar_issues(
        {
            "results": [
                {
                    "check_id": "python.lang.security.audit.danger",
                    "path": "app/main.py",
                    "start": {"line": 7},
                    "end": {"line": 9},
                    "extra": {"severity": "ERROR", "message": "dangerous call"},
                },
                {"check_id": "missing-path", "extra": {"severity": "INFO"}},
            ]
        }
    )

    assert len(issues["issues"]) == 1
    assert issues["issues"][0]["engineId"] == "semgrep"
    assert issues["issues"][0]["severity"] == "CRITICAL"
    assert issues["issues"][0]["primaryLocation"]["filePath"] == "app/main.py"
    assert issues["issues"][0]["primaryLocation"]["textRange"] == {
        "startLine": 7,
        "endLine": 9,
    }


def test_build_sonar_issues_uses_safe_defaults_for_sparse_findings() -> None:
    """Sparse findings should still produce valid file-level Sonar issues."""

    issues = semgrep_report.build_sonar_issues(
        {
            "results": [
                {
                    "path": "app/config.yaml",
                    "start": {"line": "bad"},
                    "end": {"line": 0},
                    "extra": "not-a-dict",
                }
            ]
        }
    )

    issue = issues["issues"][0]
    assert issue["ruleId"] == "semgrep.unknown"
    assert issue["type"] == "CODE_SMELL"
    assert issue["severity"] == "INFO"
    assert issue["primaryLocation"]["message"] == "Semgrep finding"
    assert issue["primaryLocation"]["textRange"] == {"startLine": 1, "endLine": 1}


def test_read_json_handles_invalid_json_and_non_object(tmp_path: Path) -> None:
    """Report loading should fail closed for bad JSON and wrong top-level shapes."""

    bad_json = tmp_path / "bad.json"
    bad_json.write_text("{bad", encoding="utf-8")
    list_json = tmp_path / "list.json"
    list_json.write_text("[]", encoding="utf-8")

    assert semgrep_report._read_json(bad_json)["errors"][0]["message"].startswith("invalid JSON:")
    assert semgrep_report._read_json(list_json) == {
        "errors": [{"message": "report payload is not an object"}],
        "results": [],
    }


def test_main_handles_missing_or_invalid_input(tmp_path: Path) -> None:
    """Missing or invalid Semgrep JSON should still produce a failed report file."""

    output = tmp_path / "build" / "semgrep-report.json"

    rc = semgrep_report.main(
        [
            "--semgrep-json",
            str(tmp_path / "missing.json"),
            "--exit-code",
            "2",
            "--output",
            str(output),
            "--sonar-issues-output",
            str(tmp_path / "build" / "sonar-issues.json"),
        ]
    )

    assert rc == 0
    payload = json.loads(output.read_text(encoding="utf-8"))
    assert payload["status"] == "failed"
    assert payload["errors_total"] == 1
    sonar_payload = json.loads((tmp_path / "build" / "sonar-issues.json").read_text(encoding="utf-8"))
    assert sonar_payload == {"issues": []}


def test_main_honors_custom_blocking_severity_without_sonar_output(tmp_path: Path) -> None:
    """CLI callers can choose stricter blocking severities without writing Sonar issues."""

    semgrep_json = tmp_path / "semgrep.json"
    semgrep_json.write_text(
        json.dumps({"results": [{"path": "app.py", "extra": {"severity": "WARNING"}}], "errors": []}),
        encoding="utf-8",
    )
    output = tmp_path / "semgrep-report.json"

    rc = semgrep_report.main(
        [
            "--semgrep-json",
            str(semgrep_json),
            "--exit-code",
            "1",
            "--output",
            str(output),
            "--blocking-severity",
            "WARNING",
        ]
    )

    assert rc == 0
    payload = json.loads(output.read_text(encoding="utf-8"))
    assert payload["status"] == "failed"
    assert payload["blocking_findings"] == 1
