#!/usr/bin/env python3
"""Seed GitHub issues from all backlog epics via REST API.

Usage: python tools/seed-github-issues.py [--dry-run] [--epic=conductor] [--batch=N]

Parses all 11 backlog epic files from docs/backlog/ and creates GitHub issues
with proper labels, milestones, and structured bodies.

Respects rate limits (60/hr on flagged accounts). Safe to re-run — skips
issues whose titles already exist.

Options:
    --dry-run       Preview without creating issues
    --epic=NAME     Only seed issues from one epic (e.g. --epic=conductor)
    --batch=N       Create at most N issues per run (default: unlimited)
"""

from __future__ import annotations

import json
import re
import subprocess
import sys
import time
from pathlib import Path

REPO = "repos/LookNoHandsMom/LLMHosts.com"
PROJECT_ROOT = Path(__file__).parent.parent
BACKLOG_DIR = PROJECT_ROOT / "docs" / "backlog"

DRY_RUN = "--dry-run" in sys.argv
EPIC_FILTER = None
BATCH_LIMIT = 0  # 0 = unlimited

for arg in sys.argv[1:]:
    if arg.startswith("--epic="):
        EPIC_FILTER = arg.split("=", 1)[1]
    elif arg.startswith("--batch="):
        BATCH_LIMIT = int(arg.split("=", 1)[1])

# Epic file → label mapping
EPIC_MAP: dict[str, str] = {
    "epic-conductor.md": "epic:conductor",
    "epic-compute-fabric.md": "epic:compute",
    "epic-frontend.md": "epic:frontend",
    "epic-security.md": "epic:security",
    "epic-caching.md": "epic:cache",
    "epic-onboarding.md": "epic:onboarding",
    "epic-observability.md": "epic:observability",
    "epic-hive-mode.md": "epic:hive",
    "epic-hive-ceo.md": "epic:hive-ceo",
    "epic-email-ops.md": "epic:email",
    "epic-self-evolution.md": "epic:self-evolution",
}

# Phase → milestone number (assumes milestones exist with these IDs)
# Will be populated dynamically from GitHub API
MILESTONE_MAP: dict[int, int] = {}

# Priority mapping
PRIORITY_MAP: dict[str, str] = {
    "P0": "priority:critical",
    "P1": "priority:high",
    "P2": "priority:medium",
    "P3": "priority:low",
}

# Status mapping
STATUS_MAP: dict[str, str] = {
    "BACKLOG": "",
    "RESEARCH": "status:needs-spec",
    "DESIGN": "status:needs-spec",
    "READY": "status:ready",
    "IN_PROGRESS": "status:in-progress",
    "TESTING": "status:in-progress",
    "DONE": "status:done",
    "DEPRECATED": "",
}


def gh_api(endpoint: str, method: str = "GET", data: dict | None = None) -> dict | list | None:
    """Call GitHub REST API via gh CLI with rate limit retry."""
    cmd = ["gh", "api", endpoint]
    if method != "GET":
        cmd.extend(["-X", method])
    if data:
        cmd.extend(["--input", "-"])
        result = subprocess.run(cmd, input=json.dumps(data), capture_output=True, text=True, timeout=30)
    else:
        result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)

    if result.returncode != 0:
        stderr = result.stderr.strip()
        if "rate limit" in stderr.lower() or "403" in stderr:
            print("  RATE LIMITED — waiting 65s...")
            time.sleep(65)
            return gh_api(endpoint, method, data)
        print(f"  ERROR: {stderr}")
        return None

    if result.stdout.strip():
        return json.loads(result.stdout)
    return None


def check_rate_limit() -> int:
    """Check remaining rate limit. Returns remaining count."""
    rate = gh_api("rate_limit")
    if rate:
        remaining = rate["rate"]["remaining"]
        if remaining < 5:
            reset_time = rate["rate"]["reset"]
            wait = max(0, reset_time - time.time())
            print(f"  Rate limit nearly exhausted ({remaining} left). Waiting {wait:.0f}s...")
            time.sleep(wait + 5)
            return check_rate_limit()
        return remaining
    return 0


def get_existing_titles() -> set[str]:
    """Get titles of issues that already exist in GitHub.

    The list endpoint returns empty on flagged accounts, so we probe
    known issue numbers individually. We know issues #41-#96 exist from
    the first seeder run.
    """
    titles: set[str] = set()

    # First try the list endpoint (works on unflagged accounts)
    issues = gh_api(f"{REPO}/issues?state=all&per_page=100")
    if issues:
        for issue in issues:
            if "pull_request" not in issue:
                titles.add(issue["title"])
        if titles:
            return titles

    # Fallback: probe known issue numbers from first seeder run
    # These are the originals from run 1: #41-#96 (not all consecutive)
    print("  List endpoint empty (flagged account). Probing known issues...")
    known_numbers = [
        41,
        42,
        43,
        45,
        47,
        49,
        51,
        53,
        55,
        57,
        59,
        61,
        63,
        65,
        67,
        69,
        71,
        74,
        76,
        78,
        80,
        82,
        84,
        86,
        88,
        90,
        92,
        94,
        96,
    ]
    for num in known_numbers:
        issue = gh_api(f"{REPO}/issues/{num}")
        if issue and "title" in issue:
            titles.add(issue["title"])
        time.sleep(0.5)

    return titles


