from typing import Optional, List
from pathlib import Path
import click
import asyncio
import json
import sys

def _load_compose_config(config_files: List[Path], env_files: List[Path], env_data: List[str]):
    from mindor.dsl.loader import load_compose_config
    from mindor.core.runtime.env import load_env_files, merge_env_data

    env = load_env_files(".", env_files or [])
    env = merge_env_data(env, env_data)

    return load_compose_config(".", config_files, env)

def _get_version() -> str:
    from mindor.version import __version__

    return __version__

@click.group()
@click.option(
    "--file", "-f", "config_files", multiple=True,
    type=click.Path(exists=True, dir_okay=False, path_type=Path),
    help="Compose configuration files."
)
@click.version_option(
    version=_get_version(),
    message="%(prog)s %(version)s"
)
@click.pass_context
def compose_command(ctx: click.Context, config_files: List[Path]) -> None:
    """model-compose"""
    ctx.ensure_object(dict)
    ctx.obj["config_files"] = list(config_files)

@click.command(name="up")
@click.option("-d", "--detach", is_flag=True, help="Run in detached mode.")
@click.option(
    "--env-file", "env_files", multiple=True,
    type=click.Path(exists=True, dir_okay=False, path_type=Path),
    required=False,
    help="Path to a .env file containing environment variables."
)
@click.option(
    "--env", "-e", "env_data", multiple=True,
    help="Environment variable in the form KEY=VALUE. Repeatable."
)
@click.option("--verbose", "-v", is_flag=True, help="Enable verbose output.")
@click.pass_context
def up_command(
    ctx: click.Context,
    detach: bool,
    env_files: List[Path],
    env_data: List[str],
    verbose: bool
) -> None:
    from mindor.core.compose import launch_services
    config_files = ctx.obj.get("config_files", [])
    async def _async_command():
        try:
            config = _load_compose_config(config_files, env_files, env_data)
            await launch_services(config, detach, verbose)
        except Exception as e:
            import traceback
            click.echo(f"❌ Error: {e}\n\nTraceback:", err=True)
            traceback.print_exc()
            raise SystemExit(1)
    asyncio.run(_async_command())

@click.command(name="down")
@click.option(
    "--env-file", "env_files", multiple=True,
    type=click.Path(exists=True, dir_okay=False, path_type=Path),
    required=False,
    help="Path to a .env file containing environment variables."
)
@click.option(
    "--env", "-e", "env_data", multiple=True,
    help="Environment variable in the form KEY=VALUE. Repeatable."
)
@click.option("--verbose", "-v", is_flag=True, help="Enable verbose output.")
@click.pass_context
def down_command(
    ctx: click.Context,
    env_files: List[Path],
    env_data: List[str],
    verbose: bool
) -> None:
    from mindor.core.compose import terminate_services
    config_files = ctx.obj.get("config_files", [])
    async def _async_command():
        try:
            config = _load_compose_config(config_files, env_files, env_data)
            await terminate_services(config, verbose)
        except Exception as e:
            if verbose:
                import traceback
                click.echo(f"❌ Error: {e}\n\nTraceback:", err=True)
                traceback.print_exc()
            else:
                click.echo(f"❌ {e}", err=True)
            raise SystemExit(1)
    asyncio.run(_async_command())

@click.command(name="start")
@click.option(
    "--env-file", "env_files", multiple=True,
    type=click.Path(exists=True, dir_okay=False, path_type=Path),
    required=False,
    help="Path to a .env file containing environment variables."
)
@click.option(
    "--env", "-e", "env_data", multiple=True,
    help="Environment variable in the form KEY=VALUE. Repeatable.",
)
@click.option("--verbose", "-v", is_flag=True, help="Enable verbose output.")
@click.pass_context
def start_command(
    ctx: click.Context,
    env_files: List[Path],
    env_data: List[str],
    verbose: bool
) -> None:
    from mindor.core.compose import start_services
    config_files = ctx.obj.get("config_files", [])
    async def _async_command():
        try:
            config = _load_compose_config(config_files, env_files, env_data)
            await start_services(config, verbose)
        except Exception as e:
            if verbose:
                import traceback
                click.echo(f"❌ Error: {e}\n\nTraceback:", err=True)
                traceback.print_exc()
            else:
                click.echo(f"❌ {e}", err=True)
            raise SystemExit(1)
    asyncio.run(_async_command())

@click.command(name="stop")
@click.option(
    "--env-file", "env_files", multiple=True,
    type=click.Path(exists=True, dir_okay=False, path_type=Path),
    required=False,
    help="Path to a .env file containing environment variables."
)
@click.option(
    "--env", "-e", "env_data", multiple=True,
    help="Environment variable in the form KEY=VALUE. Repeatable."
)
@click.option("--verbose", "-v", is_flag=True, help="Enable verbose output.")
@click.pass_context
def stop_command(
    ctx: click.Context,
    env_files: List[Path],
    env_data: List[str],
    verbose: bool
) -> None:
    from mindor.core.compose import stop_services
    config_files = ctx.obj.get("config_files", [])
    async def _async_command():
        try:
            config = _load_compose_config(config_files, env_files, env_data)
            await stop_services(config, verbose)
        except Exception as e:
            if verbose:
                import traceback
                click.echo(f"❌ Error: {e}\n\nTraceback:", err=True)
                traceback.print_exc()
            else:
                click.echo(f"❌ {e}", err=True)
            raise SystemExit(1)
    asyncio.run(_async_command())

