"""Knowledge documents bulk metadata update command implementation."""

import json
from typing import Any, Optional, cast

import typer

from cli.commands.knowledge.documents.bulk_metadata.presenter import (
    present_bulk_metadata_result,
)
from cli.infrastructure.command import async_command, authenticated_client
from cli.infrastructure.file_content import resolve_content_or_raise
from alloy_runtime_types.dtos.knowledge import BulkUpdateDocumentMetadataRequest


def knowledge_documents_bulk_metadata_command(
    filter_: str = typer.Option(
        ...,
        "--filter",
        help="JSONB containment filter to match documents (JSON or @file.json)",
    ),
    metadata: str = typer.Option(
        ...,
        "--metadata",
        help="Metadata fields to apply (JSON or @file.json)",
    ),
    metadata_mode: str = typer.Option(
        "merge",
        "--metadata-mode",
        help="Metadata update mode: 'merge' (default) or 'replace'",
    ),
    collection: Optional[str] = typer.Option(
        None,
        "-C",
        "--collection",
        help="Optional collection name or UUID to scope the update",
    ),
    dry_run: bool = typer.Option(
        False,
        "--dry-run",
        help="Preview matched count without updating",
    ),
) -> None:
    """Bulk-update metadata across documents matching a filter.

    Both --filter and --metadata accept a JSON string or @file reference.
    Use --dry-run to preview how many documents match without modifying them.

    Examples:
        alloy knowledge documents bulk-metadata \\
            --filter '{"source": "slack"}' \\
            --metadata '{"reviewed": true}'

        alloy knowledge documents bulk-metadata \\
            --filter @filter.json --metadata @patch.json --dry-run

        alloy knowledge documents bulk-metadata \\
            --filter '{"team": "eng"}' --metadata '{"archived": true}' \\
            -C <collection-uuid>
    """
    _execute_bulk_metadata(
        filter_raw=filter_,
        metadata_raw=metadata,
        metadata_mode=metadata_mode,
        collection=collection,
        dry_run=dry_run,
    )


def _parse_json_option(value: str, option_name: str) -> dict[str, Any]:
    resolved = resolve_content_or_raise(value)
    if resolved is None:
        raise typer.BadParameter(f"{option_name} is required")

    try:
        result = json.loads(resolved)
    except json.JSONDecodeError as e:
        raise typer.BadParameter(f"Invalid JSON for {option_name}: {e}")
    if not isinstance(result, dict):
        raise typer.BadParameter(f"{option_name} must be a JSON object")
    return cast(dict[str, Any], result)


@async_command
async def _execute_bulk_metadata(
    filter_raw: str,
    metadata_raw: str,
    metadata_mode: str,
    collection: Optional[str],
    dry_run: bool,
) -> None:
    """Execute bulk document metadata update operation."""

    if metadata_mode not in ("merge", "replace"):
        raise typer.BadParameter(
            f"Invalid metadata mode '{metadata_mode}'. Must be 'merge' or 'replace'."
        )

    filter_dict = _parse_json_option(filter_raw, "--filter")
    metadata_dict = _parse_json_option(metadata_raw, "--metadata")

    request = BulkUpdateDocumentMetadataRequest(
        filter=filter_dict,
        patch=metadata_dict,
        metadata_mode=metadata_mode,  # type: ignore[arg-type]
        collection_id=collection,
        dry_run=dry_run,
    )

    async with authenticated_client() as (_config, client):
        response = await client.bulk_update_document_metadata(request)

    present_bulk_metadata_result(response)
