import argparse
import csv
import hashlib
import json
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
from typing import Iterable


DEFAULT_EXCLUDE_DIRS = {
    ".git",
    ".venv",
    "__pycache__",
    ".mypy_cache",
    ".pytest_cache",
    ".ruff_cache",
    ".vscode",
}

NORMALIZED_SUFFIX = "_normalized.xlsx"


@dataclass
class FileEntry:
    rel_path: str
    size_bytes: int
    sha256: str


def sha256_file(path: Path, chunk_size: int = 1024 * 1024) -> str:
    digest = hashlib.sha256()
    with path.open("rb") as handle:
        for chunk in iter(lambda: handle.read(chunk_size), b""):
            digest.update(chunk)
    return digest.hexdigest()


def iter_files(root: Path, exclude_dirs: set[str]) -> Iterable[Path]:
    for path in sorted(root.rglob("*")):
        if not path.is_file():
            continue
        rel = path.relative_to(root)
        if any(part in exclude_dirs for part in rel.parts):
            continue
        yield path


def build_registry(root: Path, exclude_dirs: set[str]) -> list[FileEntry]:
    entries: list[FileEntry] = []
    for path in iter_files(root, exclude_dirs):
        rel = path.relative_to(root).as_posix()
        entries.append(
            FileEntry(
                rel_path=rel,
                size_bytes=path.stat().st_size,
                sha256=sha256_file(path),
            )
        )
    return entries


def write_registry_csv(entries: list[FileEntry], out_csv: Path) -> None:
    out_csv.parent.mkdir(parents=True, exist_ok=True)
    with out_csv.open("w", encoding="utf-8", newline="") as handle:
        writer = csv.writer(handle)
        writer.writerow(["path", "size_bytes", "sha256"])
        for e in entries:
            writer.writerow([e.rel_path, e.size_bytes, e.sha256])


def write_hash_manifest(entries: list[FileEntry], out_manifest: Path) -> None:
    out_manifest.parent.mkdir(parents=True, exist_ok=True)
    with out_manifest.open("w", encoding="utf-8") as handle:
        for e in entries:
            handle.write(f"{e.sha256}  {e.rel_path}\n")


def collect_pipeline_checks(root: Path) -> list[str]:
    checks: list[str] = []

    input_files = sorted(
        p for p in root.glob(f"*{NORMALIZED_SUFFIX}") if p.is_file() and not p.name.startswith("~$")
    )

    csv_dir = root / "out" / "csv"
    json_dir = root / "out" / "json"
    xlsx_dir = root / "out" / "excel"

    for src in input_files:
        slug = src.stem
        expected = [
            csv_dir / f"{slug}_LETTER_MATRIX.csv",
            json_dir / f"{slug}_LETTER_MATRIX.json",
            xlsx_dir / f"{slug}_LETTER_MATRIX.xlsx",
        ]
        for target in expected:
            if not target.exists():
                checks.append(f"MISSING_OUTPUT: {target.relative_to(root).as_posix()}")

    for json_file in sorted(json_dir.glob("*_LETTER_MATRIX.json")):
        try:
            payload = json.loads(json_file.read_text(encoding="utf-8"))
            totals = payload.get("totals", {})
            summed = sum(int(v) for v in totals.values())
            total_letters = int(payload.get("total_letters", -1))
            if summed != total_letters:
                checks.append(
                    "JSON_TOTAL_MISMATCH: "
                    f"{json_file.relative_to(root).as_posix()} "
                    f"sum(totals)={summed} total_letters={total_letters}"
                )
        except Exception as exc:  # noqa: BLE001
            checks.append(f"JSON_READ_ERROR: {json_file.relative_to(root).as_posix()} {exc}")

    return checks