def get_milestone_map() -> dict[int, int]:
    """Build phase number → milestone ID mapping."""
    milestones = gh_api(f"{REPO}/milestones?per_page=10")
    if not milestones:
        return {}
    mapping: dict[int, int] = {}
    for ms in milestones:
        title = ms["title"]
        ms_number = ms["number"]
        if "Phase 1" in title:
            mapping[1] = ms_number
        elif "Phase 2" in title:
            mapping[2] = ms_number
        elif "Phase 3" in title:
            mapping[3] = ms_number
        elif "Phase 4" in title:
            mapping[4] = ms_number
    return mapping


def parse_epic_file(filepath: Path) -> list[dict]:
    """Parse a backlog epic markdown file into issue definitions.

    Handles two formats:
    1. Standard: ### ID: Title + bullet fields
    2. Table: | ID | Title | Status | Priority | rows
    """
    content = filepath.read_text()
    items: list[dict] = []

    # Try standard format first: ### ID: Title
    sections = re.split(r"^### ", content, flags=re.MULTILINE)

    for section in sections[1:]:  # Skip preamble
        lines = section.strip().split("\n")
        if not lines:
            continue

        # Parse header: "CB-001: Title Here"
        header = lines[0].strip()
        match = re.match(r"([A-Z]{2,6}-\d{3}):\s*(.+)", header)
        if not match:
            # Try table format: | ID | Title | Status | Priority |
            for line in lines:
                table_match = re.match(
                    r"\|\s*([A-Z]{2,6}-\d{3})\s*\|\s*(.+?)\s*\|\s*(\w+)\s*\|\s*(P\d)\s*\|",
                    line.strip(),
                )
                if table_match:
                    backlog_id = table_match.group(1)
                    title = table_match.group(2).strip()
                    status = table_match.group(3).strip()
                    priority = table_match.group(4).strip()
                    # Determine phase from section header
                    phase = 2  # default for table items
                    if "phase 1" in section.lower() or "scaffolding" in section.lower():
                        phase = 1
                    elif "phase 3" in section.lower():
                        phase = 3
                    elif "phase 4" in section.lower():
                        phase = 4
                    items.append(
                        {
                            "backlog_id": backlog_id,
                            "title": title,
                            "priority": priority,
                            "phase": phase,
                            "status": status,
                            "description": title,
                            "acceptance": "",
                            "dependencies": "None",
                            "size": "",
                        }
                    )
            continue

        backlog_id = match.group(1)
        title = match.group(2).strip()

        # Parse fields
        fields: dict[str, str] = {}
        description_lines: list[str] = []
        in_description = False

        for line in lines[1:]:
            line = line.strip()
            if not line or line.startswith("---"):
                continue

            field_match = re.match(r"^-\s*\*\*(\w+)\*\*:\s*(.+)", line)
            if field_match:
                key = field_match.group(1).lower()
                value = field_match.group(2).strip()
                fields[key] = value
                if key == "description":
                    in_description = True
                    description_lines = [value]
                else:
                    in_description = False
            elif in_description and line.startswith("-"):
                # New field after description
                in_description = False
            elif in_description:
                description_lines.append(line)

        # Extract values
        priority = fields.get("priority", "P1")
        phase_str = fields.get("phase", "1")
        # Handle "1 (prototype), 2 (production)" → take first number
        phase_match = re.match(r"(\d+)", phase_str)
        phase = int(phase_match.group(1)) if phase_match else 1
        status = fields.get("status", "BACKLOG")
        description = fields.get("description", title)
        acceptance = fields.get("acceptance", "")
        dependencies = fields.get("dependencies", "None")
        size = fields.get("size", "")

        items.append(
            {
                "backlog_id": backlog_id,
                "title": title,
                "priority": priority,
                "phase": phase,
                "status": status,
                "description": description,
                "acceptance": acceptance,
                "dependencies": dependencies,
                "size": size,
            }
        )

    return items


def build_issue_title(item: dict, status: str) -> str:
    """Build a GitHub issue title from a backlog item."""
    phase = item["phase"]
    prefix = f"[P{phase}"
    if status == "DONE":
        prefix += "-DONE"
    prefix += "]"
    return f"{prefix} {item['title']}"


