#!/usr/bin/env python3
from __future__ import annotations

import csv
import json
import py_compile
import re
import shutil
import sys
import xml.etree.ElementTree as ET
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
ERRORS: list[str] = []
WARNINGS: list[str] = []
CHECKS: list[str] = []


def ok(message: str) -> None:
    CHECKS.append(message)


def fail(message: str) -> None:
    ERRORS.append(message)


def warn(message: str) -> None:
    WARNINGS.append(message)


def active_files(pattern: str):
    for path in ROOT.rglob(pattern):
        rel = path.relative_to(ROOT)
        if rel.parts[:1] == ("attachments",):
            continue
        if rel.parts[:2] == ("research", "original"):
            continue
        if rel.parts[:1] == ("tests",):
            continue
        if any(part in {"bin", "obj", "node_modules", "__pycache__"} for part in rel.parts):
            continue
        yield path


required = [
    "README.md",
    "AGENTS.md",
    "global.json",
    "KArtSell.sln",
    "docs/v12/00_EXECUTIVE_INTEGRATED_PROPOSAL.md",
    "docs/v12/07_WBS_MASTER.csv",
    "docs/v12/08_TECH_DEBT_REGISTER.csv",
    "docs/v12/09_TRACEABILITY_MATRIX.csv",
    "db/migrations/0012_signal_engine_integrated_hardening.sql",
    "research/hardening/TEST_RESULTS.txt",
    "attachments/original/K-ArtSell_12_2_complete_package(1).zip",
    "attachments/original/K-ArtSell_퀀트투자자문_SI_본프로그램착수_누적고도화_통합실행기준서_v10.0(1).docx",
    "attachments/original/KArtSell_v11_implementation_acceleration(1).zip",
    "attachments/original/KArtSell_Aegis_v11_1_hardening(1).zip",
]
for name in required:
    if not (ROOT / name).exists():
        fail(f"missing required file: {name}")
if not ERRORS:
    ok(f"required files present: {len(required)}")

for path in active_files("*.json"):
    try:
        json.loads(path.read_text(encoding="utf-8"))
    except Exception as exc:
        fail(f"invalid JSON {path.relative_to(ROOT)}: {exc}")
ok("JSON parse complete")

for path in [*active_files("*.csproj"), *active_files("*.props")]:
    try:
        ET.parse(path)
    except Exception as exc:
        fail(f"invalid XML {path.relative_to(ROOT)}: {exc}")
ok("MSBuild XML parse complete")

for path in active_files("*.csv"):
    try:
        with path.open(encoding="utf-8-sig", newline="") as handle:
            rows = list(csv.reader(handle))
        if not rows:
            fail(f"empty CSV {path.relative_to(ROOT)}")
        else:
            width = len(rows[0])
            bad = [i + 1 for i, row in enumerate(rows) if len(row) != width]
            if bad:
                fail(f"ragged CSV {path.relative_to(ROOT)} rows {bad[:10]}")
    except Exception as exc:
        fail(f"invalid CSV {path.relative_to(ROOT)}: {exc}")
ok("CSV structure complete")

for path in active_files("*.py"):
    try:
        py_compile.compile(str(path), doraise=True)
    except Exception as exc:
        fail(f"Python compile failed {path.relative_to(ROOT)}: {exc}")
ok("Python compile complete")

for path in active_files("*.yaml"):
    text = path.read_text(encoding="utf-8")
    if "\t" in text:
        fail(f"YAML contains tab indentation: {path.relative_to(ROOT)}")
    if not text.strip():
        fail(f"empty YAML: {path.relative_to(ROOT)}")
ok("YAML basic checks complete")

source_files = [*active_files("*.cs"), *active_files("*.sql")]
patterns = {
    "AllowAnonymous()": "anonymous module endpoint",
    "IGenericRepository": "generic repository",
    "DateTime.Now": "direct wall-clock use",
    "DateTime.UtcNow": "direct wall-clock use",
}
for token, label in patterns.items():
    hits = [str(p.relative_to(ROOT)) for p in source_files if token in p.read_text(encoding="utf-8")]
    if hits:
        fail(f"{label} pattern {token}: {hits}")

select_star = []
for path in source_files:
    text = path.read_text(encoding="utf-8")
    if re.search(r"\bselect\s+\*", text, flags=re.IGNORECASE):
        select_star.append(str(path.relative_to(ROOT)))
if select_star:
    fail(f"SELECT * prohibited: {select_star}")
ok("prohibited source pattern scan complete")

for path in active_files("Endpoint.cs"):
    text = path.read_text(encoding="utf-8")
    if "Roles(" not in text and "Policies(" not in text:
        fail(f"endpoint lacks Roles/Policies: {path.relative_to(ROOT)}")
ok("endpoint authorization declarations complete")

for path in active_files("*.cs"):
    if f"{Path('Domain')}" in str(path.relative_to(ROOT)):
        text = path.read_text(encoding="utf-8")
        for token in ["using Dapper", "using Npgsql", "using FastEndpoints", "using Hangfire", "HttpContext", "DbConnection"]:
            if token in text:
                fail(f"domain framework dependency {token}: {path.relative_to(ROOT)}")
ok("domain dependency scan complete")

migration_names = sorted(p.name for p in (ROOT / "db" / "migrations").glob("*.sql"))
if not all(re.match(r"^\d{4}_[a-z0-9_]+\.sql$", name) for name in migration_names):
    fail("migration filename format must be NNNN_snake_case.sql")
if len(migration_names) != len(set(migration_names)):
    fail("duplicate migration filename")
ok(f"migration ordering complete: {len(migration_names)} scripts")

for path in active_files("*"):
    if path.is_file() and path.name.lower() in {"testfile", "temp", "tmp"}:
        fail(f"placeholder file: {path.relative_to(ROOT)}")
ok("placeholder scan complete")

if not (ROOT / "frontend" / "pnpm-lock.yaml").exists():
    warn("frontend/pnpm-lock.yaml is missing by design; G0 must generate and review it")
if shutil.which("dotnet") is None:
    warn("dotnet SDK unavailable in current validation environment")
if shutil.which("pnpm") is None:
    warn("pnpm unavailable in current validation environment")

print("K-ArtSell Aegis v12.0 static validation")
print(f"root={ROOT}")
for message in CHECKS:
    print(f"PASS: {message}")
for message in WARNINGS:
    print(f"WARN: {message}")
for message in ERRORS:
    print(f"FAIL: {message}")
print(f"summary: pass={len(CHECKS)} warn={len(WARNINGS)} fail={len(ERRORS)}")
sys.exit(1 if ERRORS else 0)