def write_validation_report(
    entries: list[FileEntry],
    checks: list[str],
    root: Path,
    out_report: Path,
    exclude_dirs: set[str],
    strict_duplicates: bool,
) -> str:
    out_report.parent.mkdir(parents=True, exist_ok=True)

    hash_map: dict[str, list[str]] = {}
    total_size = 0
    for e in entries:
        total_size += e.size_bytes
        hash_map.setdefault(e.sha256, []).append(e.rel_path)

    duplicate_groups = [paths for paths in hash_map.values() if len(paths) > 1]

    status = "PASS" if not checks else "FAIL"
    if strict_duplicates and duplicate_groups:
        status = "FAIL"
    now = datetime.now(timezone.utc).isoformat()

    lines: list[str] = []
    lines.append("WORKSPACE VALIDATION REPORT")
    lines.append("=" * 28)
    lines.append(f"Generated UTC: {now}")
    lines.append(f"Workspace: {root.as_posix()}")
    lines.append(f"Status: {status}")
    lines.append("")
    lines.append("Summary")
    lines.append("-------")
    lines.append(f"Files scanned: {len(entries)}")
    lines.append(f"Total size bytes: {total_size}")
    lines.append(f"Duplicate hash groups: {len(duplicate_groups)}")
    lines.append(f"Strict duplicates mode: {'ON' if strict_duplicates else 'OFF'}")
    lines.append(f"Excluded directories: {', '.join(sorted(exclude_dirs))}")
    lines.append("")

    lines.append("Validation Checks")
    lines.append("-----------------")
    if checks:
        for item in checks:
            lines.append(f"- {item}")
    else:
        lines.append("- OK: All validation checks passed.")
    lines.append("")

    lines.append("Duplicate Hash Groups (if any)")
    lines.append("------------------------------")
    if duplicate_groups:
        for i, group in enumerate(duplicate_groups, start=1):
            lines.append(f"Group {i}:")
            for path in group:
                lines.append(f"  * {path}")
    else:
        lines.append("- None")

    out_report.write_text("\n".join(lines) + "\n", encoding="utf-8")
    return status


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(
        description="Generate workspace file registry, SHA256 hash manifest, and TXT validation report."
    )
    parser.add_argument(
        "--root",
        type=Path,
        default=Path(__file__).resolve().parent,
        help="Workspace root directory to audit.",
    )
    parser.add_argument(
        "--output-dir",
        type=Path,
        default=Path("out") / "audit",
        help="Output directory for generated audit artifacts.",
    )
    parser.add_argument(
        "--exclude-dir",
        action="append",
        default=[],
        help="Directory name to exclude. Can be supplied multiple times.",
    )
    parser.add_argument(
        "--strict-duplicates",
        action="store_true",
        help="Fail validation when duplicate hash groups are present.",
    )
    return parser.parse_args()


def main() -> int:
    args = parse_args()
    root = args.root.resolve()

    exclude_dirs = set(DEFAULT_EXCLUDE_DIRS)
    exclude_dirs.update(args.exclude_dir)

    output_dir = args.output_dir
    if not output_dir.is_absolute():
        output_dir = root / output_dir
    output_dir.mkdir(parents=True, exist_ok=True)

    registry_path = output_dir / "workspace_registry.csv"
    hash_manifest_path = output_dir / "workspace_hash_manifest.sha256"
    report_path = output_dir / "workspace_validation_report.txt"

    entries = build_registry(root, exclude_dirs)
    write_registry_csv(entries, registry_path)
    write_hash_manifest(entries, hash_manifest_path)

    checks = collect_pipeline_checks(root)
    status = write_validation_report(
        entries,
        checks,
        root,
        report_path,
        exclude_dirs,
        strict_duplicates=args.strict_duplicates,
    )

    print(f"STATUS={status}")
    print(f"REGISTRY={registry_path.relative_to(root).as_posix()}")
    print(f"HASH_MANIFEST={hash_manifest_path.relative_to(root).as_posix()}")
    print(f"VALIDATION_REPORT={report_path.relative_to(root).as_posix()}")

    return 0 if status == "PASS" else 1


if __name__ == "__main__":
    raise SystemExit(main())
