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

import argparse
import re
import shutil
from datetime import datetime, timezone
from pathlib import Path


ROOT = Path(__file__).resolve().parents[1]
DEFAULT_OUT_ROOT = ROOT / "exports" / "sanitized"

INCLUDE_FILES = [
    "README.md",
    "CLAUDE.md",
    "Makefile",
]

INCLUDE_GLOBS = [
    "docs/*.md",
    "artifacts/*.md",
    "artifacts/*.csv",
    "findings/data/*.md",
    "findings/data/*.json",
]

EXCLUDE_PARTS = {
    "__pycache__",
}

EXCLUDE_SUFFIXES = {
    ".zip",
    ".rar",
    ".7z",
    ".gz",
    ".tar",
    ".db",
    ".sqlite",
    ".sqlite3",
    ".kdbx",
    ".keys",
    ".key",
    ".pem",
    ".exe",
    ".ods",
    ".log",
    ".csv",
    ".tsv",
}

ALLOWLIST_CSV = {
    Path("artifacts/index.csv"),
    Path("artifacts/status-matrix.csv"),
}

SECRET_PATTERNS: list[tuple[re.Pattern[str], str]] = [
    (re.compile(r"(?i)\bAKIA[0-9A-Z]{16}\b"), "[REDACTED_AWS_ACCESS_KEY]"),
    (re.compile(r"(?i)\bASIA[0-9A-Z]{16}\b"), "[REDACTED_AWS_SESSION_KEY]"),
    (re.compile(r"\bgh[pousr]_[A-Za-z0-9_]{20,}\b"), "[REDACTED_GITHUB_TOKEN]"),
    (re.compile(r"\bgithub_pat_[A-Za-z0-9_]{20,}\b"), "[REDACTED_GITHUB_TOKEN]"),
    (re.compile(r"\bglpat-[A-Za-z0-9_\-]{10,}\b"), "[REDACTED_GITLAB_TOKEN]"),
    (re.compile(r"\bglcbt-[A-Za-z0-9_\-]{10,}\b"), "[REDACTED_GITLAB_CI_TOKEN]"),
    (re.compile(r"\bdckr_pat_[A-Za-z0-9_\-]{10,}\b"), "[REDACTED_DOCKER_TOKEN]"),
    (re.compile(r"\bxox[baprs]-[A-Za-z0-9\-]{20,}\b"), "[REDACTED_SLACK_TOKEN]"),
    (re.compile(r"\bsk-[A-Za-z0-9]{20,}\b"), "[REDACTED_API_KEY]"),
    (re.compile(r"\b(?:eyJ[A-Za-z0-9_\-]{20,})\.[A-Za-z0-9_\-]{10,}\.[A-Za-z0-9_\-]{10,}\b"), "[REDACTED_JWT]"),
    (re.compile(r"-----BEGIN [A-Z ]*PRIVATE KEY-----.*?-----END [A-Z ]*PRIVATE KEY-----", re.S), "[REDACTED_PRIVATE_KEY_BLOCK]"),
    (re.compile(r"(?i)(password|passwd|pass|secret|token|api[_-]?key)(\s*[:=]\s*)(`?)[^`\s|,;]{4,}"), r"\1\2\3[REDACTED]"),
    (re.compile(r"(?i)(/)([A-Za-z0-9!#$%&'()*+,\-.:;<=>?@\[\]^_`{|}~]{8,})(?=\s*(?:\||$))"), r"/[REDACTED]"),
]

EMAIL_PATTERN = re.compile(r"\b[A-Za-z0-9._%+\-]{2,}@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}\b")
IP_PATTERN = re.compile(r"\b(?:(?:25[0-5]|2[0-4]\d|1?\d?\d)\.){3}(?:25[0-5]|2[0-4]\d|1?\d?\d)\b")


def relative(path: Path) -> Path:
    return path.resolve().relative_to(ROOT)


def should_include(path: Path) -> bool:
    rel = relative(path)
    if any(part in EXCLUDE_PARTS for part in rel.parts):
        return False
    if rel in ALLOWLIST_CSV:
        return True
    if path.suffix.lower() in EXCLUDE_SUFFIXES:
        return False
    return True


def iter_sources() -> list[Path]:
    paths: set[Path] = set()
    for name in INCLUDE_FILES:
        path = ROOT / name
        if path.is_file():
            paths.add(path)
    for pattern in INCLUDE_GLOBS:
        for path in ROOT.glob(pattern):
            if path.is_file() and should_include(path):
                paths.add(path)
    return sorted(paths, key=lambda p: str(relative(p)))


def redact(text: str) -> str:
    redacted = text
    for pattern, replacement in SECRET_PATTERNS:
        redacted = pattern.sub(replacement, redacted)
    redacted = EMAIL_PATTERN.sub("[REDACTED_EMAIL]", redacted)
    redacted = IP_PATTERN.sub("[REDACTED_IP]", redacted)
    return redacted


def export_file(src: Path, out_dir: Path) -> None:
    rel = relative(src)
    dst = out_dir / rel
    dst.parent.mkdir(parents=True, exist_ok=True)
    text = src.read_text(encoding="utf-8", errors="replace")
    dst.write_text(redact(text), encoding="utf-8")


def write_manifest(out_dir: Path, sources: list[Path]) -> None:
    lines = [
        "# Sanitized Export Manifest",
        "",
        f"Generated: {datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')}",
        "",
        "## Included Files",
        "",
    ]
    for src in sources:
        lines.append(f"- `{relative(src)}`")
    lines.extend(
        [
            "",
            "## Notes",
            "",
            "- Raw evidence, archives, databases, binaries, and machine-scale credential datasets are excluded.",
            "- Emails, IP addresses, common token formats, private-key blocks, and obvious secret assignments are redacted.",
            "- Manually review this bundle before external sharing.",
            "",
        ]
    )
    (out_dir / "MANIFEST.md").write_text("\n".join(lines), encoding="utf-8")


def main() -> int:
    parser = argparse.ArgumentParser(description="Create a redacted reporting export")
    parser.add_argument("--output", type=Path, help="Output directory. Defaults to exports/sanitized/<timestamp>")
    args = parser.parse_args()

    timestamp = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
    out_dir = args.output or (DEFAULT_OUT_ROOT / timestamp)
    if out_dir.exists():
        shutil.rmtree(out_dir)
    out_dir.mkdir(parents=True)

    sources = iter_sources()
    for src in sources:
        export_file(src, out_dir)
    write_manifest(out_dir, sources)

    print(f"sanitized_export={out_dir}")
    print(f"files={len(sources)}")
    return 0


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