"""Run command system CLI subcommand group

Provides run instance management, logging, screenshot and other commands
"""

import json
import sys
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, Optional

import click

from ..run.context import ContextManager
from ..run.discovery import RunDiscovery
from ..run.exceptions import (
    ContextAlreadySetError,
    ContextNotSetError,
    FileSystemError,
    RunException,
    RunNotFoundError,
)
from ..run.logger import RunLogger
from ..run.manager import RunManager
from ..run.models import ActionType, ExecutionMethod, InsightEntry, InsightType, LogStatus, RunStatus
from .agent_friendly import AgentFriendlyGroup


# Use user directory consistently
FRAGO_HOME = Path.home() / ".frago"
PROJECTS_DIR = FRAGO_HOME / "projects"


def get_manager() -> RunManager:
    """Get RunManager instance"""
    return RunManager(PROJECTS_DIR)


def get_context_manager() -> ContextManager:
    """Get ContextManager instance"""
    return ContextManager(FRAGO_HOME, PROJECTS_DIR)


def format_timestamp(dt: datetime) -> str:
    """Format timestamp to ISO 8601 format (with Z suffix)"""
    return dt.isoformat().replace("+00:00", "Z")


def output_json(data: Dict[str, Any]) -> None:
    """Output data in JSON format"""
    click.echo(json.dumps(data, ensure_ascii=False, indent=2))


def get_extra_metadata(instance: Any) -> Dict[str, Any]:
    """Get extra metadata fields (excluding core fields)"""
    core_fields = {"run_id", "theme_description", "created_at", "last_accessed", "status"}

    # Get all fields of the instance
    instance_dict = instance.model_dump()

    # Filter out extra fields
    extra_metadata = {}
    for key, value in instance_dict.items():
        if key not in core_fields:
            extra_metadata[key] = value

    return extra_metadata


def format_extra_metadata(extra_metadata: Dict[str, Any], indent: str = "  ") -> str:
    """Format extra metadata into a readable string"""
    if not extra_metadata:
        return ""

    lines = ["\nExtra Metadata:"]
    for key, value in sorted(extra_metadata.items()):
        # Use type name check to avoid Pydantic special type issues
        type_name = type(value).__name__
        if type_name == "dict" or isinstance(value, dict):
            lines.append(f"{indent}- {key}:")
            for sub_key, sub_value in value.items():
                lines.append(f"{indent}  {sub_key}: {json.dumps(sub_value, ensure_ascii=False)}")
        elif type_name == "list" or hasattr(value, "__iter__") and not isinstance(value, (str, dict)):
            lines.append(f"{indent}- {key}: {json.dumps(value, ensure_ascii=False)}")
        else:
            lines.append(f"{indent}- {key}: {value}")

    return "\n".join(lines)


def handle_error(e: Exception, exit_code: int = 1) -> None:
    """Unified error handling"""
    click.echo(f"Error: {e}", err=True)
    sys.exit(exit_code)


@click.group(name="run", cls=AgentFriendlyGroup)
def run_group():
    """Run command system - Manage AI-driven task execution

    \b
    Core concepts:
    - Run instance: Theme-based information center that persistently stores task execution history
    - Context: Currently active run instance (set via set-context)
    - Logs: Structured execution records in JSONL format

    \b
    Typical workflow:
    1. Create run: frago run init "task description"
    2. Set context: frago run set-context <run_id>
    3. Log entry: frago run log --step "..." --status "success" ...
    4. Check progress: frago run info <run_id>
    5. Archive: frago run archive <run_id>
    """
    pass


@run_group.command()
@click.argument("description")
@click.option(
    "--run-id",
    help="Custom run ID (auto-generated by default)",
)
def init(description: str, run_id: Optional[str]):
    """Initialize a new run instance

    \b
    Examples:
        frago run init "Search Python jobs on Upwork"
        frago run init "Test task" --run-id custom-id
    """
    try:
        manager = get_manager()
        instance = manager.create_run(description, run_id)

        output_json({
            "run_id": instance.run_id,
            "created_at": format_timestamp(instance.created_at),
            "path": str(PROJECTS_DIR / instance.run_id),
        })
    except RunException as e:
        handle_error(e)


@run_group.command()
@click.argument("run_id")
def set_context(run_id: str):
    """Set the current working run

    \b
    Note: The system allows only one active run context. To switch, first release with the release command.

    \b
    Examples:
        frago run set-context find-job-on-upwork
    """
    try:
        manager = get_manager()
        instance = manager.find_run(run_id)

        context_mgr = get_context_manager()
        context = context_mgr.set_current_run(run_id, instance.theme_description)

        output_json({
            "run_id": context.run_id,
            "theme_description": context.theme_description,
            "set_at": format_timestamp(context.last_accessed),
        })
    except ContextAlreadySetError as e:
        handle_error(e, exit_code=2)
    except RunException as e:
        handle_error(e)


