"""CLI entry point for agentgog."""

import json
import sys
import tempfile
from pathlib import Path
from typing import Optional

import click
from rich.console import Console
from rich.table import Table
from importlib.metadata import version as get_package_version

from agentgog.zz_models import Result
from agentgog.zz_models_list import ModelEntry, parse_models_list
from agentgog.zz_output_formatter import (
    create_result,
    sanitize_filename,
    save_result,
)
from agentgog.zz_runner import read_file_content, run_agata_chat, run_agata_code, run_agata_compare
from agentgog.zz_yaml_loader import TaskLoadError, load_task
from agentgog.comparison_spectrum import (
    is_passing_result,
    normalize_result,
    TECHNICAL_FAILURE,
)

console = Console()


def process_single_model(
    task_file: str,
    model_entry: ModelEntry,
    results_dir: str,
    temp_dir: str,
    mode: str = "chat",
    timeout: Optional[int] = None
) -> Result:
    """Process a single model against a task.

    Workflow:
    1. Run agata chat to get model output
    2. Run agata compare to validate output
    3. Create and return Result object

    Args:
        task_file: Path to YAML task file
        model_entry: ModelEntry with provider and model
        results_dir: Directory to save results
        temp_dir: Directory for temporary files
        mode: Mode of operation ('chat' or 'code')
        timeout: Timeout for chat command

    Returns:
        Result object
    """
    provider = model_entry.provider
    model = model_entry.model

    # Load task to get expected answer
    try:
        task = load_task(task_file)
    except TaskLoadError as e:
        console.print(f"[red]Error loading task:[/red] {e}")
        return Result(
            task_id="unknown",
            model=model,
            provider=provider,
            timestamp="",
            time_sec=0.0,
            success=False,
            mode=mode,
            error=str(e)
        )

    # Create temp files for outputs (include mode in filename)
    safe_model_name = sanitize_filename(model)
    model_output_file = Path(temp_dir) / f"{task.id}_{mode}_{safe_model_name}_output.txt"
    compare_output_file = Path(temp_dir) / f"{task.id}_{mode}_{safe_model_name}_compare.txt"

    console.print(f"\n[blue]Processing {provider}/{model}...[/blue]")

    # Check if output file already exists - skip if it does
    if model_output_file.exists():
        console.print(f"  [red]Output file already exists, skipping. Output: {model_output_file.name}[/red]")
        # Try to read existing files and create result
        model_output = read_file_content(str(model_output_file))
        compare_output = read_file_content(str(compare_output_file))

        if model_output is None:
            return create_result(
                task=task,
                model=model,
                provider=provider,
                success=False,
                execution_time=0.0,
                mode=mode,
                error="Cannot read existing output file"
            )

        is_correct = False
        verification_result = None
        if compare_output:
            verification_result = normalize_result(compare_output)
            is_correct = is_passing_result(verification_result)

        return create_result(
            task=task,
            model=model,
            provider=provider,
            success=True,
            execution_time=0.0,
            mode=mode,
            output=model_output,
            correct=is_correct,
            verification_result=verification_result
        )

    # Step 1: Run agata chat or codeagent based on mode
    if mode == "code":
        console.print(f"  [dim]Running agata codeagent...[/dim]")
        chat_success, chat_error, chat_time = run_agata_code(
            task_file=task_file,
            provider=provider,
            model=model,
            output_file=str(model_output_file),
            timeout=timeout
        )
    else:
        console.print(f"  [dim]Running agata chat...[/dim]")
        chat_success, chat_error, chat_time = run_agata_chat(
            task_file=task_file,
            provider=provider,
            model=model,
            output_file=str(model_output_file),
            timeout=timeout
        )

    if not chat_success:
        console.print(f"  [red]Chat failed:[/red] {chat_error}")
        return create_result(
            task=task,
            model=model,
            provider=provider,
            success=False,
            execution_time=chat_time,
            mode=mode,
            error=chat_error
        )

    # Read model output
    model_output = read_file_content(str(model_output_file))
    if model_output is None:
        error_msg = "Model output file not found"
        console.print(f"  [red]{error_msg}[/red]")
        return create_result(
            task=task,
            model=model,
            provider=provider,
            success=False,
            execution_time=chat_time,
            mode=mode,
            error=error_msg
        )

    console.print(f"  [green]Chat completed in {chat_time:.2f}s[/green]")

    # Display model output (trimmed to 80 chars)
    if model_output:
        display_output = model_output.strip()
        if len(display_output) > 80:
            display_output = display_output[:77] + "..."
        console.print(f"  [dim]Output: '{display_output}'[/dim]")

    # Step 2: Run agata compare for validation
    console.print(f"  [dim]Running agata compare...[/dim]")
    compare_success, compare_error, compare_time = run_agata_compare(
        result_file=str(model_output_file),
        task_file=task_file,
        output_file=str(compare_output_file),
        timeout=60
    )

    total_time = chat_time + compare_time

    if not compare_success:
        console.print(f"  [yellow]Compare failed:[/yellow] {compare_error}")
        # Chat succeeded but compare command failed (technical failure)
        return create_result(
            task=task,
            model=model,
            provider=provider,
            success=True,  # Chat succeeded
            execution_time=total_time,
            mode=mode,
            output=model_output,
            correct=False,
            verification_result=TECHNICAL_FAILURE,
            error=f"Validation failed: {compare_error}"
        )

    # Read compare output to determine if correct
    compare_output = read_file_content(str(compare_output_file))
    is_correct = False
    verification_result = None

    if compare_output:
        # Normalize and validate comparison result using spectrum
        verification_result = normalize_result(compare_output)
        is_correct = is_passing_result(verification_result)

    if is_correct:
        console.print(f"  [green]Validation passed ({verification_result})[/green]")
    else:
        console.print(f"  [yellow]Validation failed ({verification_result})[/yellow]")

    return create_result(
        task=task,
        model=model,
        provider=provider,
        success=True,
        execution_time=total_time,
        mode=mode,
        output=model_output,
        correct=is_correct,
        verification_result=verification_result
    )


