"""CLI for structured data generation."""

from __future__ import annotations

import json
import os
from pathlib import Path

import typer

from worai.core.profile_loader import load_profile, resolve_profile_api_key
from wordlift_sdk.render import RenderOptions
try:
    from wordlift_sdk.validation import validate_jsonld_from_url
except ImportError:  # pragma: no cover - depends on installed wordlift-sdk version
    def validate_jsonld_from_url(*_args, **_kwargs):
        raise RuntimeError("validate_jsonld_from_url is unavailable in this wordlift-sdk version")
from worai.structured_data import (
    CreateRequest,
    CreateWorkflow,
    GenerateRequest,
    GenerateWorkflow,
)
from worai.commands.validate import _emit_validation_result
from worai.core.structured_data_inventory import (
    DEFAULT_BASE_URL as INVENTORY_DEFAULT_BASE_URL,
    InventoryOptions,
    run_inventory,
)
from worai.errors import UsageError

app = typer.Typer(add_completion=False, no_args_is_help=True)
validate_app = typer.Typer(add_completion=False, no_args_is_help=True)
DEFAULT_BASE_URL = "https://api.wordlift.io"


def _resolve_value(
    ctx: typer.Context,
    value: str | None,
    path: str,
    env_key: str | None,
) -> str | None:
    if value:
        return value
    if env_key and env_key in os.environ:
        return os.environ[env_key]
    try:
        profile, _, _ = load_profile(ctx)
    except ValueError:
        return None
    profile_settings = getattr(profile, "settings", {}) or {}
    underscore_key = path.replace(".", "_")
    if underscore_key in profile_settings:
        return profile_settings[underscore_key]
    if path in profile_settings:
        return profile_settings[path]
    return None


def _resolve_bool_value(
    ctx: typer.Context,
    value: bool | None,
    path: str,
    env_key: str | None = None,
) -> bool | None:
    if value is not None:
        return value
    raw = _resolve_value(ctx, None, path, env_key)
    if raw is None:
        return None
    if isinstance(raw, bool):
        return raw
    token = str(raw).strip().lower()
    if token in {"1", "true", "yes", "on"}:
        return True
    if token in {"0", "false", "no", "off"}:
        return False
    raise UsageError(f"Invalid boolean value for {path}: {raw}")


@validate_app.command(
    "page",
    no_args_is_help=True,
    help="Validate JSON-LD extracted from a webpage.",
)
def validate_page(
    url: str = typer.Argument(..., help="Webpage URL to extract JSON-LD from."),
    shape: list[str] | None = typer.Option(
        None,
        "--shape",
        "-s",
        help="Shape path or packaged shape name (default: all packaged shapes).",
        show_default=False,
    ),
    report_file: str | None = typer.Option(
        None,
        "--report-file",
        help="Write the SHACL report text to this file.",
    ),
    format: str = typer.Option(
        "pretty",
        "--format",
        help="Output format: pretty or raw.",
        show_default=True,
    ),
    color: bool = typer.Option(
        True,
        "--color/--no-color",
        help="Colorize pretty output.",
        show_default=True,
    ),
    list_shapes: bool = typer.Option(
        False,
        "--list-shapes",
        help="List available packaged shapes and exit.",
    ),
    headed: bool = typer.Option(False, "--headed", help="Run the browser with a visible UI."),
    timeout_ms: int = typer.Option(30000, "--timeout-ms", help="Timeout (ms) for page loads."),
    wait_until: str = typer.Option(
        "networkidle",
        "--wait-until",
        help="Playwright wait strategy.",
    ),
    ignore_https_errors: bool = typer.Option(
        False,
        "--ignore-https-errors",
        help="Ignore HTTPS errors when rendering the page.",
    ),
) -> None:
    if list_shapes:
        from wordlift_sdk.validation import list_shape_names

        for name in list_shape_names():
            typer.echo(name)
        return

    options = RenderOptions(
        url=url,
        headless=not headed,
        timeout_ms=timeout_ms,
        wait_until=wait_until,
        ignore_https_errors=ignore_https_errors,
    )
    try:
        result = validate_jsonld_from_url(url, shape_specs=shape, render_options=options)
    except Exception as exc:
        raise UsageError(
            "Failed to validate JSON-LD from webpage. "
            "Ensure the page renders and contains JSON-LD."
        ) from exc

    _emit_validation_result(result, format=format, color=color, report_file=report_file)


