"""Helper-branch coverage for the titan-iac quality gate runner."""

from __future__ import annotations

import json
import urllib.error
from pathlib import Path
from types import SimpleNamespace
from typing import Any

import pytest

from testing import quality_gate


class _JsonResponse:
    """Simple context-manager response that returns a JSON payload."""

    def __init__(self, payload: Any):
        self._payload = payload

    def __enter__(self) -> _JsonResponse:
        return self

    def __exit__(self, exc_type, exc, tb) -> bool:
        return False

    def read(self) -> bytes:
        return json.dumps(self._payload).encode("utf-8")


def test_env_flag_truthy_falsey_and_default(monkeypatch):
    monkeypatch.delenv("QUALITY_FLAG", raising=False)
    assert quality_gate._env_flag("QUALITY_FLAG", default=True) is True

    monkeypatch.setenv("QUALITY_FLAG", " yes ")
    assert quality_gate._env_flag("QUALITY_FLAG", default=False) is True

    monkeypatch.setenv("QUALITY_FLAG", "0")
    assert quality_gate._env_flag("QUALITY_FLAG", default=True) is False


def test_load_json_report_handles_missing_invalid_and_non_object(tmp_path: Path):
    payload, issue = quality_gate._load_json_report(tmp_path / "missing.json")
    assert payload is None
    assert issue and issue.startswith("report missing:")

    invalid_path = tmp_path / "invalid.json"
    invalid_path.write_text("{", encoding="utf-8")
    payload, issue = quality_gate._load_json_report(invalid_path)
    assert payload is None
    assert issue and issue.startswith("report invalid JSON:")

    list_path = tmp_path / "list.json"
    list_path.write_text("[]", encoding="utf-8")
    payload, issue = quality_gate._load_json_report(list_path)
    assert payload is None
    assert issue and issue.startswith("report payload must be an object:")

    object_path = tmp_path / "object.json"
    object_path.write_text('{"status":"OK"}', encoding="utf-8")
    payload, issue = quality_gate._load_json_report(object_path)
    assert payload == {"status": "OK"}
    assert issue is None


def test_sonarqube_status_extraction_fallbacks():
    assert quality_gate._sonarqube_gate_status_from_report({"projectStatus": {"status": "OK"}}) == "OK"
    assert quality_gate._sonarqube_gate_status_from_report({"status": "PASSED"}) == "PASSED"
    assert quality_gate._sonarqube_gate_status_from_report({"projectStatus": {"state": "NONE"}}) == ""


def test_fetch_sonarqube_gate_status_happy_path_and_auth(monkeypatch):
    seen_headers: dict[str, str] = {}

    def fake_urlopen(request, timeout):
        assert timeout == 5.0
        seen_headers["authorization"] = request.headers.get("Authorization", "")
        return _JsonResponse({"projectStatus": {"status": "OK"}})

    monkeypatch.setattr(quality_gate.urllib.request, "urlopen", fake_urlopen)
    status, issue = quality_gate._fetch_sonarqube_gate_status(
        "http://sonar.local",
        "atlas",
        "token-value",
        5.0,
    )
    assert status == "OK"
    assert issue is None
    assert seen_headers["authorization"].startswith("Basic ")


def test_fetch_sonarqube_gate_status_handles_non_object_and_network_error(monkeypatch):
    monkeypatch.setattr(quality_gate.urllib.request, "urlopen", lambda *_a, **_k: _JsonResponse(["not", "dict"]))
    status, issue = quality_gate._fetch_sonarqube_gate_status("http://sonar", "atlas", "", 5.0)
    assert status == ""
    assert issue == "sonarqube query returned non-object payload"

    def raise_url_error(*_a, **_k):
        raise urllib.error.URLError("boom")

    monkeypatch.setattr(quality_gate.urllib.request, "urlopen", raise_url_error)
    status, issue = quality_gate._fetch_sonarqube_gate_status("http://sonar", "atlas", "", 5.0)
    assert status == ""
    assert issue and issue.startswith("sonarqube query failed:")