def build_issue_body(item: dict, epic_label: str) -> str:
    """Build a structured GitHub issue body."""
    epic_name = epic_label.replace("epic:", "")
    size_str = item["size"] if item["size"] else "TBD"

    body = f"**Epic**: {epic_name} | **Backlog ID**: {item['backlog_id']} | **Phase**: {item['phase']} | **Size**: {size_str}\n\n"
    body += f"## Description\n{item['description']}\n\n"

    if item["acceptance"]:
        body += f"## Acceptance Criteria\n{item['acceptance']}\n\n"

    if item["dependencies"] and item["dependencies"] != "None":
        body += f"## Dependencies\n{item['dependencies']}\n\n"

    return body.strip()


def build_labels(item: dict, epic_label: str) -> list[str]:
    """Build label list for an issue."""
    labels = ["type:feature", epic_label, f"phase:{item['phase']}"]

    # Priority
    priority_label = PRIORITY_MAP.get(item["priority"])
    if priority_label:
        labels.append(priority_label)

    # Status
    status_label = STATUS_MAP.get(item["status"], "")
    if status_label:
        labels.append(status_label)

    # Size
    if item["size"]:
        size_lower = item["size"].lower()
        for sz in ["xs", "s", "m", "l", "xl"]:
            if sz in size_lower:
                labels.append(f"size:{sz}")
                break

    return labels


def create_issue(title: str, body: str, labels: list[str], milestone: int | None = None) -> bool:
    """Create a single GitHub issue. Returns True on success."""
    data: dict = {"title": title, "body": body, "labels": labels}
    if milestone:
        data["milestone"] = milestone

    if DRY_RUN:
        print(f"  [DRY RUN] Would create: {title}")
        print(f"            Labels: {', '.join(labels)}")
        return True

    result = gh_api(f"{REPO}/issues", method="POST", data=data)
    if result and "number" in result:
        print(f"  #{result['number']}: {title}")
        return True
    else:
        print(f"  FAILED: {title}")
        return False


def main() -> None:
    # Discover epic files
    epic_files = sorted(BACKLOG_DIR.glob("epic-*.md"))
    if EPIC_FILTER:
        epic_files = [f for f in epic_files if EPIC_FILTER in f.stem]
        if not epic_files:
            print(f"No epic file matching '{EPIC_FILTER}' found.")
            sys.exit(1)

    print(f"GitHub Issue Seeder ({'DRY RUN' if DRY_RUN else 'LIVE'})")
    print(f"Epic files: {len(epic_files)}")
    if BATCH_LIMIT:
        print(f"Batch limit: {BATCH_LIMIT}")
    print()

    # Check rate limit
    if not DRY_RUN:
        remaining = check_rate_limit()
        print(f"Rate limit: {remaining} remaining")
        print()

    # Get milestone mapping
    if not DRY_RUN:
        global MILESTONE_MAP
        MILESTONE_MAP = get_milestone_map()
        print(f"Milestones: {MILESTONE_MAP}")
    print()

    # Get existing issue titles for dedup
    if not DRY_RUN:
        existing = get_existing_titles()
        print(f"Existing issues: {len(existing)}")
    else:
        existing = set()
    print()

    # Parse all epics
    all_items: list[tuple[dict, str]] = []
    for epic_file in epic_files:
        epic_label = EPIC_MAP.get(epic_file.name)
        if not epic_label:
            print(f"  SKIP (no label mapping): {epic_file.name}")
            continue

        items = parse_epic_file(epic_file)
        print(f"  {epic_file.name}: {len(items)} items → {epic_label}")
        for item in items:
            all_items.append((item, epic_label))

    print(f"\nTotal backlog items: {len(all_items)}")
    print()

    # Create issues
    created = 0
    skipped = 0
    failed = 0

    for item, epic_label in all_items:
        if BATCH_LIMIT and created >= BATCH_LIMIT:
            print(f"\nBatch limit ({BATCH_LIMIT}) reached. Run again to continue.")
            break

        title = build_issue_title(item, item["status"])
        if title in existing:
            skipped += 1
            continue

        body = build_issue_body(item, epic_label)
        labels = build_labels(item, epic_label)
        milestone = MILESTONE_MAP.get(item["phase"])

        if create_issue(title, body, labels, milestone):
            created += 1
            existing.add(title)  # Prevent creating same title twice in one run
        else:
            failed += 1

        if not DRY_RUN:
            time.sleep(3)  # Be gentle with rate limit

    print("\nDone!")
    print(f"  Created: {created}")
    print(f"  Skipped (exist): {skipped}")
    print(f"  Failed: {failed}")
    print(f"  Total items: {len(all_items)}")


if __name__ == "__main__":
    main()
