"""abreka exp list — list experiments with filtering and sorting."""

from __future__ import annotations

import operator
import re

import click
from rich.console import Console
from rich.table import Table

from abreka.config import find_abreka_dir, load_config, load_index
from abreka.metrics import parse_metric_ref

_WHERE_RE = re.compile(r"^metric:(\S+)\s*(>=|<=|>|<|==|!=)\s*([0-9eE.+-]+)$")

_OPS = {
    ">": operator.gt,
    ">=": operator.ge,
    "<": operator.lt,
    "<=": operator.le,
    "==": operator.eq,
    "!=": operator.ne,
}


@click.command("list")
@click.option("--status", "-s", default=None, help="Filter by status.")
@click.option("--parent", "-p", default=None, help="Filter by parent experiment.")
@click.option("--sort", "sort_by", default=None, help="Sort by: time | metric:<split>:<key>")
@click.option("--where", "where_clauses", multiple=True, help='Filter: "metric:<split>:<key> <op> <value>"')
def list_cmd(status: str | None, parent: str | None, sort_by: str | None,
             where_clauses: tuple[str, ...]):
    """List experiments with optional filtering and sorting."""
    abreka_dir = find_abreka_dir()
    config = load_config(abreka_dir)
    index = load_index(abreka_dir)

    if not index:
        click.echo("No experiments yet.")
        return

    entries = list(index.values())

    # Filter by status
    if status:
        entries = [e for e in entries if e["status"] == status]

    # Filter by parent
    if parent:
        entries = [e for e in entries if parent in e.get("parents", [])]

    # Filter by --where clauses
    for clause in where_clauses:
        m = _WHERE_RE.match(clause.strip())
        if not m:
            raise click.ClickException(f"Invalid --where clause: {clause!r}")
        split, key = parse_metric_ref(m.group(1))
        op = _OPS[m.group(2)]
        threshold = float(m.group(3))
        entries = [
            e for e in entries
            if op(e.get("metrics", {}).get(split, {}).get(key, float("-inf")), threshold)
        ]

    # Sort
    if sort_by == "time":
        entries.sort(key=lambda e: e.get("created_at", ""), reverse=True)
    elif sort_by and sort_by.startswith("metric:"):
        split, key = parse_metric_ref(sort_by[len("metric:"):])
        maximize = config.experiment.metric_direction == "maximize"
        entries.sort(
            key=lambda e: e.get("metrics", {}).get(split, {}).get(key, float("-inf")),
            reverse=maximize,
        )
    else:
        entries.sort(key=lambda e: e["id"])

    # Display
    console = Console()
    primary_split, primary_key = parse_metric_ref(config.experiment.primary_metric)

    table = Table(show_header=True, header_style="bold")
    table.add_column("ID", style="bold")
    table.add_column("Status")
    table.add_column("Hypothesis", max_width=50, no_wrap=True)
    table.add_column(f"{primary_split}:{primary_key}", justify="right")
    table.add_column("Parents")

    status_styles = {
        "completed": "green",
        "running": "yellow",
        "proposed": "cyan",
        "implementing": "blue",
        "testing": "blue",
        "failed": "red",
    }

    for e in entries:
        metric_val = e.get("metrics", {}).get(primary_split, {}).get(primary_key)
        metric_str = f"{metric_val:.4f}" if metric_val is not None else "-"
        style = status_styles.get(e["status"], "")
        parents_str = ", ".join(e.get("parents", []))
        table.add_row(
            e["id"],
            f"[{style}]{e['status']}[/{style}]",
            e.get("hypothesis", "")[:50],
            metric_str,
            parents_str,
        )

    console.print(table)
