#!/usr/bin/env python3
"""
Sync Codex Skills - Generate symlinks and index for OpenAI Codex compatibility.

This script scans all domain folders for SKILL.md files and creates:
1. Symlinks in .codex/skills/ directory
2. skills-index.json manifest for tooling

Usage:
    python scripts/sync-codex-skills.py [--dry-run] [--verbose]
"""

import argparse
import json
import os
import sys
from pathlib import Path
from typing import Dict, List, Optional


# Skill domain configuration
SKILL_DOMAINS = {
    "marketing-skill": {
        "category": "marketing",
        "description": "Marketing, content, and demand generation skills"
    },
    "engineering-team": {
        "category": "engineering",
        "description": "Software engineering and technical skills"
    },
    "engineering": {
        "category": "engineering-advanced",
        "description": "Advanced engineering skills - agents, RAG, MCP, CI/CD, databases, observability"
    },
    "product-team": {
        "category": "product",
        "description": "Product management and design skills"
    },
    "c-level-advisor": {
        "category": "c-level",
        "description": "Executive leadership and advisory skills"
    },
    "project-management": {
        "category": "project-management",
        "description": "Project management and Atlassian skills"
    },
    "ra-qm-team": {
        "category": "ra-qm",
        "description": "Regulatory affairs and quality management skills"
    },
    "business-growth": {
        "category": "business-growth",
        "description": "Customer success, sales engineering, and revenue operations skills"
    },
    "finance": {
        "category": "finance",
        "description": "Financial analysis, valuation, and forecasting skills"
    },
    "productivity": {
        "category": "productivity",
        "description": "Personal-productivity skills - capture, email, reflect"
    },
    "marketing": {
        "category": "marketing",
        "description": "Top-level marketing slices (landing-page generator)"
    },
    "research": {
        "category": "research",
        "description": "Research orchestrator + 6 specialists (pulse, litreview, grants, dossier, patent, syllabus, notebooklm)"
    },
    "business-operations": {
        "category": "business-operations",
        "description": "Internal BizOps skills (v2.8.0): process mapping, vendor management, capacity planning (Erlang-C), internal comms (ADKAR+Kotter), knowledge ops (SOP+runbook), procurement (UNSPSC)"
    },
    "commercial": {
        "category": "commercial",
        "description": "Per-deal-and-packaging Commercial skills (v2.8.0): pricing strategy, deal desk, partnerships, channel economics, commercial policy, RFP responder, commercial forecaster"
    },
    "research-ops": {
        "category": "research-ops",
        "description": "Enterprise Research Operations skills (v2.9.0): clinical study design, R&D program finance, market research methodology, product/user research"
    },
    "markdown-html": {
        "category": "documentation",
        "description": "Markdown-to-HTML converter (v2.10.0 foundation): orchestrator (context: fork, deterministic doctype classifier, refuses < 100 lines per Shihipar) + design-system (one-time onboarding wizard with WCAG-AA-validated 12-token palette, project > global > defaults precedence). Converter sub-skills (md-document, md-review, md-slides) land in v2.10.1."
    },
    "compliance-os": {
        "category": "compliance",
        "description": "Compliance-OS skills: ISO 13485 / ISO 27001 / SOC 2 / GDPR / FDA QSR / EU AI Act audit-prep + compliance-readiness orchestrator"
    }
}


