from pathlib import Path
from typing import Annotated

from rich.console import Console
from rich.table import Table
from typer import Argument, Option, Typer

from harbor.auth.client import reset_client
from harbor.cli.utils import run_async

datasets_app = Typer(no_args_is_help=True)
console = Console()


@datasets_app.command(name="list")
def list_datasets(
    registry_url: Annotated[
        str | None,
        Option(
            "--registry-url",
            help="Registry URL for remote dataset listing",
            show_default="The default harbor registry.",
        ),
    ] = None,
    registry_path: Annotated[
        Path | None,
        Option(
            "--registry-path",
            help="Path to local registry for dataset listing",
            show_default=False,
        ),
    ] = None,
    legacy: Annotated[
        bool,
        Option(
            "--legacy",
            help="Show the legacy table-based listing instead of the registry website link.",
        ),
    ] = False,
):
    """List all datasets available in a registry.

    By default, prints a link to the Harbor registry website. Use --legacy
    to show the table-based listing.
    """
    if not legacy:
        from harbor.constants import HARBOR_REGISTRY_DATASETS_URL

        console.print(f"View registered datasets at {HARBOR_REGISTRY_DATASETS_URL}")
        return

    from harbor.models.registry import LocalRegistryInfo, RemoteRegistryInfo
    from harbor.registry.client.factory import RegistryClientFactory

    try:
        if registry_url is not None and registry_path is not None:
            console.print(
                "[red]Error: Cannot specify both --registry-url and --registry-path[/red]"
            )
            return

        if registry_path is not None:
            registry_info = LocalRegistryInfo(path=registry_path)
            console.print(f"[blue]Using local registry: {registry_path}[/blue]\n")
        elif registry_url is not None:
            registry_info = RemoteRegistryInfo(url=registry_url)
            console.print(f"[blue]Using remote registry: {registry_url}[/blue]\n")
        else:
            registry_info = None
            console.print("[blue]Using default Harbor registry[/blue]\n")

        client = RegistryClientFactory.create(registry_info)
        datasets = run_async(client.list_datasets())

        if not datasets:
            console.print("[yellow]No datasets found in registry[/yellow]")
            return

        table = Table(title="Available Datasets", show_lines=True)
        table.add_column("Name", style="cyan", no_wrap=True)
        table.add_column("Version", style="magenta")
        table.add_column("Tasks", style="green", justify="right")
        table.add_column("Description", style="white")

        total_tasks = 0
        sorted_datasets = sorted(datasets, key=lambda d: (d.name, d.version or ""))

        for dataset in sorted_datasets:
            total_tasks += dataset.task_count

            table.add_row(
                dataset.name,
                dataset.version or "",
                str(dataset.task_count),
                dataset.description,
            )

        console.print(table)
        console.print(
            f"\n[green]Total: {len(datasets)} dataset(s) with {total_tasks} task(s)[/green]"
        )

    except Exception as e:
        console.print(f"[red]Error: {e}[/red]")
        raise


@datasets_app.command()
def init(
    name: Annotated[str | None, Argument(help="Dataset name (org/name).")] = None,
    output_dir: Annotated[
        Path, Option("-o", "--output-dir", help="Output directory.")
    ] = Path("."),
    org: Annotated[str | None, Option("--org", help="Organization name.")] = None,
    description: Annotated[
        str | None, Option("--description", help="Description.")
    ] = None,
    with_metric: Annotated[
        bool, Option("--with-metric", help="Create metric.py template.")
    ] = False,
    author: Annotated[
        list[str] | None,
        Option(
            "--author",
            help="Author in 'Name <email>' or 'Name' format. Can be used multiple times.",
        ),
    ] = None,
):
    """Initialize a new dataset directory."""
    from harbor.cli.init import _init_dataset, _parse_authors, _resolve_name

    resolved_name = _resolve_name(name, org)
    _init_dataset(
        name=resolved_name,
        output_dir=output_dir,
        description=description,
        with_metric=with_metric,
        authors=_parse_authors(author),
    )