def test_fetch_sonarqube_gate_status_missing_status_message(monkeypatch):
    monkeypatch.setattr(quality_gate.urllib.request, "urlopen", lambda *_a, **_k: _JsonResponse({"projectStatus": {}}))
    status, issue = quality_gate._fetch_sonarqube_gate_status("http://sonar", "atlas", "", 5.0)
    assert status == ""
    assert issue == "sonarqube response missing projectStatus.status"


def test_run_sonarqube_check_uses_report_and_fails_when_status_is_not_ok(tmp_path: Path, monkeypatch):
    build_dir = tmp_path / "build"
    build_dir.mkdir(parents=True, exist_ok=True)
    report = build_dir / "sonarqube-quality-gate.json"
    report.write_text('{"projectStatus":{"status":"ERROR"}}', encoding="utf-8")

    monkeypatch.setenv("QUALITY_GATE_SONARQUBE_REPORT", str(report))
    monkeypatch.setenv("QUALITY_GATE_SONARQUBE_ENFORCE", "1")

    result = quality_gate._run_sonarqube_check(build_dir)
    assert result["source"] == "report"
    assert result["gate_status"] == "ERROR"
    assert result["status"] == "failed"
    assert any("expected OK" in issue for issue in result["issues"])


def test_run_sonarqube_check_uses_api_when_report_missing(tmp_path: Path, monkeypatch):
    build_dir = tmp_path / "build"
    build_dir.mkdir(parents=True, exist_ok=True)
    monkeypatch.setenv("QUALITY_GATE_SONARQUBE_REPORT", str(build_dir / "missing.json"))
    monkeypatch.setenv("SONARQUBE_HOST_URL", "http://sonarqube")
    monkeypatch.setenv("SONARQUBE_PROJECT_KEY", "atlas")
    monkeypatch.setenv("QUALITY_GATE_SONARQUBE_ENFORCE", "1")
    monkeypatch.setattr(quality_gate, "_fetch_sonarqube_gate_status", lambda *_a, **_k: ("OK", None))

    result = quality_gate._run_sonarqube_check(build_dir)
    assert result["source"] == "api"
    assert result["status"] == "ok"
    assert result["gate_status"] == "OK"


def test_run_sonarqube_check_relative_report_missing_status_and_missing_report(tmp_path: Path, monkeypatch):
    build_dir = tmp_path / "build"
    build_dir.mkdir(parents=True, exist_ok=True)
    monkeypatch.chdir(tmp_path)

    relative_report = Path("build") / "sonar.json"
    (tmp_path / relative_report).write_text('{"projectStatus":{}}', encoding="utf-8")
    monkeypatch.setenv("QUALITY_GATE_SONARQUBE_REPORT", str(relative_report))
    monkeypatch.setenv("QUALITY_GATE_SONARQUBE_ENFORCE", "1")
    monkeypatch.delenv("SONARQUBE_HOST_URL", raising=False)
    monkeypatch.delenv("SONARQUBE_PROJECT_KEY", raising=False)
    missing_status = quality_gate._run_sonarqube_check(build_dir)
    assert any("missing quality gate status" in issue for issue in missing_status["issues"])
    assert any("status unavailable" in issue for issue in missing_status["issues"])
    assert missing_status["report_path"].endswith("build/sonar.json")

    missing_relative = Path("build") / "missing-sonar.json"
    monkeypatch.setenv("QUALITY_GATE_SONARQUBE_REPORT", str(missing_relative))
    missing_report = quality_gate._run_sonarqube_check(build_dir)
    assert any("report missing:" in issue for issue in missing_report["issues"])


