# edited by glg
import json
import re
import subprocess
import sys
from pathlib import Path
from typing import Dict, List

CRITICAL_RUNTIME_MODULES = [
    "pypos/modules/auth",
    "pypos/modules/sinkronisasi",
    "pypos/modules/penjualan",
]

RUFF_FATAL_TARGETS = [
    "scripts/ci",
    "pypos/core/utils/config_utils.py",
    "pypos/core/utils/db_helper.py",
    "pypos/modules/auth/config/auth_config.py",
    "pypos/modules/auth/services/auth_service.py",
    "pypos/modules/dashboard/services/dashboard_value_utils.py",
    "pypos/modules/printer/models/printer_settings_model.py",
    *CRITICAL_RUNTIME_MODULES,
]

MYPY_FATAL_ERROR_CODES = {
    "func-returns-value",
    "misc",
    "no-redef",
    "operator",
    "return",
    "return-value",
}

_MYPY_ERROR_LINE_PATTERN = re.compile(
    r"^(?P<path>.*?):(?P<line>\d+)(?::(?P<column>\d+))?: error: (?P<message>.*?)(?:\s+\[(?P<code>[a-z0-9\-]+)\])?$"
)


def _run_command(args: List[str], *, check: bool = True) -> subprocess.CompletedProcess:
    print("$", " ".join(args))
    completed = subprocess.run(args, check=False)
    if check and completed.returncode != 0:
        raise SystemExit(completed.returncode)
    return completed


def _run_ruff_gate() -> None:
    # Hardening P1-P2: subset lint fatal diperluas ke modul runtime kritikal.
    _run_command(
        [
            sys.executable,
            "-m",
            "ruff",
            "check",
            "--select",
            "E9,F63,F7,F82",
            *RUFF_FATAL_TARGETS,
        ]
    )


def _extract_mypy_errors(stdout: str) -> List[Dict[str, str]]:
    rows: List[Dict[str, str]] = []
    for raw_line in str(stdout or "").splitlines():
        line = str(raw_line or "").strip()
        if not line:
            continue
        match = _MYPY_ERROR_LINE_PATTERN.match(line)
        if match:
            rows.append(
                {
                    "raw": line,
                    "path": str(match.group("path") or "").strip(),
                    "line": str(match.group("line") or "").strip(),
                    "column": str(match.group("column") or "").strip(),
                    "message": str(match.group("message") or "").strip(),
                    "code": str(match.group("code") or "").strip(),
                }
            )
            continue
        if ": error:" in line:
            rows.append(
                {
                    "raw": line,
                    "path": "",
                    "line": "",
                    "column": "",
                    "message": line,
                    "code": "",
                }
            )
    return rows


def _run_mypy_gate_critical_fatal_only() -> None:
    args = [
        sys.executable,
        "-m",
        "mypy",
        "--show-error-codes",
        "--ignore-missing-imports",
        "--follow-imports=skip",
        "--disable-error-code=var-annotated",
        "--disable-error-code=call-overload",
        "--disable-error-code=assignment",
        *CRITICAL_RUNTIME_MODULES,
    ]
    print("$", " ".join(args))
    completed = subprocess.run(args, check=False, text=True, capture_output=True)
    stdout = str(completed.stdout or "")
    stderr = str(completed.stderr or "")
    if stdout:
        print(stdout, end="" if stdout.endswith("\n") else "\n")
    if stderr:
        print(stderr, end="" if stderr.endswith("\n") else "\n")
    Path(".tmp_mypy_critical.log").write_text(f"{stdout}{stderr}", encoding="utf-8")

    if completed.returncode not in (0, 1):
        raise SystemExit(completed.returncode)

    all_errors = _extract_mypy_errors(stdout)
    fatal_hits = [item for item in all_errors if str(item.get("code") or "").strip() in MYPY_FATAL_ERROR_CODES]
    unknown_errors = [item for item in all_errors if not str(item.get("code") or "").strip()]
    if fatal_hits or unknown_errors:
        print("Mypy fatal gate gagal:")
        for item in fatal_hits[:80]:
            print(
                "- {path}:{line}:{column} [{code}] {message}".format(
                    path=str(item.get("path") or "-"),
                    line=str(item.get("line") or "-"),
                    column=str(item.get("column") or "-"),
                    code=str(item.get("code") or "-"),
                    message=str(item.get("message") or "").strip(),
                )
            )
        for item in unknown_errors[:20]:
            print(f"- {str(item.get('raw') or '').strip()}")
        raise SystemExit(1)

    print("Mypy fatal gate lulus: tidak ada error code fatal pada modul runtime kritikal.")


def _load_bandit_report(path: Path) -> Dict:
    with path.open("r", encoding="utf-8") as fh:
        return json.load(fh)


def _run_bandit_gate() -> None:
    report_path = Path(".tmp_bandit_ci.json")
    completed = _run_command(
        [
            sys.executable,
            "-m",
            "bandit",
            "-r",
            "pypos",
            "-f",
            "json",
            "-o",
            str(report_path),
        ],
        check=False,
    )
    if completed.returncode not in (0, 1):
        raise SystemExit(completed.returncode)
    if not report_path.exists():
        raise SystemExit("Bandit tidak menghasilkan report JSON.")

    payload = _load_bandit_report(report_path)
    results = list(payload.get("results") or [])
    high_findings = [
        item
        for item in results
        if str(item.get("issue_severity") or "").upper() == "HIGH"
        and str(item.get("issue_confidence") or "").upper() in {"HIGH", "MEDIUM"}
    ]
    if high_findings:
        print("Bandit gate gagal: temuan HIGH masih ada.")
        for item in high_findings[:20]:
            print(
                "- {test} {file}:{line} {msg}".format(
                    test=str(item.get("test_id") or "-"),
                    file=str(item.get("filename") or "-"),
                    line=int(item.get("line_number") or 0),
                    msg=str(item.get("issue_text") or "").strip(),
                )
            )
        raise SystemExit(1)

    print("Bandit gate lulus: tidak ada temuan HIGH.")


def main() -> None:
    _run_ruff_gate()
    _run_mypy_gate_critical_fatal_only()
    _run_bandit_gate()
    print("Static analysis gate lulus.")


if __name__ == "__main__":
    main()
