"""abreka chat — launch an interactive pi session briefed on the whole project."""

from __future__ import annotations

import os
import shutil
from collections import Counter
from importlib.resources import files as pkg_files
from pathlib import Path

import click

from abreka.config import discover_project, find_abreka_dir, load_config, load_goal, load_index
from abreka.metrics import parse_metric_ref
from abreka.persistence import load_backlog_index
from abreka.prompts import render_template
from abreka.wiki import load_wiki_index


_TEMPLATE_RESOURCE = pkg_files("abreka.defaults") / "project_chat_prompt.md"


def _primary_metric_value(entry: dict, split: str, key: str) -> float | None:
    return entry.get("metrics", {}).get(split, {}).get(key)


def _format_status_summary(index: dict) -> str:
    if not index:
        return "No experiments yet. This project has not run any experiments."

    counts = Counter(entry.get("status", "?") for entry in index.values())
    total = len(index)
    summary_bits = []
    for status in ("completed", "running", "testing", "implementing", "proposed", "failed"):
        if counts.get(status):
            summary_bits.append(f"{counts[status]} {status}")
    if not summary_bits:
        return f"{total} total experiment(s)."
    return f"{total} total experiment(s) ({', '.join(summary_bits)})."


def _format_best_experiment(index: dict, primary_metric: str, metric_direction: str) -> str:
    split, key = parse_metric_ref(primary_metric)
    best_id: str | None = None
    best_entry: dict | None = None
    best_val: float | None = None
    maximize = metric_direction == "maximize"

    for eid, entry in index.items():
        value = _primary_metric_value(entry, split, key)
        if value is None:
            continue
        if best_val is None or (maximize and value > best_val) or (not maximize and value < best_val):
            best_id = eid
            best_entry = entry
            best_val = value

    if best_id is None or best_entry is None or best_val is None:
        return f"No experiments have recorded {primary_metric} yet."

    status = best_entry.get("status", "?")
    hypothesis = best_entry.get("hypothesis", "").strip() or "(no hypothesis)"
    return f"- {best_id} [{status}] {primary_metric}={best_val:.6g} — {hypothesis}"


def _format_recent_experiments(index: dict, primary_metric: str, limit: int = 5) -> str:
    if not index:
        return "No experiments yet."

    split, key = parse_metric_ref(primary_metric)
    recent_ids = sorted(
        index,
        key=lambda eid: index[eid].get("created_at", ""),
        reverse=True,
    )[:limit]
    lines: list[str] = []
    for eid in recent_ids:
        entry = index[eid]
        status = entry.get("status", "?")
        hypothesis = entry.get("hypothesis", "").strip() or "(no hypothesis)"
        if len(hypothesis) > 100:
            hypothesis = hypothesis[:97] + "..."
        metric_val = _primary_metric_value(entry, split, key)
        metric_text = f"{primary_metric}={metric_val:.6g}" if metric_val is not None else f"{primary_metric}=-"
        lines.append(f"- {eid} [{status}] {metric_text} — {hypothesis}")
    return "\n".join(lines)


def _format_backlog_items(abreka_dir: Path, limit: int = 8) -> str:
    backlog_index = load_backlog_index(abreka_dir)
    open_items = [entry for entry in backlog_index.values() if entry.get("status") == "open"]
    if not open_items:
        return "No open backlog items."

    open_items.sort(key=lambda entry: (-entry.get("priority", 0), entry.get("id", "")))

    lines: list[str] = []
    for entry in open_items[:limit]:
        bid = entry.get("id", "????")
        title = entry.get("title", "").strip() or "(untitled)"
        priority = entry.get("priority", 0)
        description = entry.get("description", "").strip()
        if len(description) > 100:
            description = description[:97] + "..."
        line = f"- B{bid} [priority={priority}] {title}"
        if description:
            line += f" — {description}"
        source_exp = entry.get("source_exp", "")
        tags = entry.get("tags", [])
        meta: list[str] = []
        if source_exp:
            meta.append(f"source={source_exp}")
        if tags:
            meta.append(f"tags={', '.join(tags)}")
        if meta:
            line += f" ({'; '.join(meta)})"
        lines.append(line)

    remaining = len(open_items) - limit
    if remaining > 0:
        lines.append(f"- ... {remaining} more open backlog item(s)")
    return "\n".join(lines)