@app.command("create", help="Create YARRRML and JSON-LD for a webpage.")
def create(
    ctx: typer.Context,
    url: str = typer.Argument(..., help="Target page URL."),
    target_type_arg: str | None = typer.Argument(
        None, help="Schema.org type to generate (e.g., Review)."
    ),
    target_type: str | None = typer.Option(
        None, "--type", help="Schema.org type to generate (e.g., Review)."
    ),
    output_dir: Path = typer.Option(Path("."), "--output-dir", help="Output directory."),
    base_name: str = typer.Option("structured-data", "--base-name", help="Base output filename."),
    jsonld_path: Path | None = typer.Option(
        None, "--jsonld", help="Write JSON-LD to this file path."
    ),
    yarrml_path: Path | None = typer.Option(
        None, "--yarrml", help="Write YARRRML to this file path."
    ),
    debug: bool = typer.Option(False, "--debug", help="Write agent prompt/response to disk."),
    headed: bool = typer.Option(False, "--headed", help="Run the browser with a visible UI."),
    timeout_ms: int = typer.Option(30000, "--timeout-ms", help="Timeout (ms) for page loads."),
    max_retries: int = typer.Option(
        2, "--max-retries", help="Max retries for agent refinement when required props are missing."
    ),
    quality_check: bool = typer.Option(
        True,
        "--quality-check/--no-quality-check",
        help="Use agent-based quality scoring to decide retries.",
    ),
    max_xhtml_chars: int = typer.Option(
        40000, "--max-xhtml-chars", help="Max characters to keep in cleaned XHTML."
    ),
    max_text_node_chars: int = typer.Option(
        400, "--max-text-node-chars", help="Max characters per text node in cleaned XHTML."
    ),
    max_nesting_depth: int = typer.Option(
        2, "--max-nesting-depth", help="Max depth for related types in the schema guide."
    ),
    verbose: bool = typer.Option(
        True, "--verbose/--no-verbose", help="Emit progress logs to stderr."
    ),
    validate: bool = typer.Option(
        False, "--validate", help="Validate JSON-LD output with SHACL shapes."
    ),
    wait_until: str = typer.Option(
        "networkidle",
        "--wait-until",
        help="Playwright wait strategy.",
    ),
) -> None:
    if target_type is None:
        target_type = target_type_arg
    if not target_type:
        raise UsageError("Schema.org type is required. Pass it as an argument or via --type.")
    try:
        api_key, _, _ = resolve_profile_api_key(ctx)
    except ValueError as exc:
        raise UsageError(str(exc)) from exc

    def log(message: str) -> None:
        if verbose:
            typer.echo(message, err=True)

    request = CreateRequest(
        url=url,
        target_type=target_type,
        output_dir=output_dir,
        base_name=base_name,
        jsonld_path=jsonld_path,
        yarrml_path=yarrml_path,
        api_key=api_key,
        base_url=DEFAULT_BASE_URL,
        debug=debug,
        headed=headed,
        timeout_ms=timeout_ms,
        max_retries=max_retries,
        quality_check=quality_check,
        max_xhtml_chars=max_xhtml_chars,
        max_text_node_chars=max_text_node_chars,
        max_nesting_depth=max_nesting_depth,
        verbose=verbose,
        validate=validate,
        wait_until=wait_until,
    )
    result = CreateWorkflow().run(request, log)
    typer.echo(json.dumps(result.__dict__, indent=2))


@app.command("generate", help="Generate RDF from YARRRML over URL inputs.")
def generate(
    ctx: typer.Context,
    input_value: str = typer.Argument(..., metavar="INPUT", help="Sitemap URL/path or page URL."),
    yarrrml_path: Path = typer.Option(..., "--yarrrml", help="Path to YARRRML mapping file."),
    regex: str = typer.Option(".*", "--regex", help="Regex to filter URLs (matches full URL)."),
    output_dir: Path = typer.Option(Path("."), "--output-dir", help="Output directory."),
    output_format: str = typer.Option(
        "ttl", "--format", help="Output format: ttl, jsonld, rdf, nt, nq."
    ),
    concurrency: str = typer.Option(
        "auto", "--concurrency", help="Worker count or 'auto' to adapt to responses."
    ),
    headed: bool = typer.Option(False, "--headed", help="Run the browser with a visible UI."),
    timeout_ms: int = typer.Option(30000, "--timeout-ms", help="Timeout (ms) for page loads."),
    wait_until: str = typer.Option("networkidle", "--wait-until", help="Playwright wait strategy."),
    max_xhtml_chars: int = typer.Option(
        40000, "--max-xhtml-chars", help="Max characters to keep in cleaned XHTML."
    ),
    max_text_node_chars: int = typer.Option(
        400, "--max-text-node-chars", help="Max characters per text node in cleaned XHTML."
    ),
    max_pages: int | None = typer.Option(None, "--max-pages", help="Max pages to process."),
    verbose: bool = typer.Option(True, "--verbose/--no-verbose", help="Emit progress logs to stderr."),
) -> None:
    try:
        api_key, _, _ = resolve_profile_api_key(ctx)
    except ValueError as exc:
        raise UsageError(str(exc)) from exc

    def log(message: str) -> None:
        if verbose:
            typer.echo(message, err=True)

    request = GenerateRequest(
        input_value=input_value,
        yarrrml_path=yarrrml_path,
        regex=regex,
        output_dir=output_dir,
        output_format=output_format,
        concurrency=concurrency,
        api_key=api_key,
        base_url=DEFAULT_BASE_URL,
        headed=headed,
        timeout_ms=timeout_ms,
        wait_until=wait_until,
        max_xhtml_chars=max_xhtml_chars,
        max_text_node_chars=max_text_node_chars,
        max_pages=max_pages,
        verbose=verbose,
    )
    summary = GenerateWorkflow().run(request, log)
    typer.echo(json.dumps(summary, indent=2))