def print_version(ctx, param, value):
    if not value or ctx.resilient_parsing:
        return
    click.echo(get_package_version("agentgog"))
    ctx.exit()


@click.group()
@click.option('--version', '-v', is_flag=True, callback=print_version, expose_value=False, is_eager=True, help='Show version and exit')
def main():
    """AgentGOG - Model testing and evaluation framework."""
    pass


@main.command()
@click.option('-f', '--file', 'task_file', required=True,
              help='Path to the YAML task file or text file')
@click.option('-m', '--models', 'models_file', default='./models.list',
              help='Path to models.list file (default: ./models.list)')
@click.option('-b', '--banned', 'banned_file', default='./models_banned.list',
              help='Path to models_banned.list file (default: ./models_banned.list)')
@click.option('--results-dir', default='./results',
              help='Directory to save JSON results (default: ./results)')
@click.option('--temp-dir', default='./temp',
              help='Directory for temporary files (default: ./temp)')
@click.option('--timeout', type=int, default=300,
              help='Timeout in seconds for each model (default: 300)')
def chat(task_file: str, models_file: str, banned_file: str, results_dir: str, temp_dir: str, timeout: int):
    """Run chat task against all models in models.list."""

    # Parse models.list with banned list filtering
    try:
        models = parse_models_list(models_file, banned_file)
        console.print(f"[green]Loaded {len(models)} models from {models_file}[/green]")
        if Path(banned_file).exists():
            from agentgog.zz_models_list import parse_banned_models_list
            banned_models = parse_banned_models_list(banned_file)
            if banned_models:
                console.print(f"[yellow]Excluded {len(banned_models)} banned models from {banned_file}[/yellow]")
    except FileNotFoundError:
        console.print(f"[red]Models list file not found:[/red] {models_file}")
        sys.exit(1)
    except ValueError as e:
        console.print(f"[red]Error parsing models.list:[/red] {e}")
        sys.exit(1)

    if not models:
        console.print("[yellow]No models found in models.list[/yellow]")
        sys.exit(0)

    # Create directories
    Path(results_dir).mkdir(parents=True, exist_ok=True)
    Path(temp_dir).mkdir(parents=True, exist_ok=True)

    # Process each model
    results = []
    skipped = 0
    for model_entry in models:
        result = process_single_model(
            task_file=task_file,
            model_entry=model_entry,
            results_dir=results_dir,
            temp_dir=temp_dir,
            mode="chat",
            timeout=timeout
        )
        results.append(result)

        # Check if result JSON already exists
        filename = f"{sanitize_filename(result.task_id)}_chat_{sanitize_filename(result.provider)}_{sanitize_filename(result.model)}.json"
        filepath = Path(results_dir) / filename

        if filepath.exists():
            console.print(f"  [dim]Skipped: {filepath.name} (already exists)[/dim]")
            skipped += 1
        else:
            filepath = save_result(result, results_dir, filename)
            console.print(f"  [dim]Saved: {filepath}[/dim]")

    # Summary
    successful = sum(1 for r in results if r.success)
    correct = sum(1 for r in results if r.correct)

    console.print(f"\n[green]Completed:[/green] {successful}/{len(results)} successful, {correct}/{len(results)} correct", end="")
    if skipped > 0:
        console.print(f" ({skipped} skipped)")
    else:
        console.print()


