"""crondag dags — DAG management commands."""
from __future__ import annotations

import asyncio
import json
import sys
from datetime import datetime, timezone

import click

from crondag.config import get_settings


def _output(data: dict | list, output_format: str):
    """Output data in the requested format."""
    if output_format == "json":
        click.echo(json.dumps(data, indent=2, default=str))
    else:
        if isinstance(data, list):
            for item in data:
                _print_row(item)
        else:
            _print_row(data)


def _print_row(item: dict):
    """Print a single item as a formatted row."""
    parts = []
    for k, v in item.items():
        parts.append(f"{k}={v}")
    click.echo("  ".join(parts))


@click.group()
def dags():
    """DAG management commands."""
    pass


@dags.command("list")
@click.option("-o", "--output-format", type=click.Choice(["text", "json"]), default="text")
def dags_list(output_format: str):
    """List all DAGs."""
    async def _run():
        settings = get_settings()
        from crondag.models.base import get_session_factory, get_engine
        from crondag.models.dag import DagModel
        from sqlalchemy import select

        # Ensure engine is created
        get_engine(settings.database_url)
        session_factory = get_session_factory(settings.database_url)

        async with session_factory() as session:
            result = await session.execute(select(DagModel).order_by(DagModel.dag_id))
            dag_models = result.scalars().all()
            data = [
                {
                    "dag_id": d.dag_id,
                    "is_paused": d.is_paused,
                    "schedule": f"{d.schedule_type}:{d.schedule_value}" if d.schedule_type else "None",
                    "next_run": str(d.next_run_at) if d.next_run_at else "None",
                    "fileloc": d.fileloc,
                }
                for d in dag_models
            ]

        if output_format == "json":
            click.echo(json.dumps(data, indent=2, default=str))
        else:
            if not data:
                click.echo("No DAGs found.")
                return
            # Table format
            headers = ["DAG ID", "Paused", "Schedule", "Next Run", "File"]
            widths = [max(len(headers[i]), max((len(str(d[k])) for d in data), default=0))
                      for i, k in enumerate(["dag_id", "is_paused", "schedule", "next_run", "fileloc"])]
            header_line = "  ".join(h.ljust(w) for h, w in zip(headers, widths))
            click.echo(header_line)
            click.echo("-" * len(header_line))
            for d in data:
                row = [
                    str(d["dag_id"]).ljust(widths[0]),
                    str(d["is_paused"]).ljust(widths[1]),
                    str(d["schedule"]).ljust(widths[2]),
                    str(d["next_run"]).ljust(widths[3]),
                    str(d["fileloc"]).ljust(widths[4]),
                ]
                click.echo("  ".join(row))

    asyncio.run(_run())


@dags.command("info")
@click.argument("dag_id")
@click.option("-o", "--output-format", type=click.Choice(["text", "json"]), default="text")
def dags_info(dag_id: str, output_format: str):
    """Show details about a DAG."""
    async def _run():
        settings = get_settings()
        from crondag.models.base import get_session_factory, get_engine
        from crondag.models.dag import DagModel
        from crondag.models.dag_run import DagRunModel
        from sqlalchemy import select

        get_engine(settings.database_url)
        session_factory = get_session_factory(settings.database_url)

        async with session_factory() as session:
            dag_model = await session.get(DagModel, dag_id)
            if not dag_model:
                click.echo(f"DAG '{dag_id}' not found.", err=True)
                sys.exit(1)

            # Get recent runs
            runs_result = await session.execute(
                select(DagRunModel)
                .where(DagRunModel.dag_id == dag_id)
                .order_by(DagRunModel.created_at.desc())
                .limit(5)
            )
            recent_runs = runs_result.scalars().all()

            data = {
                "dag_id": dag_model.dag_id,
                "description": dag_model.description,
                "schedule_type": dag_model.schedule_type,
                "schedule_value": dag_model.schedule_value,
                "is_paused": dag_model.is_paused,
                "fileloc": dag_model.fileloc,
                "next_run_at": str(dag_model.next_run_at),
                "last_parsed_at": str(dag_model.last_parsed_at),
                "tags": json.loads(dag_model.tags) if dag_model.tags else [],
                "recent_runs": [
                    {
                        "run_id": r.run_id,
                        "state": r.state,
                        "logical_date": str(r.logical_date),
                        "start_date": str(r.start_date),
                        "end_date": str(r.end_date),
                    }
                    for r in recent_runs
                ],
            }

        if output_format == "json":
            click.echo(json.dumps(data, indent=2, default=str))
        else:
            click.echo(f"DAG: {data['dag_id']}")
            click.echo(f"  Description: {data['description']}")
            click.echo(f"  Schedule: {data['schedule_type']}:{data['schedule_value']}")
            click.echo(f"  Paused: {data['is_paused']}")
            click.echo(f"  File: {data['fileloc']}")
            click.echo(f"  Next Run: {data['next_run_at']}")
            if data["recent_runs"]:
                click.echo(f"  Recent Runs:")
                for r in data["recent_runs"]:
                    click.echo(f"    {r['run_id']}  {r['state']}  {r['logical_date']}")

    asyncio.run(_run())


