"""CLI wrapper for graph workflows."""

from __future__ import annotations

from datetime import datetime
from pathlib import Path

import typer

from worai.config import resolve_config_path
from worai.core.graph import (
    GraphExportOptions,
    GraphSyncCreateOptions,
    GraphSyncOptions,
    PropertyDeleteOptions,
    run_graph_export,
    run_graph_sync,
    run_graph_sync_create,
    run_property_delete,
    validate_exported_graph,
)
from worai.core.profile_loader import resolve_profile_api_key
from worai.errors import UsageError

app = typer.Typer(add_completion=False, no_args_is_help=True)
sync_app = typer.Typer(add_completion=False, no_args_is_help=True)
property_app = typer.Typer(add_completion=False, no_args_is_help=True)


@sync_app.command(
    "run",
    no_args_is_help=True,
    help="Run graph sync for a configured profile.",
)
def run(
    ctx: typer.Context,
    profile: str = typer.Option(..., "--profile", help="Profile name in worai.toml."),
    debug: bool = typer.Option(
        False,
        "--debug",
        help="Write generated callback graphs to output/debug_cloud/<profile>/.",
    ),
) -> None:
    try:
        config_path = (ctx.obj or {}).get("config_path") if ctx else None
        run_graph_sync(
            GraphSyncOptions(
                profile=profile,
                config_path=config_path or resolve_config_path(None),
                debug=debug,
            )
        )
    except ValueError as exc:
        raise UsageError(str(exc)) from exc


@sync_app.command(
    "create",
    no_args_is_help=True,
    help="Create a graph sync project from a Copier template.",
)
def create(
    destination: Path | None = typer.Argument(
        None,
        metavar="DESTINATION",
        help="Output directory where the graph sync project will be created.",
    ),
    template: str = typer.Option(
        "gh:wordlift/graph-sync-template",
        "--template",
        help="Copier template source (Git URL, gh: shortcut, or local path).",
    ),
    defaults: bool = typer.Option(
        False,
        "--defaults",
        help="Use Copier default answers.",
    ),
    data_file: Path | None = typer.Option(
        None,
        "--data-file",
        help="Path to a Copier data file with pre-filled answers.",
    ),
    vcs_ref: str | None = typer.Option(
        None,
        "--vcs-ref",
        help="Template VCS ref (tag, branch, or commit).",
    ),
    non_interactive: bool = typer.Option(
        False,
        "--non-interactive",
        help="Disable prompts and fail if required answers are missing.",
    ),
    force: bool = typer.Option(
        False,
        "--force",
        help="Allow writing into existing destinations.",
    ),
) -> None:
    if destination is None:
        if non_interactive:
            raise UsageError("Destination path is required when using --non-interactive.")
        destination = Path(typer.prompt("Destination directory"))

    run_graph_sync_create(
        GraphSyncCreateOptions(
            destination=destination,
            template=template,
            defaults=defaults,
            data_file=data_file,
            vcs_ref=vcs_ref,
            non_interactive=non_interactive,
            force=force,
        )
    )


app.add_typer(sync_app, name="sync", help="Run graph sync workflows.")


def _default_export_output_path(profile: str) -> Path:
    day = datetime.now().strftime("%Y%m%d")
    sequence = 1
    while True:
        candidate = Path(f"export_{profile}_{day}_{sequence}.ttl")
        if not candidate.exists():
            return candidate
        sequence += 1


