#!/usr/bin/env python3
"""
FREEEXILE CODE & DOCUMENTATION HYGIENE AUDIT TOOL
Enforces strict file modularization, conciseness, and architectural hygiene standards:
- Code File Length: Soft Cap <= 350 lines, Hard Cap <= 500 lines.
- Doc File Length: Soft Cap <= 400 lines, Hard Cap <= 600 lines.
- Function/Method Length: AST check <= 50 lines.
- Proper use of common/, utils/, helpers/ modules.
- Zero lip-service automated quality gating.

Usage:
    python tools/lint/check_code_and_doc_hygiene.py [--strict] [--json-out <path>]
"""

from __future__ import annotations
import argparse
import ast
import os
import sys
import json
from dataclasses import dataclass, field
from typing import List, Dict, Tuple, Optional

# Ensure UTF-8 on Windows
if hasattr(sys.stdout, "reconfigure"):
    sys.stdout.reconfigure(encoding="utf-8")
if hasattr(sys.stderr, "reconfigure"):
    sys.stderr.reconfigure(encoding="utf-8")

PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))

# Excluded directories and auto-generated bundles
EXCLUDE_DIRS = {
    ".git",
    ".pytest_cache",
    ".antigravity",
    ".agents",
    "node_modules",
    "__pycache__",
    ".venv",
    "venv",
    "build",
    "build_ninja",
    "CMakeFiles",
    "x64",
    "Debug",
    "Release",
}

EXCLUDE_FILES = {
    "bundle.d.ts",
    "bundle.js",
    "package-lock.json",
    "GLOBAL_LOCALIZATION_DICTIONARY.md",  # Canonical 9-language master dictionary
}

# Thresholds
CODE_SOFT_CAP = 350
CODE_HARD_CAP = 500

CATALOG_SOFT_CAP = 700
CATALOG_HARD_CAP = 1000

DOC_SOFT_CAP = 400
DOC_HARD_CAP = 600

HTML_SOFT_CAP = 200
HTML_HARD_CAP = 400

CSS_SOFT_CAP = 350
CSS_HARD_CAP = 500

FUNCTION_HARD_CAP = 50

# Active pending audit tickets tracked in audit/ (None pending; 100% clean)
PENDING_AUDIT_EXCLUSIONS: Dict[str, str] = {}


@dataclass
class FileViolation:
    file_path: str
    line_count: int
    category: str  # "CODE" or "DOC"
    severity: str  # "WARNING" or "ERROR"
    message: str


@dataclass
class FunctionViolation:
    file_path: str
    func_name: str
    line_count: int
    start_line: int
    end_line: int


@dataclass
class HygieneAuditReport:
    total_files_scanned: int = 0
    total_code_files: int = 0
    total_doc_files: int = 0
    passed_clean_files: int = 0
    file_violations: List[FileViolation] = field(default_factory=list)
    function_violations: List[FunctionViolation] = field(default_factory=list)
    is_compliant: bool = True


def is_excluded(path: str, filename: str) -> bool:
    if filename in EXCLUDE_FILES:
        return True
    if filename.endswith("_pb2.py") or filename.endswith("_pb2.pyi"):
        return True
    parts = os.path.normpath(path).split(os.sep)
    for part in parts:
        if part in EXCLUDE_DIRS:
            return True
    return False