@main.command()
@click.option('-f', '--file', 'task_file', required=True,
              help='Path to the YAML task file or text file')
@click.option('-m', '--models', 'models_file', default='./models.list',
              help='Path to models.list file (default: ./models.list)')
@click.option('-b', '--banned', 'banned_file', default='./models_banned.list',
              help='Path to models_banned.list file (default: ./models_banned.list)')
@click.option('--results-dir', default='./results',
              help='Directory to save JSON results (default: ./results)')
@click.option('--temp-dir', default='./temp',
              help='Directory for temporary files (default: ./temp)')
@click.option('--timeout', type=int, default=300,
              help='Timeout in seconds for each model (default: 300)')
def code(task_file: str, models_file: str, banned_file: str, results_dir: str, temp_dir: str, timeout: int):
    """Run codeagent task against all models in models.list."""

    # Parse models.list with banned list filtering
    try:
        models = parse_models_list(models_file, banned_file)
        console.print(f"[green]Loaded {len(models)} models from {models_file}[/green]")
        if Path(banned_file).exists():
            from agentgog.zz_models_list import parse_banned_models_list
            banned_models = parse_banned_models_list(banned_file)
            if banned_models:
                console.print(f"[yellow]Excluded {len(banned_models)} banned models from {banned_file}[/yellow]")
    except FileNotFoundError:
        console.print(f"[red]Models list file not found:[/red] {models_file}")
        sys.exit(1)
    except ValueError as e:
        console.print(f"[red]Error parsing models.list:[/red] {e}")
        sys.exit(1)

    if not models:
        console.print("[yellow]No models found in models.list[/yellow]")
        sys.exit(0)

    # Create directories
    Path(results_dir).mkdir(parents=True, exist_ok=True)
    Path(temp_dir).mkdir(parents=True, exist_ok=True)

    # Process each model
    results = []
    skipped = 0
    for model_entry in models:
        result = process_single_model(
            task_file=task_file,
            model_entry=model_entry,
            results_dir=results_dir,
            temp_dir=temp_dir,
            mode="code",
            timeout=timeout
        )
        results.append(result)

        # Check if result JSON already exists
        filename = f"{sanitize_filename(result.task_id)}_code_{sanitize_filename(result.provider)}_{sanitize_filename(result.model)}.json"
        filepath = Path(results_dir) / filename

        if filepath.exists():
            console.print(f"  [dim]Skipped: {filepath.name} (already exists)[/dim]")
            skipped += 1
        else:
            filepath = save_result(result, results_dir, filename)
            console.print(f"  [dim]Saved: {filepath}[/dim]")

    # Summary
    successful = sum(1 for r in results if r.success)
    correct = sum(1 for r in results if r.correct)

    console.print(f"\n[green]Completed:[/green] {successful}/{len(results)} successful, {correct}/{len(results)} correct", end="")
    if skipped > 0:
        console.print(f" ({skipped} skipped)")
    else:
        console.print()


