#!/usr/bin/env python3
"""output/ 폴더 구조 검증 스크립트.

사용법:
    python3 scripts/validate-output-structure.py [--output-dir /path/to/output]

기본값: /home/jay/workspace/output/
"""

import argparse
import json
import os
import re
import sys
from pathlib import Path

_WORKSPACE_ROOT = os.environ.get("WORKSPACE_ROOT", str(Path(__file__).resolve().parent.parent))
OUTPUT_DIR_DEFAULT = str(Path(_WORKSPACE_ROOT) / "output") + "/"

# 결과 레벨
PASS = "PASS"
WARN = "WARN"
FAIL = "FAIL"
INFO = "INFO"

# 버전 폴더 네이밍 예외 목록 (angle-* 하위에 있어도 버전 폴더로 취급하지 않음)
VERSION_FOLDER_EXCEPTIONS = {
    "briefs",
    "concept-catalog",
    "concept-samples",
    "backgrounds",
    "delivery",
}

# 루트 파일 검사 시 검사할 주요 하위 폴더
MAJOR_AD_DIRS = ["meta-ads", "google-ads", "naver-ads"]


def check_root_files(output_dir: Path) -> list[dict]:
    """루트 및 주요 하위 폴더에 직접 파일이 있는지 검사."""
    results = []

    # 1) output/ 직접 하위 파일 검사
    root_files = [p for p in output_dir.iterdir() if p.is_file()]
    if root_files:
        for f in root_files:
            results.append(
                {
                    "check": "root_files",
                    "level": WARN,
                    "message": f"output/ 루트에 파일 존재: {f.relative_to(output_dir)}",
                }
            )
    else:
        results.append(
            {
                "check": "root_files",
                "level": PASS,
                "message": "output/ 루트에 직접 파일 없음",
            }
        )

    # 2) output/<ad-channel>/ 직접 하위 파일 검사
    for ad_dir_name in MAJOR_AD_DIRS:
        ad_dir = output_dir / ad_dir_name
        if not ad_dir.exists():
            continue
        ad_files = [p for p in ad_dir.iterdir() if p.is_file()]
        if ad_files:
            for f in ad_files:
                results.append(
                    {
                        "check": "root_files",
                        "level": WARN,
                        "message": f"주요 하위 폴더 루트에 파일 존재: {f.relative_to(output_dir)}",
                    }
                )
        else:
            results.append(
                {
                    "check": "root_files",
                    "level": PASS,
                    "message": f"{ad_dir_name}/ 루트에 직접 파일 없음",
                }
            )

    # 3) output/<ad-channel>/angle-*/ 직접 하위 파일 검사 (briefs/ 제외한 non-version 파일)
    for ad_dir_name in MAJOR_AD_DIRS:
        ad_dir = output_dir / ad_dir_name
        if not ad_dir.exists():
            continue
        for angle_dir in ad_dir.iterdir():
            if not angle_dir.is_dir() or not angle_dir.name.startswith("angle-"):
                continue
            angle_files = [p for p in angle_dir.iterdir() if p.is_file()]
            if angle_files:
                for f in angle_files:
                    results.append(
                        {
                            "check": "root_files",
                            "level": WARN,
                            "message": f"angle 폴더 루트에 파일 존재: {f.relative_to(output_dir)}",
                        }
                    )
            else:
                results.append(
                    {
                        "check": "root_files",
                        "level": PASS,
                        "message": f"{ad_dir_name}/{angle_dir.name}/ 루트에 직접 파일 없음",
                    }
                )

    return results