def audit_file_length(file_path: str, rel_path: str) -> Optional[FileViolation]:
    ext = os.path.splitext(file_path)[1].lower()
    try:
        with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
            lines = f.readlines()
            line_count = len(lines)
    except Exception as e:
        return None

    if ext in {".py", ".ts", ".js", ".cpp", ".h", ".hpp", ".cs"}:
        base_name = os.path.basename(file_path).lower()
        is_catalog = (
            "_catalog" in base_name
            or "catalog_" in base_name
            or "_database" in base_name
            or "_tags" in base_name
            or "tags_" in base_name
            or "data" in os.path.normpath(file_path).split(os.sep)
        )
        hard_cap = CATALOG_HARD_CAP if is_catalog else CODE_HARD_CAP
        soft_cap = CATALOG_SOFT_CAP if is_catalog else CODE_SOFT_CAP
        category = "CATALOG" if is_catalog else "CODE"

        if line_count > hard_cap:
            return FileViolation(
                file_path=rel_path,
                line_count=line_count,
                category=category,
                severity="ERROR",
                message=f"Vượt quá Hard Cap ({line_count} > {hard_cap} dòng). Bắt buộc tách module, catalog hoặc DTOs!",
            )
        elif line_count > soft_cap:
            return FileViolation(
                file_path=rel_path,
                line_count=line_count,
                category=category,
                severity="WARNING",
                message=f"Vượt quá Soft Cap ({line_count} > {soft_cap} dòng). Khuyến nghị tối ưu hóa hoặc chia nhỏ.",
            )

    elif ext in {".html", ".htm"}:
        category = "WEB_HTML"
        if rel_path in PENDING_AUDIT_EXCLUSIONS or rel_path.replace("/", "\\") in PENDING_AUDIT_EXCLUSIONS:
            ticket = PENDING_AUDIT_EXCLUSIONS.get(rel_path) or PENDING_AUDIT_EXCLUSIONS.get(rel_path.replace("/", "\\"))
            return FileViolation(
                file_path=rel_path,
                line_count=line_count,
                category=category,
                severity="WARNING",
                message=f"[PENDING_AUDIT] {ticket} - Đang chờ refactor sang ES Modules.",
            )
        if line_count > HTML_HARD_CAP:
            return FileViolation(
                file_path=rel_path,
                line_count=line_count,
                category=category,
                severity="ERROR",
                message=f"Tệp HTML nguyên khối vượt trần nghiêm trọng ({line_count} > {HTML_HARD_CAP} dòng). Bắt buộc tam phân lập mã nguồn thành Native ES Modules (tách riêng css/ và js/)!",
            )
        elif line_count > HTML_SOFT_CAP:
            return FileViolation(
                file_path=rel_path,
                line_count=line_count,
                category=category,
                severity="WARNING",
                message=f"Tệp HTML dài ({line_count} > {HTML_SOFT_CAP} dòng). Khuyến nghị tách CSS và JS ra các file độc lập.",
            )

    elif ext == ".css":
        category = "WEB_CSS"
        if line_count > CSS_HARD_CAP:
            return FileViolation(
                file_path=rel_path,
                line_count=line_count,
                category=category,
                severity="ERROR",
                message=f"Tệp CSS quá dài ({line_count} > {CSS_HARD_CAP} dòng). Bắt buộc tách thành các stylesheet theo domain (hud.css, shop.css, inventory.css)!",
            )
        elif line_count > CSS_SOFT_CAP:
            return FileViolation(
                file_path=rel_path,
                line_count=line_count,
                category=category,
                severity="WARNING",
                message=f"Tệp CSS dài ({line_count} > {CSS_SOFT_CAP} dòng). Khuyến nghị module hóa.",
            )

    elif ext == ".md":
        category = "DOC"
        if line_count > DOC_HARD_CAP:
            return FileViolation(
                file_path=rel_path,
                line_count=line_count,
                category=category,
                severity="ERROR",
                message=f"Tài liệu quá dài ({line_count} > {DOC_HARD_CAP} dòng). Cần tóm tắt khoa học, súc tích hoặc chia file theo Diátaxis!",
            )
        elif line_count > DOC_SOFT_CAP:
            return FileViolation(
                file_path=rel_path,
                line_count=line_count,
                category=category,
                severity="WARNING",
                message=f"Tài liệu dài ({line_count} > {DOC_SOFT_CAP} dòng). Khuyến nghị cô đọng bảng biểu và sơ đồ.",
            )

    return None


def audit_python_functions(file_path: str, rel_path: str) -> List[FunctionViolation]:
    violations: List[FunctionViolation] = []
    if not file_path.endswith(".py"):
        return violations

    try:
        with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
            code = f.read()
        tree = ast.parse(code, filename=file_path)
    except Exception:
        return violations

    for node in ast.walk(tree):
        if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
            # Ignore test methods which can have long assertions or fixture setups
            if node.name.startswith("test_"):
                continue
            start_line = getattr(node, "lineno", 0)
            end_line = getattr(node, "end_lineno", start_line)
            length = end_line - start_line + 1
            if length > FUNCTION_HARD_CAP:
                violations.append(
                    FunctionViolation(
                        file_path=rel_path,
                        func_name=node.name,
                        line_count=length,
                        start_line=start_line,
                        end_line=end_line,
                    )
                )

    return violations


def run_hygiene_audit(root_dir: str, check_funcs: bool = True) -> HygieneAuditReport:
    report = HygieneAuditReport()

    for dirpath, dirnames, filenames in os.walk(root_dir):
        # Prune excluded directories in-place
        dirnames[:] = [d for d in dirnames if d not in EXCLUDE_DIRS]

        for filename in filenames:
            ext = os.path.splitext(filename)[1].lower()
            if ext not in {".py", ".ts", ".js", ".cpp", ".h", ".hpp", ".md"}:
                continue

            full_path = os.path.join(dirpath, filename)
            rel_path = os.path.relpath(full_path, root_dir)

            if is_excluded(full_path, filename):
                continue

            report.total_files_scanned += 1
            if ext in {".py", ".ts", ".js", ".cpp", ".h", ".hpp"}:
                report.total_code_files += 1
            elif ext == ".md":
                report.total_doc_files += 1

            # 1. File Length Check
            v = audit_file_length(full_path, rel_path)
            if v:
                report.file_violations.append(v)
                if v.severity == "ERROR":
                    report.is_compliant = False
            else:
                report.passed_clean_files += 1

            # 2. Python Function Check
            if check_funcs and ext == ".py":
                f_violations = audit_python_functions(full_path, rel_path)
                report.function_violations.extend(f_violations)

    return report