def _format_wiki_pages(abreka_dir: Path, limit: int = 12) -> str:
    index = load_wiki_index(abreka_dir)
    entries = sorted(index.values(), key=lambda e: e.get("slug", ""))
    if not entries:
        return "No wiki pages yet."

    lines: list[str] = []
    for entry in entries[:limit]:
        slug = entry.get("slug", "?")
        title = entry.get("title", "").strip() or "(untitled)"
        summary = entry.get("summary", "").strip()
        tags = entry.get("tags", [])
        line = f"- `{slug}` {title}"
        if summary:
            if len(summary) > 100:
                summary = summary[:97] + "..."
            line += f" — {summary}"
        if tags:
            line += f" ({', '.join(tags)})"
        lines.append(line)

    remaining = len(entries) - limit
    if remaining > 0:
        lines.append(f"- ... {remaining} more wiki page(s)")
    return "\n".join(lines)


def _list_relative_files(root: Path, base: Path, *, limit: int = 30) -> str:
    if not base.is_dir():
        return f"({base.relative_to(root)} missing)"

    files = [
        str(path.relative_to(root))
        for path in sorted(base.rglob("*"))
        if path.is_file()
    ]
    if not files:
        return f"({base.relative_to(root)} is empty)"

    lines = [f"- {path}" for path in files[:limit]]
    remaining = len(files) - limit
    if remaining > 0:
        lines.append(f"- ... {remaining} more file(s)")
    return "\n".join(lines)


def _build_context(abreka_dir: Path) -> dict[str, str]:
    project_root = abreka_dir.parent
    project_name, lib_name = discover_project(project_root)
    config = load_config(abreka_dir)
    goal_text = load_goal(abreka_dir).strip() or "(no goal.md written yet)"
    index = load_index(abreka_dir)

    return {
        "project_name": project_name,
        "lib_name": lib_name,
        "goal_text": goal_text,
        "primary_metric": config.experiment.primary_metric,
        "metric_direction": config.experiment.metric_direction,
        "test_command": config.project.test_command,
        "timeout_minutes": str(config.run.timeout_minutes),
        "run_mode": config.run.mode,
        "status_summary": _format_status_summary(index),
        "best_experiment": _format_best_experiment(
            index,
            config.experiment.primary_metric,
            config.experiment.metric_direction,
        ),
        "recent_experiments": _format_recent_experiments(
            index,
            config.experiment.primary_metric,
        ),
        "backlog_items": _format_backlog_items(abreka_dir),
        "wiki_pages": _format_wiki_pages(abreka_dir),
        "shared_library_listing": _list_relative_files(
            project_root,
            project_root / "src" / lib_name,
        ),
        "prompt_listing": _list_relative_files(
            project_root,
            abreka_dir / "prompts",
        ),
    }


def _render_system_prompt(context: dict[str, str]) -> str:
    template = _TEMPLATE_RESOURCE.read_text(encoding="utf-8")
    return render_template(template, context)


def _build_argv(
    *,
    system_prompt: str,
    chats_dir: Path,
    continue_: bool,
    resume: bool,
    messages: tuple[str, ...],
) -> list[str]:
    argv = [
        "pi",
        "--append-system-prompt",
        system_prompt,
        "--session-dir",
        str(chats_dir),
    ]
    if continue_:
        argv.append("--continue")
    if resume:
        argv.append("--resume")
    if messages:
        argv.extend(messages)
    else:
        argv.append(
            "Let's discuss this abreka project. I've given you the project goal, "
            "layout, experiment history, backlog, and wiki in the system prompt. "
            "What should we work through first?"
        )
    return argv


@click.command("chat")
@click.option(
    "--continue",
    "continue_",
    is_flag=True,
    help="Continue the most recent project chat.",
)
@click.option(
    "--resume",
    is_flag=True,
    help="Pick a previous project chat to resume.",
)
@click.argument("messages", nargs=-1)
def chat_cmd(continue_: bool, resume: bool, messages: tuple[str, ...]):
    """Open an interactive pi chat briefed on the whole project."""
    abreka_dir = find_abreka_dir()

    if not shutil.which("pi"):
        raise click.ClickException(
            "pi not found. Install it: https://github.com/badlogic/pi-mono"
        )

    context = _build_context(abreka_dir)
    system_prompt = _render_system_prompt(context)

    chats_dir = abreka_dir / "chats"
    chats_dir.mkdir(parents=True, exist_ok=True)

    argv = _build_argv(
        system_prompt=system_prompt,
        chats_dir=chats_dir,
        continue_=continue_,
        resume=resume,
        messages=messages,
    )

    project_root = abreka_dir.parent
    click.echo(f"Launching pi for project {context['project_name']} in {project_root}...\n")
    os.chdir(project_root)
    os.execvp("pi", argv)