def test_run_sonarqube_check_fallback_api_path_with_query_error(tmp_path: Path, monkeypatch):
    build_dir = tmp_path / "build"
    build_dir.mkdir(parents=True, exist_ok=True)
    monkeypatch.setenv("QUALITY_GATE_SONARQUBE_ENFORCE", "1")
    monkeypatch.setenv("SONARQUBE_HOST_URL", "http://sonarqube")
    monkeypatch.setenv("SONARQUBE_PROJECT_KEY", "atlas")
    monkeypatch.setattr(quality_gate, "_load_json_report", lambda _path: (None, None))
    monkeypatch.setattr(quality_gate, "_fetch_sonarqube_gate_status", lambda *_a, **_k: ("", "api exploded"))

    result = quality_gate._run_sonarqube_check(build_dir)
    assert result["source"] == "api"
    assert "api exploded" in result["issues"]


def test_ironbank_status_extraction_and_required_missing_behavior(tmp_path: Path, monkeypatch):
    assert quality_gate._ironbank_status_from_report({"status": "compliant"}) == ("compliant", None)
    assert quality_gate._ironbank_status_from_report({"compliant": True}) == ("compliant", True)
    assert quality_gate._ironbank_status_from_report({"unrelated": "value"}) == ("", None)

    build_dir = tmp_path / "build"
    build_dir.mkdir(parents=True, exist_ok=True)

    monkeypatch.setenv("QUALITY_GATE_IRONBANK_REPORT", str(build_dir / "missing.json"))
    monkeypatch.setenv("QUALITY_GATE_IRONBANK_REQUIRED", "0")
    monkeypatch.setenv("QUALITY_GATE_IRONBANK_ENFORCE", "1")
    optional_result = quality_gate._run_ironbank_check(build_dir)
    assert optional_result["status"] == "ok"
    assert optional_result["required"] is False

    monkeypatch.setenv("QUALITY_GATE_IRONBANK_REQUIRED", "1")
    required_result = quality_gate._run_ironbank_check(build_dir)
    assert required_result["status"] == "failed"
    assert any("report missing:" in issue for issue in required_result["issues"])


def test_run_ironbank_check_with_report_compliance_values(tmp_path: Path, monkeypatch):
    build_dir = tmp_path / "build"
    build_dir.mkdir(parents=True, exist_ok=True)
    report = build_dir / "ironbank-compliance.json"

    monkeypatch.setenv("QUALITY_GATE_IRONBANK_REPORT", str(report))
    monkeypatch.setenv("QUALITY_GATE_IRONBANK_REQUIRED", "1")
    monkeypatch.setenv("QUALITY_GATE_IRONBANK_ENFORCE", "1")

    report.write_text('{"status":"compliant"}', encoding="utf-8")
    ok_result = quality_gate._run_ironbank_check(build_dir)
    assert ok_result["status"] == "ok"
    assert ok_result["source"] == "report"

    report.write_text('{"compliant": false}', encoding="utf-8")
    bad_result = quality_gate._run_ironbank_check(build_dir)
    assert bad_result["status"] == "failed"
    assert any("expected compliant" in issue for issue in bad_result["issues"])


def test_run_ironbank_check_relative_path_and_unavailable_status(tmp_path: Path, monkeypatch):
    build_dir = tmp_path / "build"
    build_dir.mkdir(parents=True, exist_ok=True)
    monkeypatch.chdir(tmp_path)

    report_rel = Path("build") / "ironbank.json"
    (tmp_path / report_rel).write_text("{}", encoding="utf-8")
    monkeypatch.setenv("QUALITY_GATE_IRONBANK_REPORT", str(report_rel))
    monkeypatch.setenv("QUALITY_GATE_IRONBANK_REQUIRED", "1")
    monkeypatch.setenv("QUALITY_GATE_IRONBANK_ENFORCE", "1")

    result = quality_gate._run_ironbank_check(build_dir)
    assert result["report_path"].endswith("build/ironbank.json")
    assert result["status"] == "failed"
    assert "ironbank compliance status unavailable" in result["issues"]


def test_status_and_result_helpers():
    assert quality_gate._status_from_issues([]) == "ok"
    assert quality_gate._status_from_issues(["problem"]) == "failed"

    result = quality_gate._result("docs", "desc", "ok", extra=True)
    assert result == {"name": "docs", "description": "desc", "status": "ok", "extra": True}