@app.command(
    "export",
    no_args_is_help=False,
    help="Export graph data from the WordLift /dataset/export endpoint.",
)
def export(
    ctx: typer.Context,
    output_file_name: str | None = typer.Argument(
        None,
        metavar="OUTPUT_FILE_NAME",
        help="Output file path. Extension controls export format (.ttl, .nt, .nq, .rdf, .xml, .jsonld, .json).",
    ),
    profile: str = typer.Option(
        "default",
        "--profile",
        help="Profile name in worai.toml. Defaults to 'default'.",
    ),
    endpoint: str = typer.Option(
        "https://api.wordlift.io/dataset/export",
        "--endpoint",
        help="WordLift export endpoint.",
    ),
    timeout: int = typer.Option(120, "--timeout", help="HTTP timeout in seconds."),
    validate: bool = typer.Option(
        False,
        "--validate",
        help="Run SHACL validation on the exported graph file.",
    ),
) -> None:
    try:
        api_key, resolved_profile, _ = resolve_profile_api_key(ctx, explicit_profile=profile)
    except ValueError as exc:
        raise UsageError(str(exc)) from exc
    if not api_key:
        raise UsageError(
            f"WORDLIFT_API_KEY is required (or set profiles.{resolved_profile}.api_key in config)."
        )

    output_path = Path(output_file_name) if output_file_name else _default_export_output_path(profile)
    try:
        exported = run_graph_export(
            GraphExportOptions(
                api_key=api_key,
                output_path=output_path,
                endpoint=endpoint,
                timeout=timeout,
            )
        )
    except ValueError as exc:
        raise UsageError(str(exc)) from exc
    except RuntimeError as exc:
        raise UsageError(str(exc)) from exc

    if validate:
        try:
            validate_exported_graph(exported)
        except RuntimeError as exc:
            raise UsageError(str(exc)) from exc
        typer.echo(f"Validated graph export: {exported}")

    typer.echo(f"Exported graph to {exported}")


@property_app.command(
    "delete",
    no_args_is_help=True,
    help="Delete one predicate from all matching entities in the graph.",
)
def property_delete(
    ctx: typer.Context,
    predicate: str = typer.Argument(..., help="Predicate IRI or CURIE (for example, seovoc:html)."),
    endpoint: str = typer.Option(
        "https://api.wordlift.io/graphql", "--endpoint", help="WordLift GraphQL endpoint."
    ),
    entities_endpoint: str = typer.Option(
        "https://api.wordlift.io/entities", "--entities-endpoint", help="WordLift entities API endpoint."
    ),
    dry_run: bool = typer.Option(False, "--dry-run", help="List matches without applying changes."),
    yes: bool = typer.Option(False, "--yes", help="Skip confirmation prompt."),
    workers: int = typer.Option(4, "--workers", help="Number of concurrent PATCH requests."),
    retries: int = typer.Option(3, "--retries", help="Retries per failed PATCH request."),
    rate_delay: float = typer.Option(0.0, "--rate-delay", help="Delay in seconds after each successful PATCH."),
    limit: int = typer.Option(0, "--limit", help="Limit number of matching entities to process."),
    timeout: int = typer.Option(60, "--timeout", help="HTTP timeout in seconds."),
) -> None:
    try:
        api_key, _, _ = resolve_profile_api_key(ctx)
    except ValueError as exc:
        raise UsageError(str(exc)) from exc
    if not api_key:
        raise UsageError("WORDLIFT_API_KEY is required (or set profiles.<name>.api_key in config).")

    options = PropertyDeleteOptions(
        api_key=api_key,
        predicate=predicate,
        graphql_endpoint=endpoint,
        entities_endpoint=entities_endpoint,
        dry_run=True,
        workers=workers,
        retries=retries,
        rate_delay=rate_delay,
        limit=limit,
        timeout=timeout,
    )
    try:
        summary = run_property_delete(options)
    except ValueError as exc:
        raise UsageError(str(exc)) from exc

    typer.echo(f"Predicate: {summary.predicate}")
    typer.echo(f"Matching entities: {summary.candidates}")
    if dry_run:
        return

    if summary.candidates == 0:
        return

    if not yes:
        typer.confirm(
            f"Delete predicate {summary.predicate} from {summary.candidates} entities?",
            abort=True,
        )

    execute_options = PropertyDeleteOptions(
        api_key=options.api_key,
        predicate=options.predicate,
        graphql_endpoint=options.graphql_endpoint,
        entities_endpoint=options.entities_endpoint,
        dry_run=False,
        workers=options.workers,
        retries=options.retries,
        rate_delay=options.rate_delay,
        limit=options.limit,
        timeout=options.timeout,
    )
    result = run_property_delete(execute_options)
    typer.echo(f"Attempted: {result.attempted}")
    typer.echo(f"Removed: {result.removed}")
    typer.echo(f"Failed: {result.failed}")
    if result.failed:
        raise UsageError("Some entities failed to patch. Re-run with lower --workers or higher --retries.")


app.add_typer(property_app, name="property", help="Bulk predicate operations on the graph.")
