
"""Boris planner - task breakdown via Claude CLI."""
import json
import logging
import os
import re
import subprocess
import sys
from datetime import datetime

import config
from state import Milestone, Plan

IS_WINDOWS = sys.platform == "win32"

logger = logging.getLogger(__name__)


def create_plan(task: str, project_dir: str) -> Plan:
    """Break a task into milestones using Claude CLI."""
    file_listing = _list_project_files(project_dir)

    prompt = (
        "You are a technical project planner. Break this task into ordered milestones.\n"
        "Each milestone must be a concrete, buildable unit that DaveLoop can execute independently.\n"
        "Order by dependency: foundation first, then features that depend on it.\n\n"
        f"Task: {task}\n"
        f"Project directory: {project_dir}\n"
        f"Existing files:\n{file_listing}\n\n"
        "Return ONLY a JSON array (no other text) of milestones:\n"
        "[\n"
        "  {\n"
        '    "id": "M1",\n'
        '    "title": "Short title",\n'
        '    "description": "Detailed description of what to build",\n'
        '    "depends_on": [],\n'
        '    "acceptance_criteria": ["criterion 1", "criterion 2"],\n'
        '    "files_to_create": ["file1.py"],\n'
        '    "files_to_modify": []\n'
        "  }\n"
        "]"
    )

    try:
        result = subprocess.run(
            [config.CLAUDE_CMD, "-p", "--output-format", "text"],
            input=prompt,
            capture_output=True,
            text=True,
            timeout=300,
            shell=IS_WINDOWS,
            encoding="utf-8",
            errors="replace",
        )

        if result.returncode != 0:
            raise RuntimeError(f"Claude CLI failed (exit {result.returncode}): {result.stderr}")

        response = result.stdout.strip()

        # Handle markdown code blocks - strip ```json and ``` if present
        response = re.sub(r"^```(?:json)?\s*\n?", "", response)
        response = re.sub(r"\n?```\s*$", "", response)
        response = response.strip()

        # Try to extract JSON array from response if it contains extra text
        if not response.startswith("["):
            match = re.search(r"\[.*\]", response, re.DOTALL)
            if match:
                response = match.group(0)
            else:
                logger.error("Claude response (no JSON found): %s", response[:500])

        milestones_data = json.loads(response)

        milestones = [
            Milestone(
                id=m["id"],
                title=m["title"],
                description=m["description"],
                depends_on=m.get("depends_on", []),
                acceptance_criteria=m.get("acceptance_criteria", []),
                files_to_create=m.get("files_to_create", []),
                files_to_modify=m.get("files_to_modify", []),
            )
            for m in milestones_data
        ]

        plan = Plan(task=task, milestones=milestones)

        # Save plan as markdown
        plan_path = _save_plan_markdown(plan, task)
        logger.info("Plan saved to %s", plan_path)

        return plan

    except json.JSONDecodeError as e:
        raise RuntimeError(f"Failed to parse Claude's plan response as JSON: {e}") from e
    except subprocess.TimeoutExpired:
        raise RuntimeError("Claude CLI timed out while generating plan")
    except FileNotFoundError:
        raise RuntimeError(
            f"'{config.CLAUDE_CMD}' command not found. Is Claude CLI installed?"
        )


def _save_plan_markdown(plan: Plan, task: str) -> str:
    """Save a human-readable markdown version of the plan. Returns filepath."""
    os.makedirs(config.PLANS_DIR, exist_ok=True)
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    filename = f"plan_{timestamp}.md"
    filepath = os.path.join(config.PLANS_DIR, filename)

    lines = [
        f"# Boris Plan",
        f"",
        f"**Task:** {task}",
        f"**Created:** {plan.created_at}",
        f"**Milestones:** {len(plan.milestones)}",
        f"",
        f"---",
        f"",
    ]

    for m in plan.milestones:
        lines.append(f"## {m.id}: {m.title}")
        lines.append(f"")
        lines.append(f"{m.description}")
        lines.append(f"")
        if m.depends_on:
            lines.append(f"**Depends on:** {', '.join(m.depends_on)}")
            lines.append(f"")
        lines.append(f"**Acceptance Criteria:**")
        for c in m.acceptance_criteria:
            lines.append(f"- {c}")
        lines.append(f"")
        if m.files_to_create:
            lines.append(f"**Files to create:** {', '.join(m.files_to_create)}")
        if m.files_to_modify:
            lines.append(f"**Files to modify:** {', '.join(m.files_to_modify)}")
        lines.append(f"")
        lines.append(f"---")
        lines.append(f"")

    with open(filepath, "w", encoding="utf-8") as f:
        f.write("\n".join(lines))

    return filepath


def _list_project_files(project_dir: str) -> str:
    """Walk project_dir and return formatted file listing."""
    skip_dirs = {".git", "__pycache__", "node_modules", ".boris", ".venv", "venv"}
    file_list = []

    for root, dirs, files in os.walk(project_dir):
        # Filter out hidden and skip dirs
        dirs[:] = [d for d in dirs if d not in skip_dirs and not d.startswith(".")]
        rel_root = os.path.relpath(root, project_dir)
        for fname in sorted(files):
            if rel_root == ".":
                file_list.append(fname)
            else:
                file_list.append(os.path.join(rel_root, fname))

    if not file_list:
        return "(empty project)"

    return "\n".join(f"  {f}" for f in file_list)