def check_version_naming(output_dir: Path) -> list[dict]:
    """버전 폴더 네이밍 규칙(v01, v02...) 검증."""
    results = []
    version_pattern = re.compile(r"^v\d{2,}$")

    def check_angle_children(angle_dir: Path, context_label: str):
        """angle-* 폴더 하위 폴더들을 검사한다."""
        for child in angle_dir.iterdir():
            if not child.is_dir():
                continue
            name = child.name
            if name in VERSION_FOLDER_EXCEPTIONS:
                continue
            if not version_pattern.match(name):
                results.append(
                    {
                        "check": "version_naming",
                        "level": WARN,
                        "message": (
                            f"버전 폴더 네이밍 위반 ({context_label}): "
                            f"'{name}' — v01, v02... 패턴이 아님 "
                            f"(경로: {child.relative_to(output_dir)})"
                        ),
                    }
                )

    # output/<ad-channel>/angle-*/ 하위 검사
    for ad_dir_name in MAJOR_AD_DIRS:
        ad_dir = output_dir / ad_dir_name
        if not ad_dir.exists():
            continue
        for angle_dir in ad_dir.iterdir():
            if not angle_dir.is_dir() or not angle_dir.name.startswith("angle-"):
                continue
            check_angle_children(angle_dir, f"{ad_dir_name}/{angle_dir.name}")

    # output/campaign-top/ 하위 폴더도 동일하게 버전 폴더 패턴 검사
    # (campaign-top 자체 내 backgrounds/, delivery/ 등은 예외)
    campaign_top = output_dir / "campaign-top"
    if campaign_top.exists():
        for child in campaign_top.iterdir():
            if not child.is_dir():
                continue
            name = child.name
            if name in VERSION_FOLDER_EXCEPTIONS:
                continue
            if not version_pattern.match(name):
                results.append(
                    {
                        "check": "version_naming",
                        "level": WARN,
                        "message": (
                            f"버전 폴더 네이밍 위반 (campaign-top): "
                            f"'{name}' — v01, v02... 패턴이 아님 "
                            f"(경로: {child.relative_to(output_dir)})"
                        ),
                    }
                )

    if not any(r["check"] == "version_naming" and r["level"] == WARN for r in results):
        results.append(
            {
                "check": "version_naming",
                "level": PASS,
                "message": "버전 폴더 네이밍 위반 없음",
            }
        )

    return results


def check_concept_catalog_duplicates(output_dir: Path) -> list[dict]:
    """concept-catalog/concept-samples 중복 위치 검사."""
    results = []
    catalog_paths = []
    samples_paths = []

    for root, dirs, _ in os.walk(output_dir):
        root_path = Path(root)
        for d in dirs:
            if d == "concept-catalog":
                catalog_paths.append(root_path / d)
            elif d == "concept-samples":
                samples_paths.append(root_path / d)

    # concept-catalog: 1개 초과면 WARN
    if len(catalog_paths) == 0:
        results.append(
            {
                "check": "concept_catalog_duplicates",
                "level": INFO,
                "message": "concept-catalog 폴더가 존재하지 않음",
            }
        )
    elif len(catalog_paths) == 1:
        results.append(
            {
                "check": "concept_catalog_duplicates",
                "level": PASS,
                "message": f"concept-catalog 폴더 1개 존재: {catalog_paths[0].relative_to(output_dir)}",
            }
        )
    else:
        paths_str = ", ".join(str(p.relative_to(output_dir)) for p in catalog_paths)
        results.append(
            {
                "check": "concept_catalog_duplicates",
                "level": WARN,
                "message": f"concept-catalog 폴더가 {len(catalog_paths)}곳에 존재 (1개만 허용): {paths_str}",
            }
        )

    # concept-samples: 존재 자체가 WARN
    if len(samples_paths) == 0:
        results.append(
            {
                "check": "concept_catalog_duplicates",
                "level": PASS,
                "message": "concept-samples 폴더 없음 (정상)",
            }
        )
    else:
        paths_str = ", ".join(str(p.relative_to(output_dir)) for p in samples_paths)
        results.append(
            {
                "check": "concept_catalog_duplicates",
                "level": WARN,
                "message": (f"concept-samples 폴더가 존재함 — concept-catalog로 통합 필요 " f"(위치: {paths_str})"),
            }
        )

    return results