def find_skills(repo_root: Path) -> List[Dict]:
    """
    Scan repository for all skills (folders containing SKILL.md).

    Returns list of skill dictionaries with metadata.
    """
    skills = []

    for domain_dir, domain_info in SKILL_DOMAINS.items():
        domain_path = repo_root / domain_dir

        if not domain_path.exists():
            continue

        # Three discovery patterns supported:
        #   1. <domain>/skills/<name>/SKILL.md  — flat-domain pattern (most domains)
        #   2. <domain>/<name>/SKILL.md         — legacy pattern
        #   3. <domain>/<plugin>/skills/<name>/SKILL.md — nested plugin pattern
        #      (used by engineering/caveman/, engineering/write-a-skill/, etc.)
        seen_paths: set = set()

        scan_roots = []
        skills_subdir = domain_path / "skills"
        if skills_subdir.is_dir():
            scan_roots.append(("skills/", skills_subdir))
        scan_roots.append(("", domain_path))

        for prefix, scan_root in scan_roots:
            for skill_path in scan_root.iterdir():
                if not skill_path.is_dir():
                    continue
                # Skip subfolders that are themselves nested plugins or special dirs
                if skill_path.name in {"skills", ".claude-plugin", ".codex-plugin"}:
                    continue

                skill_md = skill_path / "SKILL.md"
                if skill_md.exists():
                    if str(skill_md) in seen_paths:
                        continue
                    seen_paths.add(str(skill_md))

                    skill_name = skill_path.name
                    description = extract_skill_description(skill_md)
                    relative_path = f"../../{domain_dir}/{prefix}{skill_name}"

                    skills.append({
                        "name": skill_name,
                        "source": relative_path,
                        "source_absolute": str(skill_path.relative_to(repo_root)),
                        "category": domain_info["category"],
                        "description": description or f"Skill from {domain_dir}"
                    })
                    continue

                # Pattern 3: plugin with nested skills/ subdir (engineering/caveman/skills/caveman/SKILL.md)
                nested_skills = skill_path / "skills"
                if not nested_skills.is_dir():
                    continue
                for inner_path in nested_skills.iterdir():
                    if not inner_path.is_dir():
                        continue
                    inner_skill_md = inner_path / "SKILL.md"
                    if not inner_skill_md.exists():
                        continue
                    if str(inner_skill_md) in seen_paths:
                        continue
                    seen_paths.add(str(inner_skill_md))

                    skill_name = inner_path.name
                    description = extract_skill_description(inner_skill_md)
                    relative_path = f"../../{domain_dir}/{skill_path.name}/skills/{skill_name}"

                    skills.append({
                        "name": skill_name,
                        "source": relative_path,
                        "source_absolute": str(inner_path.relative_to(repo_root)),
                        "category": domain_info["category"],
                        "description": description or f"Skill from {domain_dir}"
                    })

    # Sort by category then name for consistent output
    skills.sort(key=lambda s: (s["category"], s["name"]))

    return skills


def extract_skill_description(skill_md_path: Path) -> Optional[str]:
    """
    Extract description from SKILL.md YAML frontmatter.

    Looks for:
    ---
    name: ...
    description: ...
    ---
    """
    try:
        content = skill_md_path.read_text(encoding="utf-8")

        # Check for YAML frontmatter
        if not content.startswith("---"):
            return None

        # Find end of frontmatter
        end_idx = content.find("---", 3)
        if end_idx == -1:
            return None

        frontmatter = content[3:end_idx]

        # Simple extraction without YAML parser dependency
        for line in frontmatter.split("\n"):
            line = line.strip()
            if line.startswith("description:"):
                desc = line[len("description:"):].strip()
                # Remove quotes if present
                if desc.startswith('"') and desc.endswith('"'):
                    desc = desc[1:-1]
                elif desc.startswith("'") and desc.endswith("'"):
                    desc = desc[1:-1]
                return desc

        return None

    except Exception:
        return None


def create_symlinks(repo_root: Path, skills: List[Dict], dry_run: bool = False, verbose: bool = False) -> Dict:
    """
    Create symlinks in .codex/skills/ directory.

    Returns summary of operations.
    """
    codex_skills_dir = repo_root / ".codex" / "skills"

    created = []
    updated = []
    unchanged = []
    errors = []

    if not dry_run:
        codex_skills_dir.mkdir(parents=True, exist_ok=True)

    for skill in skills:
        symlink_path = codex_skills_dir / skill["name"]
        target = skill["source"]

        try:
            if symlink_path.is_symlink():
                current_target = os.readlink(symlink_path)
                if current_target == target:
                    unchanged.append(skill["name"])
                    if verbose:
                        print(f"  [UNCHANGED] {skill['name']} -> {target}")
                else:
                    if not dry_run:
                        symlink_path.unlink()
                        symlink_path.symlink_to(target)
                    updated.append(skill["name"])
                    if verbose:
                        print(f"  [UPDATED] {skill['name']} -> {target} (was: {current_target})")
            elif symlink_path.exists():
                errors.append(f"{skill['name']}: path exists but is not a symlink")
                if verbose:
                    print(f"  [ERROR] {skill['name']}: path exists but is not a symlink")
            else:
                if not dry_run:
                    symlink_path.symlink_to(target)
                created.append(skill["name"])
                if verbose:
                    print(f"  [CREATED] {skill['name']} -> {target}")

        except Exception as e:
            errors.append(f"{skill['name']}: {str(e)}")
            if verbose:
                print(f"  [ERROR] {skill['name']}: {str(e)}")

    return {
        "created": created,
        "updated": updated,
        "unchanged": unchanged,
        "errors": errors
    }