@main.command()
@click.argument('results_dir', default='./results')
@click.option('--color-time', is_flag=True, help='Color code time instead of result (red-yellow-green scale)')
def summary(results_dir: str, color_time: bool):
    """Display a summary of all results.

    Creates a pivot table with:
    - Rows: provider + model combinations
    - Columns: task_id + mode combinations (mode extracted from filename)
    - Values: verification_result and time_sec

    By default, colors verification_result values:
    - green: same (best result)
    - bright_green: similar (passing)
    - yellow: differ (failing)
    - red: none (technical failure)

    With --color-time, colors time on red-yellow-green scale based on min/max.
    """
    results_path = Path(results_dir)

    if not results_path.exists():
        console.print(f"[red]Results directory not found:[/red] {results_dir}")
        sys.exit(1)

    result_files = list(results_path.glob('*.json'))

    if not result_files:
        console.print(f"[yellow]No result files found in {results_dir}[/yellow]")
        sys.exit(0)

    # Parse all results and organize by model and task+mode
    # Structure: {(provider, model): {(task_id, mode): {data}}}
    all_data = {}
    all_task_modes = set()
    all_times = []

    for result_file in sorted(result_files):
        with open(result_file) as f:
            data = json.load(f)

        provider = data.get('provider', 'unknown')
        model = data.get('model', 'unknown')
        task_id = data.get('task_id', 'unknown')
        time_sec = data.get('time_sec', 0.0)
        verification_result = data.get('verification_result', 'none')
        mode = data.get('mode', 'unknown')

        model_key = (provider, model)
        task_mode_key = (task_id, mode)

        if model_key not in all_data:
            all_data[model_key] = {}

        all_data[model_key][task_mode_key] = {
            'time_sec': time_sec,
            'verification_result': verification_result,
            'success': data.get('success', False),
            'correct': data.get('correct', False),
        }

        all_task_modes.add(task_mode_key)
        all_times.append(time_sec)

    # Sort task_modes for consistent column order
    sorted_task_modes = sorted(all_task_modes)

    # Calculate time range for color scaling
    min_time = min(all_times) if all_times else 0
    max_time = max(all_times) if all_times else 1
    time_range = max_time - min_time if max_time > min_time else 1

    # Create table
    table = Table(title="Test Results Summary")
    table.add_column("Provider", style="cyan", no_wrap=True)
    table.add_column("Model", style="magenta", no_wrap=True)

    # Add columns for each task+mode combination
    for task_id, mode in sorted_task_modes:
        col_name = f"{task_id}\n({mode})"
        table.add_column(col_name, justify="center", min_width=12)

    # Color mapping for verification results
    result_styles = {
        'same': 'green',
        'similar': 'cyan',
        'differ': 'yellow',
        'none': 'red',
        'None': 'red',
        None: 'red',
    }

    def get_time_style(time_val):
        """Get color style for time value based on min-max range."""
        if time_range == 0:
            return 'green'
        # Normalize to 0-1 range
        normalized = (time_val - min_time) / time_range
        if normalized < 0.33:
            return 'green'
        elif normalized < 0.67:
            return 'yellow'
        else:
            return 'red'

    # Add rows for each model
    for (provider, model) in sorted(all_data.keys()):
        row = [provider, model]

        for task_mode_key in sorted_task_modes:
            if task_mode_key in all_data[(provider, model)]:
                data = all_data[(provider, model)][task_mode_key]
                time_sec = data['time_sec']
                verification_result = data['verification_result']

                if color_time:
                    # Color code time, show result as text
                    time_style = get_time_style(time_sec)
                    result_text = verification_result if verification_result else 'none'
                    cell_text = f"[{time_style}]{time_sec:.1f}s[/{time_style}]\n{result_text}"
                else:
                    # Color code verification result, show time
                    result_style = result_styles.get(verification_result, 'white')
                    cell_text = f"[{result_style}]{verification_result}[/{result_style}]\n{time_sec:.1f}s"

                row.append(cell_text)
            else:
                row.append("[dim]—[/dim]")

        table.add_row(*row)

    # Add legend
    console.print(table)
    console.print()

    if color_time:
        console.print("[dim]Legend (time color-coded):[/dim]")
        console.print(f"  [green]Green[/green]: {min_time:.1f}s (fastest)")
        console.print(f"  [yellow]Yellow[/yellow]: ~{(min_time + max_time)/2:.1f}s (average)")
        console.print(f"  [red]Red[/red]: {max_time:.1f}s (slowest)")
    else:
        console.print("[dim]Legend (result color-coded):[/dim]")
        console.print("  [green]green[/green]: same (identical)")
        console.print("  [cyan]cyan[/cyan]: similar (passing)")
        console.print("  [yellow]yellow[/yellow]: differ (failing)")
        console.print("  [red]red[/red]: none (technical failure)")
    console.print(f"\n[dim]Use --color-time to color-code execution times instead.[/dim]")


if __name__ == '__main__':
    main()