@run_group.command()
def release():
    """Release the current run context (mutual exclusion lock)

    \b
    Used to release the currently active context after task completion or before switching tasks.

    \b
    Examples:
        frago run release
    """
    try:
        context_mgr = get_context_manager()
        released_run_id = context_mgr.release_context()

        if released_run_id:
            output_json({
                "released_run_id": released_run_id,
                "released_at": format_timestamp(datetime.now()),
            })
        else:
            click.echo("No active context to release.")
    except Exception as e:
        handle_error(e)


@run_group.command()
@click.option(
    "--format",
    type=click.Choice(["table", "json"]),
    default="table",
    help="Output format",
)
@click.option(
    "--status",
    type=click.Choice(["all", "active", "archived"]),
    default="all",
    help="Filter by status",
)
def list(format: str, status: str):
    """List all run instances

    \b
    Examples:
        frago run list
        frago run list --format json --status active
    """
    try:
        manager = get_manager()

        # Status filtering
        status_filter = None
        if status == "active":
            status_filter = RunStatus.ACTIVE
        elif status == "archived":
            status_filter = RunStatus.ARCHIVED

        runs = manager.list_runs(status=status_filter)

        if format == "json":
            output_json({"runs": runs, "total": len(runs)})
        else:
            # Table format
            if not runs:
                click.echo("No run instances found.")
                return

            # Table header
            click.echo(
                f"{'RUN_ID':<40} {'STATUS':<10} {'CREATED_AT':<20} {'LAST_ACCESSED':<20}"
            )
            click.echo("-" * 90)

            # Data rows
            for run in runs:
                created = run["created_at"][:19].replace("T", " ")
                accessed = run["last_accessed"][:19].replace("T", " ")
                click.echo(
                    f"{run['run_id']:<40} {run['status']:<10} {created:<20} {accessed:<20}"
                )

    except RunException as e:
        handle_error(e)


@run_group.command()
@click.argument("run_id")
@click.option(
    "--format",
    type=click.Choice(["human", "json"]),
    default="human",
    help="Output format",
)
def info(run_id: str, format: str):
    """Show run instance details

    \b
    Examples:
        frago run info find-job-on-upwork
        frago run info find-job-on-upwork --format json
    """
    try:
        manager = get_manager()
        instance = manager.find_run(run_id)
        stats = manager.get_run_statistics(run_id)

        run_dir = PROJECTS_DIR / run_id
        logger = RunLogger(run_dir)
        recent_logs = logger.get_recent_logs(count=5)

        if format == "json":
            output_json({
                "run_id": instance.run_id,
                "status": instance.status.value,
                "theme_description": instance.theme_description,
                "created_at": format_timestamp(instance.created_at),
                "last_accessed": format_timestamp(instance.last_accessed),
                "extra_metadata": get_extra_metadata(instance),
                "statistics": stats,
                "recent_logs": [
                    {
                        "timestamp": format_timestamp(log.timestamp),
                        "step": log.step,
                        "status": log.status.value,
                        "action_type": log.action_type.value,
                        "execution_method": log.execution_method.value,
                    }
                    for log in recent_logs
                ],
            })
        else:
            # Human-readable format
            click.echo(f"\nRun ID: {instance.run_id}")
            click.echo(f"Status: {instance.status.value}")
            click.echo(f"Theme: {instance.theme_description}")
            click.echo(
                f"Created: {instance.created_at.strftime('%Y-%m-%d %H:%M:%S')}"
            )
            click.echo(
                f"Last Accessed: {instance.last_accessed.strftime('%Y-%m-%d %H:%M:%S')}"
            )

            click.echo(f"\nStatistics:")
            click.echo(f"- Log Entries: {stats['log_entries']}")
            click.echo(f"- Screenshots: {stats['screenshots']}")
            click.echo(f"- Scripts: {stats['scripts']}")
            click.echo(f"- Disk Usage: {stats['disk_usage_bytes'] / 1024:.1f} KB")

            # Display extra metadata
            extra_metadata = get_extra_metadata(instance)
            if extra_metadata:
                click.echo(format_extra_metadata(extra_metadata))

            if recent_logs:
                click.echo(f"\nRecent Logs (last 5):")
                for log in recent_logs:
                    status_icon = "[OK]" if log.status == LogStatus.SUCCESS else "[X]"
                    timestamp = log.timestamp.strftime("%Y-%m-%d %H:%M")
                    click.echo(
                        f"  [{timestamp}] {status_icon} {log.step} "
                        f"({log.action_type.value}/{log.execution_method.value})"
                    )

    except RunException as e:
        handle_error(e)


@run_group.command()
@click.argument("run_id")
def archive(run_id: str):
    """Archive a run instance

    \b
    Examples:
        frago run archive find-job-on-upwork
    """
    try:
        manager = get_manager()
        instance = manager.archive_run(run_id)

        # If it's the current context's run, clear the context
        context_mgr = get_context_manager()
        current_run_id = context_mgr.get_current_run_id()
        if current_run_id == run_id:
            context_mgr._clear_context()

        output_json({
            "run_id": instance.run_id,
            "archived_at": format_timestamp(datetime.now()),
            "previous_status": "active",
        })
    except RunException as e:
        handle_error(e)