@dags.command("trigger")
@click.argument("dag_id")
@click.option("--conf", default=None, help="JSON configuration for the run")
@click.option("--wait", is_flag=True, help="Wait for the run to complete")
@click.option("--logs", "show_logs", is_flag=True, help="Show task logs (implies --wait)")
@click.option("-o", "--output-format", type=click.Choice(["text", "json"]), default="text")
def dags_trigger(dag_id: str, conf: str | None, wait: bool, show_logs: bool, output_format: str):
    """Trigger a DAG run."""
    if show_logs:
        wait = True

    conf_dict = json.loads(conf) if conf else None

    async def _run():
        settings = get_settings()
        from crondag.models.base import get_session_factory, get_engine, init_db
        from crondag.models.dag_run import DagRunModel
        from crondag.models.task_instance import TaskInstanceModel
        from crondag.dag_bag import DagBag
        from crondag.scheduler.loop import Scheduler
        from crondag.sdk.state import DagRunState
        from sqlalchemy import select

        await init_db(settings.database_url)
        session_factory = get_session_factory(settings.database_url)

        scheduler = Scheduler(settings)
        await scheduler._sync_dags()

        dag_run = await scheduler.trigger_dag(dag_id, conf_dict)
        if not dag_run:
            click.echo(f"DAG '{dag_id}' not found.", err=True)
            sys.exit(1)

        click.echo(f"Triggered DAG '{dag_id}', run_id: {dag_run.run_id}")

        if wait:
            click.echo("Waiting for run to complete...")
            while True:
                # Run one scheduler tick
                await scheduler._tick()

                # Check run state
                async with session_factory() as session:
                    run = await session.execute(
                        select(DagRunModel).where(DagRunModel.id == dag_run.id)
                    )
                    current_run = run.scalar_one_or_none()
                    if not current_run:
                        break

                    if current_run.state in (DagRunState.SUCCESS, DagRunState.FAILED):
                        # Get task instances
                        ti_result = await session.execute(
                            select(TaskInstanceModel).where(
                                TaskInstanceModel.run_id == current_run.run_id,
                                TaskInstanceModel.dag_id == dag_id,
                            )
                        )
                        tasks = ti_result.scalars().all()

                        result_data = {
                            "dag_id": dag_id,
                            "run_id": current_run.run_id,
                            "state": current_run.state,
                            "start_date": str(current_run.start_date),
                            "end_date": str(current_run.end_date),
                            "tasks": [
                                {
                                    "task_id": ti.task_id,
                                    "state": ti.state,
                                    "duration": ti.duration,
                                    "error": ti.error,
                                }
                                for ti in tasks
                            ],
                        }

                        if output_format == "json":
                            click.echo(json.dumps(result_data, indent=2, default=str))
                        else:
                            click.echo(f"Run {current_run.run_id}: {current_run.state}")
                            for ti_data in result_data["tasks"]:
                                status = "\u2713" if ti_data["state"] == "success" else "\u2717"
                                click.echo(f"  {status} {ti_data['task_id']}: {ti_data['state']}"
                                          + (f" ({ti_data['duration']:.1f}s)" if ti_data['duration'] else ""))
                                if ti_data["error"]:
                                    click.echo(f"    Error: {ti_data['error']}")
                        break

                await asyncio.sleep(0.5)

            await scheduler.executor.shutdown()
        else:
            if output_format == "json":
                click.echo(json.dumps({
                    "dag_id": dag_id,
                    "run_id": dag_run.run_id,
                    "state": dag_run.state,
                }, indent=2, default=str))

    asyncio.run(_run())


@dags.command("pause")
@click.argument("dag_id")
def dags_pause(dag_id: str):
    """Pause a DAG."""
    async def _run():
        settings = get_settings()
        from crondag.models.base import get_session_factory, get_engine
        from crondag.models.dag import DagModel

        get_engine(settings.database_url)
        session_factory = get_session_factory(settings.database_url)

        async with session_factory() as session:
            dag_model = await session.get(DagModel, dag_id)
            if not dag_model:
                click.echo(f"DAG '{dag_id}' not found.", err=True)
                sys.exit(1)
            dag_model.is_paused = True
            await session.commit()
        click.echo(f"Paused DAG '{dag_id}'")

    asyncio.run(_run())


@dags.command("unpause")
@click.argument("dag_id")
def dags_unpause(dag_id: str):
    """Unpause a DAG."""
    async def _run():
        settings = get_settings()
        from crondag.models.base import get_session_factory, get_engine
        from crondag.models.dag import DagModel

        get_engine(settings.database_url)
        session_factory = get_session_factory(settings.database_url)

        async with session_factory() as session:
            dag_model = await session.get(DagModel, dag_id)
            if not dag_model:
                click.echo(f"DAG '{dag_id}' not found.", err=True)
                sys.exit(1)
            dag_model.is_paused = False
            await session.commit()
        click.echo(f"Unpaused DAG '{dag_id}'")

    asyncio.run(_run())


@dags.command("check")
@click.argument("file", required=False)
def dags_check(file: str | None):
    """Validate DAG files."""
    settings = get_settings()
    from crondag.dag_bag import DagBag
    import os

    if file:
        # Check single file
        bag = DagBag(os.path.dirname(file) or ".")
        bag._import_dag_file(__import__("pathlib").Path(file))
        if bag.import_errors:
            for path, err in bag.import_errors.items():
                click.echo(f"ERROR {path}: {err}", err=True)
            sys.exit(1)
        click.echo(f"OK: {file}")
    else:
        bag = DagBag(settings.dags_dir)
        bag.collect_dags()
        if bag.import_errors:
            for path, err in bag.import_errors.items():
                click.echo(f"ERROR {path}: {err}", err=True)
        click.echo(f"Found {len(bag.dags)} valid DAG(s)")
        if bag.import_errors:
            click.echo(f"{len(bag.import_errors)} file(s) with errors", err=True)
            sys.exit(1)