@click.command(name="run")
@click.argument("workflow_id", required=False)
@click.option(
    "--input", "-i", "input_json",
    type=str,
    required=False,
    help="JSON input string for the workflow",
)
@click.option(
    "--env-file", "env_files", multiple=True,
    type=click.Path(exists=True, dir_okay=False, path_type=Path),
    required=False,
    help="Path to a .env file containing environment variables."
)
@click.option(
    "--env", "-e", "env_data", multiple=True,
    help="Environment variable in the form KEY=VALUE. Repeatable."
)
@click.option(
    "--output", "-o", "output_path",
    type=click.Path(dir_okay=False, writable=True, path_type=Path),
    required=False,
    help="Path to save the output result as a file."
)
@click.option(
    "--metadata", "-m", "metadata_json",
    type=str,
    required=False,
    help="JSON metadata string to pass through the workflow and echo back in the response.",
)
@click.option(
    "--session-id", "session_id",
    type=str,
    required=False,
    help="Session ID to group multiple workflow runs.",
)
@click.option("--auto-resume", is_flag=True, help="Automatically resume interrupts without prompting.")
@click.option("--verbose", "-v", is_flag=True, help="Enable verbose output.")
@click.pass_context
def run_command(
    ctx: click.Context,
    workflow_id: Optional[str],
    input_json: Optional[str],
    env_files: List[Path],
    env_data: List[str],
    output_path: Optional[Path],
    metadata_json: Optional[str],
    session_id: Optional[str],
    auto_resume: bool,
    verbose: bool
) -> None:
    from mindor.core.compose.manager import ComposeManager
    from mindor.core.controller.base import TaskStatus
    from mindor.cli.interrupt import prompt_for_interrupt
    config_files = ctx.obj.get("config_files", [])
    async def _async_command():
        try:
            config = _load_compose_config(config_files, env_files, env_data)
            input = json.loads(input_json) if input_json else {}
            metadata = json.loads(metadata_json) if metadata_json else None
            is_tty = sys.stdin.isatty()

            manager = ComposeManager(config, daemon=False)
            state = await manager.run_workflow(workflow_id or "__default__", input, output_path, verbose, session_id=session_id, metadata=metadata)

            while state.status == TaskStatus.INTERRUPTED:
                if not auto_resume and not is_tty:
                    click.echo("❌ Workflow interrupted but no TTY available. Use --auto-resume to skip.", err=True)
                    raise SystemExit(1)
                answer = None if auto_resume else prompt_for_interrupt(state)
                state = await manager.resume_workflow(state.task_id, state.interrupt.job_id, answer)

            if isinstance(state.output, (dict, list)) or state.error:
                click.echo(json.dumps(
                    state.output or state.error,
                    indent=2,
                    ensure_ascii=False
                ))
            else:
                if state.output is not None:
                    click.echo(state.output)
        except click.exceptions.Abort:
            click.echo("\nInterrupt cancelled by user.", err=True)
            raise SystemExit(130)
        except json.JSONDecodeError:
            click.echo("❌ Invalid JSON provided for --input", err=True)
            raise SystemExit(1)
        except Exception as e:
            if verbose:
                import traceback
                click.echo(f"❌ Error: {e}\n\nTraceback:", err=True)
                traceback.print_exc()
            else:
                click.echo(f"❌ {e}", err=True)
            raise SystemExit(1)
    asyncio.run(_async_command())

@click.command(name="validate")
@click.option(
    "--env-file", "env_files", multiple=True,
    type=click.Path(exists=True, dir_okay=False, path_type=Path),
    required=False,
    help="Path to a .env file containing environment variables."
)
@click.option(
    "--env", "-e", "env_data", multiple=True,
    help="Environment variable in the form KEY=VALUE. Repeatable."
)
@click.option("--verbose", "-v", is_flag=True, help="Enable verbose output.")
@click.pass_context
def validate_command(
    ctx: click.Context,
    env_files: List[Path],
    env_data: List[str],
    verbose: bool
) -> None:
    from mindor.core.compose import validate_compose_config
    config_files = ctx.obj.get("config_files", [])
    try:
        config = _load_compose_config(config_files, env_files, env_data)
        errors = validate_compose_config(config)

        if errors:
            click.echo("❌ Configuration has semantic errors:\n", err=True)
            for error in errors:
                click.echo(f"  - {error}", err=True)
            raise SystemExit(1)
        else:
            summary_parts = [f"controller: {config.controller.type.value}"]
            if config.components:
                summary_parts.append(f"{len(config.components)} component(s)")
            if config.workflows:
                summary_parts.append(f"{len(config.workflows)} workflow(s)")
            if config.listeners:
                summary_parts.append(f"{len(config.listeners)} listener(s)")
            if config.gateways:
                summary_parts.append(f"{len(config.gateways)} gateway(s)")
            if config.systems:
                summary_parts.append(f"{len(config.systems)} system(s)")
            if config.tracers:
                summary_parts.append(f"{len(config.tracers)} tracer(s)")
            if config.loggers:
                summary_parts.append(f"{len(config.loggers)} logger(s)")
            click.echo(f"✅ Configuration is valid ({', '.join(summary_parts)})")
    except Exception as e:
        if verbose:
            import traceback
            click.echo(f"❌ Error: {e}\n\nTraceback:", err=True)
            traceback.print_exc()
        else:
            click.echo(f"❌ {e}", err=True)
        raise SystemExit(1)

compose_command.add_command(up_command)
compose_command.add_command(down_command)
compose_command.add_command(start_command)
compose_command.add_command(stop_command)
compose_command.add_command(run_command)
compose_command.add_command(validate_command)

if __name__ == "__main__":
    compose_command()