@app.command("inventory", help="Create a structured-data inventory from discovered URLs.")
def inventory(
    ctx: typer.Context,
    source: str = typer.Argument(
        ...,
        help="Sitemap URL/path, local URL list file, or Google Spreadsheet URL/ID.",
    ),
    sheet_name: str | None = typer.Option(
        None,
        "--sheet-name",
        help="Source sheet tab name when SOURCE is a Google Spreadsheet.",
    ),
    source_type: str | None = typer.Option(
        None,
        "--source-type",
        help="Optional source parser override (e.g., debug-cloud).",
    ),
    ingest_source: str | None = typer.Option(
        None,
        "--ingest-source",
        help="SDK ingestion source axis: auto|urls|sitemap|sheets|local.",
    ),
    ingest_loader: str | None = typer.Option(
        None,
        "--ingest-loader",
        help=(
            "SDK ingestion loader axis: "
            "auto|simple|proxy|playwright|premium_scraper|web_scrape_api|passthrough."
        ),
    ),
    ingest_passthrough_when_html: bool | None = typer.Option(
        None,
        "--ingest-passthrough-when-html/--no-ingest-passthrough-when-html",
        help="Prefer passthrough when source records include embedded HTML.",
    ),
    output: str | None = typer.Option(None, "--output", help="Write inventory to CSV."),
    destination_sheet_id: str | None = typer.Option(
        None,
        "--destination-sheet-id",
        help="Google spreadsheet ID where inventory should be written.",
    ),
    destination_sheet_name: str | None = typer.Option(
        None,
        "--destination-sheet-name",
        help="Destination sheet tab name to write inventory into.",
    ),
    client_secrets: str | None = typer.Option(
        None,
        "--client-secrets",
        help="OAuth2 client secrets JSON for Google Sheets (if re-auth is needed).",
    ),
    token: str | None = typer.Option(
        None,
        "--token",
        help="Path to OAuth token JSON used for Google APIs.",
    ),
    port: int = typer.Option(8080, "--port", help="Local redirect port for OAuth flow."),
    timeout: float = typer.Option(30.0, "--timeout", help="HTTP timeout for sitemap/page requests."),
    concurrency: str = typer.Option(
        "auto",
        "--concurrency",
        help="Worker count or 'auto' to adapt to responses.",
    ),
) -> 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).")

    resolved_client_secrets = _resolve_value(
        ctx, client_secrets, "gsc.client_secrets", "GSC_CLIENT_SECRETS"
    )
    resolved_token = (
        _resolve_value(ctx, token, "oauth.token", "OAUTH_TOKEN")
        or _resolve_value(ctx, token, "gsc.token", "GSC_TOKEN")
        or _resolve_value(ctx, token, "ga.token", "GA_TOKEN")
        or "oauth_token.json"
    )
    resolved_ingest_source = _resolve_value(ctx, ingest_source, "ingest.source", None)
    resolved_ingest_loader = _resolve_value(ctx, ingest_loader, "ingest.loader", None)
    resolved_passthrough_when_html = _resolve_bool_value(
        ctx,
        ingest_passthrough_when_html,
        "ingest.passthrough_when_html",
        None,
    )
    options = InventoryOptions(
        source=source,
        api_key=api_key,
        base_url=INVENTORY_DEFAULT_BASE_URL,
        timeout=timeout,
        source_sheet_name=sheet_name,
        output=output,
        destination_spreadsheet_id=destination_sheet_id,
        destination_sheet_name=destination_sheet_name,
        client_secrets=resolved_client_secrets,
        token=resolved_token,
        port=port,
        concurrency=concurrency,
        ingest_source=resolved_ingest_source,
        ingest_loader=resolved_ingest_loader,
        ingest_passthrough_when_html=resolved_passthrough_when_html,
        source_type=source_type,
    )
    rows = run_inventory(options)
    typer.echo(
        json.dumps(
            {
                "total": len(rows),
                "output": output,
                "destination_sheet_id": destination_sheet_id,
                "destination_sheet_name": destination_sheet_name,
                "ingest_source": resolved_ingest_source,
                "ingest_loader": resolved_ingest_loader,
                "ingest_passthrough_when_html": resolved_passthrough_when_html,
                "source_type": source_type,
            },
            indent=2,
        )
    )


app.add_typer(validate_app, name="validate", help="Validate JSON-LD extracted from webpages.")