@run_group.command()
@click.option("--step", required=True, help="Step description")
@click.option(
    "--status",
    type=click.Choice(["success", "error", "warning"]),
    required=True,
    help="Execution status",
)
@click.option(
    "--action-type",
    type=click.Choice(
        [
            "navigation",
            "extraction",
            "interaction",
            "screenshot",
            "recipe_execution",
            "data_processing",
            "analysis",
            "user_interaction",
            "other",
        ]
    ),
    required=True,
    help="Action type",
)
@click.option(
    "--execution-method",
    type=click.Choice(["command", "recipe", "file", "manual", "analysis", "tool"]),
    required=True,
    help="Execution method",
)
@click.option("--data", required=True, help="Detailed data in JSON format")
@click.option(
    "--insight",
    multiple=True,
    help="Key findings/pitfalls, format: 'type:summary' or JSON. Type options: key_factor, pitfall, lesson, workaround",
)
def log(step: str, status: str, action_type: str, execution_method: str, data: str, insight: tuple):
    """Record structured log entry

    \b
    Examples:
        frago run log \\
          --step "Navigate to search page" \\
          --status "success" \\
          --action-type "navigation" \\
          --execution-method "command" \\
          --data '{"command": "frago chrome navigate https://upwork.com"}'

    \b
    Example with insight:
        frago run log \\
          --step "Extract job listings" \\
          --status "error" \\
          --action-type "extraction" \\
          --execution-method "command" \\
          --data '{"error": "Selector failed"}' \\
          --insight "pitfall:Dynamic class caused selector failure, need to use data-testid"

    \b
    JSON format insight:
        --insight '{"insight_type": "key_factor", "summary": "Must wait for page load", "detail": "Can only extract after loading animation disappears"}'
    """
    try:
        # Get current context
        context_mgr = get_context_manager()
        context = context_mgr.get_current_run()

        # Parse data
        try:
            data_dict = json.loads(data)
        except json.JSONDecodeError as e:
            click.echo(f"Error: Invalid JSON in --data: {e}", err=True)
            sys.exit(2)

        # Parse insights
        insights_list = None
        if insight:
            insights_list = []
            for i in insight:
                try:
                    # Try to parse JSON format
                    if i.strip().startswith("{"):
                        insight_data = json.loads(i)
                        insights_list.append(InsightEntry.from_dict(insight_data))
                    else:
                        # Shorthand format: "type:summary"
                        if ":" not in i:
                            click.echo(f"Error: Invalid insight format '{i}'. Use 'type:summary' or JSON.", err=True)
                            sys.exit(2)
                        insight_type, summary = i.split(":", 1)
                        insight_type = insight_type.strip().lower()
                        if insight_type not in ["key_factor", "pitfall", "lesson", "workaround"]:
                            click.echo(f"Error: Unknown insight type '{insight_type}'. Use: key_factor, pitfall, lesson, workaround", err=True)
                            sys.exit(2)
                        insights_list.append(InsightEntry(
                            insight_type=InsightType(insight_type),
                            summary=summary.strip(),
                        ))
                except json.JSONDecodeError as e:
                    click.echo(f"Error: Invalid JSON in --insight: {e}", err=True)
                    sys.exit(2)

        # Write log
        run_dir = PROJECTS_DIR / context.run_id
        logger = RunLogger(run_dir)
        entry = logger.write_log(
            step=step,
            status=LogStatus(status),
            action_type=ActionType(action_type),
            execution_method=ExecutionMethod(execution_method),
            data=data_dict,
            insights=insights_list,
        )

        result = {
            "logged_at": format_timestamp(entry.timestamp),
            "run_id": context.run_id,
            "log_file": str(run_dir / "logs" / "execution.jsonl"),
        }
        if insights_list:
            result["insights_count"] = len(insights_list)
        output_json(result)
    except ContextNotSetError as e:
        handle_error(e)
    except RunException as e:
        handle_error(e, exit_code=3)


@run_group.command()
@click.argument("description")
def screenshot(description: str):
    """Save screenshot (auto-numbered)

    \b
    Examples:
        frago run screenshot "Search results page"
    """
    try:
        # Get current context
        context_mgr = get_context_manager()
        context = context_mgr.get_current_run()

        run_dir = PROJECTS_DIR / context.run_id
        screenshots_dir = run_dir / "screenshots"

        # Import screenshot module (to be implemented)
        from ..run.screenshot import capture_screenshot

        file_path, sequence_number = capture_screenshot(description, screenshots_dir)

        # Automatically record log
        logger = RunLogger(run_dir)
        logger.write_log(
            step=f"Screenshot: {description}",
            status=LogStatus.SUCCESS,
            action_type=ActionType.SCREENSHOT,
            execution_method=ExecutionMethod.COMMAND,
            data={
                "file_path": str(file_path),
                "sequence_number": sequence_number,
                "description": description,
            },
        )

        output_json({
            "file_path": str(file_path),
            "sequence_number": sequence_number,
            "saved_at": format_timestamp(datetime.now()),
        })
    except ContextNotSetError as e:
        handle_error(e)
    except RunException as e:
        handle_error(e, exit_code=2)