def generate_skills_index(repo_root: Path, skills: List[Dict], dry_run: bool = False) -> Dict:
    """
    Generate .codex/skills-index.json manifest.

    Returns the index data.
    """
    # Calculate category counts
    categories = {}
    for skill in skills:
        cat = skill["category"]
        if cat not in categories:
            # Find domain info
            for domain_dir, domain_info in SKILL_DOMAINS.items():
                if domain_info["category"] == cat:
                    categories[cat] = {
                        "count": 0,
                        "source": f"../../{domain_dir}",
                        "description": domain_info["description"]
                    }
                    break
        if cat in categories:
            categories[cat]["count"] += 1

    # Build index
    index = {
        "version": "1.0.0",
        "name": "claude-code-skills",
        "description": "Production-ready skill packages for AI agents - Marketing, Engineering, Product, C-Level, PM, and RA/QM",
        "repository": "https://github.com/alirezarezvani/claude-skills",
        "total_skills": len(skills),
        "skills": [
            {
                "name": s["name"],
                "source": s["source"],
                "category": s["category"],
                "description": s["description"]
            }
            for s in skills
        ],
        "categories": categories
    }

    if not dry_run:
        index_path = repo_root / ".codex" / "skills-index.json"
        index_path.parent.mkdir(parents=True, exist_ok=True)
        index_path.write_text(json.dumps(index, indent=2) + "\n", encoding="utf-8")

    return index


def validate_symlinks(repo_root: Path, skills: List[Dict]) -> List[str]:
    """
    Validate that all symlinks resolve to valid SKILL.md files.

    Returns list of broken symlinks.
    """
    broken = []
    codex_skills_dir = repo_root / ".codex" / "skills"

    for skill in skills:
        symlink_path = codex_skills_dir / skill["name"]

        if not symlink_path.exists():
            broken.append(f"{skill['name']}: symlink does not exist")
            continue

        skill_md = symlink_path / "SKILL.md"
        if not skill_md.exists():
            broken.append(f"{skill['name']}: SKILL.md not found through symlink")

    return broken


def main():
    parser = argparse.ArgumentParser(
        description="Sync Codex skills symlinks and generate index"
    )
    parser.add_argument(
        "--dry-run", "-n",
        action="store_true",
        help="Show what would be done without making changes"
    )
    parser.add_argument(
        "--verbose", "-v",
        action="store_true",
        help="Show detailed output"
    )
    parser.add_argument(
        "--validate",
        action="store_true",
        help="Validate symlinks after sync"
    )
    parser.add_argument(
        "--json",
        action="store_true",
        help="Output results as JSON"
    )

    args = parser.parse_args()

    # Find repository root (where this script lives in scripts/)
    script_path = Path(__file__).resolve()
    repo_root = script_path.parent.parent

    if args.verbose and not args.json:
        print(f"Repository root: {repo_root}")
        print(f"Scanning for skills...")

    # Find all skills
    skills = find_skills(repo_root)

    if not skills:
        if args.json:
            print(json.dumps({"error": "No skills found"}, indent=2))
        else:
            print("No skills found in repository")
        sys.exit(1)

    if args.verbose and not args.json:
        print(f"Found {len(skills)} skills across {len(set(s['category'] for s in skills))} categories")
        print()

    # Create symlinks
    if not args.json:
        mode = "[DRY RUN] " if args.dry_run else ""
        print(f"{mode}Creating symlinks in .codex/skills/...")

    symlink_results = create_symlinks(repo_root, skills, args.dry_run, args.verbose)

    # Generate index
    if not args.json:
        print(f"{mode}Generating .codex/skills-index.json...")

    index = generate_skills_index(repo_root, skills, args.dry_run)

    # Validate if requested
    validation_errors = []
    if args.validate and not args.dry_run:
        if not args.json:
            print("Validating symlinks...")
        validation_errors = validate_symlinks(repo_root, skills)

    # Output results
    if args.json:
        output = {
            "dry_run": args.dry_run,
            "total_skills": len(skills),
            "symlinks": symlink_results,
            "index_generated": not args.dry_run,
            "validation_errors": validation_errors if args.validate else None
        }
        print(json.dumps(output, indent=2))
    else:
        print()
        print("=" * 50)
        print("SUMMARY")
        print("=" * 50)
        print(f"Total skills: {len(skills)}")
        print(f"Symlinks created: {len(symlink_results['created'])}")
        print(f"Symlinks updated: {len(symlink_results['updated'])}")
        print(f"Symlinks unchanged: {len(symlink_results['unchanged'])}")

        if symlink_results['errors']:
            print(f"Errors: {len(symlink_results['errors'])}")
            for err in symlink_results['errors']:
                print(f"  - {err}")

        if validation_errors:
            print(f"Validation errors: {len(validation_errors)}")
            for err in validation_errors:
                print(f"  - {err}")

        print()
        print("Categories:")
        for cat, info in index["categories"].items():
            print(f"  {cat}: {info['count']} skills")

        if args.dry_run:
            print()
            print("No changes made (dry run mode)")
        else:
            print()
            print(f"Index written to: .codex/skills-index.json")
            print(f"Symlinks created in: .codex/skills/")

    # Exit with error if there were issues
    if symlink_results['errors'] or validation_errors:
        sys.exit(1)

    sys.exit(0)


if __name__ == "__main__":
    main()