@datasets_app.command()
def download(
    dataset: Annotated[
        str,
        Argument(
            help="Dataset to download in format 'name@version' or 'name' (defaults to @head)"
        ),
    ],
    registry_url: Annotated[
        str | None,
        Option(
            "--registry-url",
            help="Registry URL for remote dataset",
            show_default="The default harbor registry.",
        ),
    ] = None,
    registry_path: Annotated[
        Path | None,
        Option(
            "--registry-path",
            help="Path to local registry",
            show_default=False,
        ),
    ] = None,
    output_dir: Annotated[
        Path | None,
        Option(
            "--output-dir",
            "-o",
            help="Directory to download tasks to",
            show_default="~/.cache/harbor/tasks",
        ),
    ] = None,
    overwrite: Annotated[
        bool,
        Option(
            "--overwrite",
            help="Overwrite cached tasks",
        ),
    ] = False,
):
    """Download a dataset from a registry.

    This command downloads all tasks in a dataset to the local cache.
    Tasks are fetched using shallow clones with sparse checkout for efficiency.

    Examples:
        harbor datasets download my-dataset
        harbor datasets download my-dataset@v1.0
        harbor datasets download my-dataset@head --overwrite
    """
    from harbor.cli.download import _download_dataset

    if "@" in dataset:
        name, version = dataset.split("@", 1)
    else:
        name = dataset
        version = None

    _download_dataset(
        name=name,
        version=version,
        overwrite=overwrite,
        output_dir=output_dir,
        registry_url=registry_url,
        registry_path=registry_path,
    )


@datasets_app.command()
def visibility(
    package: Annotated[
        str,
        Argument(help="Dataset package in 'org/name' format."),
    ],
    public: Annotated[
        bool, Option("--public", help="Set visibility to public.")
    ] = False,
    private: Annotated[
        bool, Option("--private", help="Set visibility to private.")
    ] = False,
    toggle: Annotated[
        bool, Option("--toggle", help="Toggle between public and private.")
    ] = False,
    cascade: Annotated[
        bool,
        Option(
            "--cascade",
            help="Cascade visibility to linked tasks (used with --toggle).",
        ),
    ] = False,
):
    """Get, set, or toggle the visibility of a published dataset."""
    from harbor.registry.db import RegistryDB

    flags = sum([public, private, toggle])
    if flags > 1:
        console.print(
            "[red]Error: --public, --private, and --toggle are mutually exclusive.[/red]"
        )
        raise SystemExit(1)

    if "/" not in package:
        console.print("[red]Error: package must be in 'org/name' format.[/red]")
        raise SystemExit(1)

    org, name = package.split("/", 1)

    if flags == 0:
        db = RegistryDB()
        current = run_async(db.get_package_visibility(org=org, name=name))
        if current is None:
            console.print(f"[red]Error: package '{package}' not found.[/red]")
            raise SystemExit(1)
        console.print(f"{package}: {current}")
        return

    vis: str | None = None
    do_cascade = cascade
    if public:
        vis = "public"
        db_for_check = RegistryDB()
        private_count = run_async(
            db_for_check.get_private_dataset_task_count(org=org, name=name)
        )
        if private_count > 0:
            answer = console.input(
                f'Setting dataset "{package}" to public will also make '
                f"{private_count} private task(s) public. Proceed? (y/N): "
            )
            if answer.strip().lower() != "y":
                console.print("Aborted.")
                return
        do_cascade = True
    elif toggle:
        db_for_check = RegistryDB()
        current = run_async(db_for_check.get_package_visibility(org=org, name=name))
        if current == "private":
            reset_client()
            private_count = run_async(
                db_for_check.get_private_dataset_task_count(org=org, name=name)
            )
            if private_count > 0:
                answer = console.input(
                    f'Toggling dataset "{package}" to public will also make '
                    f"{private_count} private task(s) public. Proceed? (y/N): "
                )
                if answer.strip().lower() != "y":
                    console.print("Aborted.")
                    return
            do_cascade = True
    elif private:
        vis = "private"

    async def _run() -> None:
        db = RegistryDB()
        try:
            await db.get_user_id()
        except RuntimeError as exc:
            console.print(f"[red]{exc}[/red]")
            raise SystemExit(1)

        result = await db.set_package_visibility(
            org=org,
            name=name,
            package_type="dataset",
            visibility=vis,
            toggle=toggle,
            cascade=do_cascade,
        )
        old = result.get("old_visibility", "unknown")
        new = result.get("new_visibility", "unknown")
        cascaded_packages = result.get("cascaded_packages", [])
        console.print(f"[green]Visibility changed: {old} → {new}[/green]")
        if cascaded_packages:
            console.print(
                f"[green]Also updated {len(cascaded_packages)} linked task(s) to {new}.[/green]"
            )

    reset_client()
    run_async(_run())