def main() -> int:
    parser = argparse.ArgumentParser(description="FreeExile Code & Documentation Hygiene Auditor")
    parser.add_argument("--root", type=str, default=PROJECT_ROOT, help="Root directory to scan")
    parser.add_argument("--strict", action="store_true", help="Exit with code 1 on ERROR violations")
    parser.add_argument("--skip-funcs", action="store_true", help="Skip function length checks")
    parser.add_argument("--json-out", type=str, default=None, help="Save report to JSON file")
    args = parser.parse_args()

    print("================================================================================")
    print("       FREEEXILE CODE & DOCUMENTATION HYGIENE AUDIT GATE (2026.1)")
    print("================================================================================")
    print(f"Quét thư mục gốc: {args.root}")
    print(f"Ngưỡng Code : Soft Cap <= {CODE_SOFT_CAP} dòng | Hard Cap <= {CODE_HARD_CAP} dòng")
    print(f"Ngưỡng Docs : Soft Cap <= {DOC_SOFT_CAP} dòng | Hard Cap <= {DOC_HARD_CAP} dòng")
    print(f"Hàm Python  : Hard Cap <= {FUNCTION_HARD_CAP} dòng")
    print("--------------------------------------------------------------------------------")

    report = run_hygiene_audit(args.root, check_funcs=not args.skip_funcs)

    print(f"Tổng số file đã quét      : {report.total_files_scanned}")
    print(f" - File mã nguồn (code)   : {report.total_code_files}")
    print(f" - File tài liệu (docs)   : {report.total_doc_files}")
    print(f" - File đạt chuẩn sạch sẽ : {report.passed_clean_files}")
    print("--------------------------------------------------------------------------------")

    errors = [v for v in report.file_violations if v.severity == "ERROR"]
    warnings = [v for v in report.file_violations if v.severity == "WARNING"]

    if errors:
        print(f"\n[!] PHÁT HIỆN {len(errors)} FILE VI PHẠM HARD CAP (ERROR):")
        for e in errors:
            print(f"  ❌ [{e.category}] {e.file_path} ({e.line_count} dòng) -> {e.message}")

    if warnings:
        print(f"\n[*] CẢNH BÁO {len(warnings)} FILE VƯỢT SOFT CAP (WARNING):")
        for w in warnings:
            print(f"  ⚠️ [{w.category}] {w.file_path} ({w.line_count} dòng) -> {w.message}")

    if report.function_violations:
        print(f"\n[*] CẢNH BÁO {len(report.function_violations)} HÀM/METHOD DÀI HƠN {FUNCTION_HARD_CAP} DÒNG:")
        for f in report.function_violations[:10]:
            print(f"  ⚠️ {f.file_path}:{f.start_line} - def {f.func_name}() ({f.line_count} dòng)")
        if len(report.function_violations) > 10:
            print(f"  ... và còn {len(report.function_violations) - 10} hàm khác.")

    if args.json_out:
        out_data = {
            "total_files_scanned": report.total_files_scanned,
            "total_code_files": report.total_code_files,
            "total_doc_files": report.total_doc_files,
            "is_compliant": report.is_compliant,
            "errors_count": len(errors),
            "warnings_count": len(warnings),
            "function_violations_count": len(report.function_violations),
            "errors": [v.__dict__ for v in errors],
            "warnings": [v.__dict__ for v in warnings],
        }
        with open(args.json_out, "w", encoding="utf-8") as f:
            json.dump(out_data, f, indent=2, ensure_ascii=False)
        print(f"\nBáo cáo JSON đã lưu tại: {args.json_out}")

    print("\n================================================================================")
    if report.is_compliant:
        print("✅ KẾT QUẢ: TOÀN BỘ MÃ NGUỒN VÀ TÀI LIỆU TUÂN THỦ HARD CAP HYGIENE!")
        print("================================================================================")
        return 0
    else:
        print("❌ KẾT QUẢ: PHÁT HIỆN FILE VI PHẠM QUY CHUẨN ĐỘ DÀI & MODULE HÓA!")
        print("================================================================================")
        if args.strict:
            return 1
        return 0


if __name__ == "__main__":
    sys.exit(main())