def test_run_ruff_returns_failed_when_subprocess_nonzero(monkeypatch, tmp_path: Path):
    monkeypatch.setattr(quality_gate.time, "monotonic", lambda: 10.0)
    monkeypatch.setattr(
        quality_gate.subprocess,
        "run",
        lambda *args, **kwargs: SimpleNamespace(returncode=2),
    )
    contract = {"lint_paths": ["testing"]}
    result = quality_gate._run_ruff(contract, tmp_path)
    assert result["name"] == "smell"
    assert result["status"] == "failed"
    assert result["returncode"] == 2
    assert result["command"][:3] == [quality_gate.sys.executable, "-m", "ruff"]


def test_run_pytest_suite_builds_commands_with_and_without_coverage(monkeypatch, tmp_path: Path):
    suite_with_cov = {
        "description": "Unit suite",
        "paths": ["testing/tests"],
        "junit": "build/junit.xml",
        "coverage_xml": "build/coverage.xml",
        "coverage_sources": ["testing"],
    }
    suite_without_cov = {
        "description": "Smoke suite",
        "paths": ["testing/tests"],
        "junit": "build/junit-smoke.xml",
    }

    commands: list[list[str]] = []
    monkeypatch.setattr(quality_gate.time, "monotonic", lambda: 20.0)
    monkeypatch.setattr(
        quality_gate.subprocess,
        "run",
        lambda command, cwd, check: commands.append(command) or SimpleNamespace(returncode=0),
    )

    first = quality_gate._run_pytest_suite(tmp_path, "unit", suite_with_cov)
    second = quality_gate._run_pytest_suite(tmp_path, "smoke", suite_without_cov)

    assert first["status"] == "ok"
    assert first["coverage_xml"] == "build/coverage.xml"
    assert any(item.startswith("--cov=testing") for item in commands[0])
    assert second["status"] == "ok"
    assert second["coverage_xml"] is None
    assert not any(item.startswith("--cov=") for item in commands[1])


def test_run_profile_unknown_profile_and_unknown_check(tmp_path: Path):
    with pytest.raises(SystemExit, match="unknown profile: jenkins"):
        quality_gate.run_profile({"profiles": {}}, tmp_path, "jenkins", tmp_path / "build")

    contract = {"profiles": {"local": ["missing"]}, "pytest_suites": {}}
    with pytest.raises(SystemExit, match="references unknown check: missing"):
        quality_gate.run_profile(contract, tmp_path, "local", tmp_path / "build")


def test_run_profile_routes_sonarqube_and_ironbank_checks(tmp_path: Path, monkeypatch):
    contract = {"profiles": {"local": ["sonarqube", "ironbank"]}, "pytest_suites": {}}
    monkeypatch.setattr(quality_gate, "_run_sonarqube_check", lambda _build_dir: {"name": "sonarqube", "status": "ok"})
    monkeypatch.setattr(quality_gate, "_run_ironbank_check", lambda _build_dir: {"name": "ironbank", "status": "ok"})

    summary = quality_gate.run_profile(contract, tmp_path, "local", tmp_path / "build")
    assert summary["status"] == "ok"
    assert [item["name"] for item in summary["results"]] == ["sonarqube", "ironbank"]


def test_main_returns_failure_when_profile_reports_failed(tmp_path: Path, monkeypatch):
    monkeypatch.chdir(tmp_path)
    monkeypatch.setattr(quality_gate, "load_contract", lambda: {"profiles": {"local": []}, "pytest_suites": {}})
    monkeypatch.setattr(
        quality_gate,
        "run_profile",
        lambda *_a, **_k: {"status": "failed", "profile": "local", "results": [], "manual_scripts": []},
    )

    rc = quality_gate.main(["--profile", "local", "--build-dir", "build"])
    assert rc == 1
    assert (tmp_path / "build" / "quality-gate-summary.json").exists()