def check_version_continuity(output_dir: Path) -> list[dict]:
    """버전 폴더 번호 연속성 검사."""
    results = []
    version_pattern = re.compile(r"^v(\d{2,})$")

    def check_angle_version_seq(angle_dir: Path, label: str):
        """angle-* 폴더 내 v숫자 폴더 번호 연속성 검사."""
        nums = []
        for child in angle_dir.iterdir():
            if not child.is_dir():
                continue
            m = version_pattern.match(child.name)
            if m:
                nums.append(int(m.group(1)))
        if not nums:
            return
        nums.sort()
        # v01부터 시작한다고 가정. 실제 최솟값부터 최댓값까지 연속인지 검사
        expected = list(range(nums[0], nums[-1] + 1))
        missing = sorted(set(expected) - set(nums))
        if missing:
            missing_strs = [f"v{n:02d}" for n in missing]
            results.append(
                {
                    "check": "version_continuity",
                    "level": WARN,
                    "message": (f"버전 번호 불연속 ({label}): " f"누락된 버전 {', '.join(missing_strs)}"),
                }
            )
        else:
            results.append(
                {
                    "check": "version_continuity",
                    "level": PASS,
                    "message": f"버전 번호 연속 ({label}): v{nums[0]:02d}~v{nums[-1]:02d}",
                }
            )

    for ad_dir_name in MAJOR_AD_DIRS:
        ad_dir = output_dir / ad_dir_name
        if not ad_dir.exists():
            continue
        for angle_dir in ad_dir.iterdir():
            if not angle_dir.is_dir() or not angle_dir.name.startswith("angle-"):
                continue
            check_angle_version_seq(
                angle_dir,
                f"{ad_dir_name}/{angle_dir.name}",
            )

    if not results:
        results.append(
            {
                "check": "version_continuity",
                "level": INFO,
                "message": "버전 폴더가 존재하는 angle-* 폴더 없음",
            }
        )

    return results


def check_empty_folders(output_dir: Path) -> list[dict]:
    """빈 폴더 검사 (concept-catalog 직접 하위 20개는 제외)."""
    results = []

    # concept-catalog 직접 하위 폴더 목록 수집 (예외 처리용)
    excluded_dirs: set[Path] = set()
    for root, dirs, _ in os.walk(output_dir):
        root_path = Path(root)
        if root_path.name == "concept-catalog":
            # 직접 하위 최대 20개를 예외로 등록
            direct_children = sorted(
                [root_path / d for d in dirs],
                key=lambda p: p.name,
            )[:20]
            excluded_dirs.update(direct_children)

    empty_found = False
    for root, dirs, files in os.walk(output_dir):
        root_path = Path(root)
        if root_path in excluded_dirs:
            continue
        # 파일이 하나도 없고, 재귀적으로도 비어있는지 확인
        if not files:
            # 하위에 파일이 있는지 확인 (하위 폴더에 파일이 있으면 빈 폴더 아님)
            has_any_file = any(bool(sub_files) for _, _, sub_files in os.walk(root_path))
            if not has_any_file:
                rel = root_path.relative_to(output_dir)
                results.append(
                    {
                        "check": "empty_folders",
                        "level": INFO,
                        "message": f"빈 폴더 발견: {rel}",
                    }
                )
                empty_found = True

    if not empty_found:
        results.append(
            {
                "check": "empty_folders",
                "level": PASS,
                "message": "빈 폴더 없음",
            }
        )

    return results


def main():
    parser = argparse.ArgumentParser(description="output/ 폴더 구조 검증")
    parser.add_argument(
        "--output-dir",
        default=OUTPUT_DIR_DEFAULT,
        help="검증 대상 디렉토리",
    )
    args = parser.parse_args()

    output_dir = Path(args.output_dir)
    if not output_dir.exists():
        print(json.dumps({"status": "error", "message": f"디렉토리 없음: {output_dir}"}))
        sys.exit(1)

    results = []
    results.extend(check_root_files(output_dir))
    results.extend(check_version_naming(output_dir))
    results.extend(check_concept_catalog_duplicates(output_dir))
    results.extend(check_version_continuity(output_dir))
    results.extend(check_empty_folders(output_dir))

    # 결과 출력
    has_fail = any(r["level"] == FAIL for r in results)
    has_warn = any(r["level"] == WARN for r in results)

    overall = FAIL if has_fail else (WARN if has_warn else PASS)

    output = {
        "status": overall,
        "total_checks": len(results),
        "pass": sum(1 for r in results if r["level"] == PASS),
        "warn": sum(1 for r in results if r["level"] == WARN),
        "fail": sum(1 for r in results if r["level"] == FAIL),
        "info": sum(1 for r in results if r["level"] == INFO),
        "details": results,
    }

    print(json.dumps(output, ensure_ascii=False, indent=2))
    sys.exit(1 if has_fail else 0)


if __name__ == "__main__":
    main()
