"""GDB data tools — CRUD and batch operations for database records."""

from __future__ import annotations

from typing import Any

from fastmcp.exceptions import ToolError
from mcp.types import ToolAnnotations

from mcp_eregistrations_gdb.gdb_client import GDBClient, GDBClientError, translate_error
from mcp_eregistrations_gdb.server import mcp

MAX_BATCH_SIZE = 500


@mcp.tool(annotations=ToolAnnotations(readOnlyHint=True))
async def gdb_data_list(
    code: str,
    version: str = "any",
    page: int = 1,
    page_size: int = 100,
    search: str | None = None,
    ordering: str | None = None,
    filter: str | None = None,
    instance: str | None = None,
) -> dict[str, Any]:
    """List data records for a database by code and version.

    To get filtered results, first call gdb_data_filter to obtain a filter
    hash, then pass it here via the filter param.

    Args:
        code: Database code (e.g. "ISIC", "HS").
        version: Database version (default: "any" for latest).
        page: Page number (default: 1).
        page_size: Results per page (default: 100).
        search: Search term to filter results.
        ordering: Field name to order by (prefix with - for descending).
        filter: Filter hash from gdb_data_filter (SHA256 string).
        instance: Instance name (default: auto-select).

    Returns:
        dict with count, current_page, has_next, results (id, uuid, code,
        status, content). Use gdb_data_get for full record with documents.
    """
    try:
        async with GDBClient.for_instance(instance) as client:
            params: dict[str, Any] = {}
            if search is not None:
                params["search"] = search
            if ordering is not None:
                params["ordering"] = ordering
            if filter is not None:
                params["filter"] = filter
            data = await client.get_page(
                f"/api/v1/data/{code.lower()}/{version}",
                page=page,
                page_size=page_size,
                **params,
            )
    except GDBClientError as e:
        raise translate_error(e, resource_type="data", resource_id=code) from e

    data["results"] = [
        {
            "id": item.get("id"),
            "uuid": item.get("uuid"),
            "code": item.get("code"),
            "status": item.get("status"),
            "content": item.get("content"),
        }
        for item in data.get("results", [])
    ]
    return data


@mcp.tool(annotations=ToolAnnotations(readOnlyHint=True))
async def gdb_data_get(
    data_id: int,
    instance: str | None = None,
) -> dict[str, Any]:
    """Get a single data record with full content and documents.

    Args:
        data_id: Data record ID (integer).
        instance: Instance name (default: auto-select).

    Returns:
        dict with id, content, documents, uuid, etc.
    """
    try:
        async with GDBClient.for_instance(instance) as client:
            result: dict[str, Any] = await client.get(f"/api/v1/data-detail/{data_id}")
            return result
    except GDBClientError as e:
        raise translate_error(e, resource_type="data", resource_id=data_id) from e


@mcp.tool(annotations=ToolAnnotations(readOnlyHint=True))
async def gdb_data_search_field(
    code: str,
    q: str,
    version: str = "any",
    field: str | None = None,
    field_id: str | None = None,
    limit: int = 10,
    instance: str | None = None,
) -> dict[str, Any]:
    """Autocomplete search within a database field.

    Args:
        code: Database code (e.g. "ISIC", "HS").
        q: Search query string.
        version: Database version (default: "any").
        field: Field name to search in.
        field_id: Field ID to search in.
        limit: Max results to return (default: 10).
        instance: Instance name (default: auto-select).

    Returns:
        dict with results list.
    """
    try:
        async with GDBClient.for_instance(instance) as client:
            params: dict[str, Any] = {"q": q, "limit": limit}
            if field is not None:
                params["field"] = field
            if field_id is not None:
                params["field_id"] = field_id
            data = await client.get(
                f"/api/v1/data/{code.lower()}/{version}/search-field",
                **params,
            )
            if isinstance(data, list):
                return {"results": data}
            result: dict[str, Any] = data
            return result
    except GDBClientError as e:
        raise translate_error(e, resource_type="data", resource_id=code) from e


@mcp.tool(annotations=ToolAnnotations(readOnlyHint=True))
async def gdb_data_filter(
    database_id: int,
    filter_data: list[dict[str, Any]],
    instance: str | None = None,
) -> dict[str, Any]:
    """Create a data filter and get its hash for use with gdb_data_list.

    Two-step workflow: (1) call this tool to get a filter hash,
    (2) pass the hash to gdb_data_list(filter=hash) to get filtered results.

    Args:
        database_id: Database ID (integer).
        filter_data: List of filter objects (each requires at least a 'gate' field).
        instance: Instance name (default: auto-select).

    Returns:
        dict with hash (SHA256 string to pass to gdb_data_list filter param).
    """
    try:
        async with GDBClient.for_instance(instance) as client:
            result: dict[str, Any] = await client.post(
                f"/api/v1/database/{database_id}/data-filter",
                data=filter_data,
            )
            return result
    except GDBClientError as e:
        raise translate_error(e, resource_type="data", resource_id=database_id) from e


@mcp.tool()
async def gdb_data_create(
    database_id: int,
    content: dict[str, Any],
    status: str = "",
    registry_number: str | None = None,
    instance: str | None = None,
) -> dict[str, Any]:
    """Create a data record in a GDB database.

    Args:
        database_id: Database ID (integer, from gdb_database_get).
        content: Record data (field values matching database schema).
        status: Record status (default: empty string).
        registry_number: Registry number (auto-generated if omitted).
        instance: Instance name (default: auto-select).

    Returns:
        dict with id, database_index_increment, database_catalog_index_increment.
    """
    payload: dict[str, Any] = {
        "database_id": database_id,
        "content": content,
        "status": status,
    }
    if registry_number is not None:
        payload["registry_number"] = registry_number
    try:
        async with GDBClient.for_instance(instance) as client:
            result: dict[str, Any] = await client.post("/api/v1/data", data=payload)
            return result
    except GDBClientError as e:
        raise translate_error(e, resource_type="data", resource_id=database_id) from e


@mcp.tool()
async def gdb_data_update(
    data_id: int,
    content: dict[str, Any],
    instance: str | None = None,
) -> dict[str, Any]:
    """Update a data record's content (MERGE, not replace).

    Content is MERGED with existing: only provided fields are updated,
    untouched fields are preserved. To clear a field, set it to null.

    Args:
        data_id: Data record ID (integer).
        content: Fields to update (merged into existing content).
        instance: Instance name (default: auto-select).

    Returns:
        dict with id, database_index_increment.
    """
    payload: dict[str, Any] = {"id": data_id, "content": content}
    try:
        async with GDBClient.for_instance(instance) as client:
            result: dict[str, Any] = await client.put("/api/v1/data", data=payload)
            return result
    except GDBClientError as e:
        raise translate_error(e, resource_type="data", resource_id=data_id) from e


@mcp.tool()
async def gdb_data_delete(
    data_id: int,
    instance: str | None = None,
) -> dict[str, Any]:
    """Delete a single data record.

    Args:
        data_id: Data record ID (integer).
        instance: Instance name (default: auto-select).

    Returns:
        dict with deleted confirmation.
    """
    try:
        async with GDBClient.for_instance(instance) as client:
            result = await client.delete(f"/api/v1/data/{data_id}")
            return {"deleted": True, "data_id": data_id, "detail": result}
    except GDBClientError as e:
        raise translate_error(e, resource_type="data", resource_id=data_id) from e


@mcp.tool()
async def gdb_data_delete_batch(
    database_id: int,
    ids: list[int],
    instance: str | None = None,
) -> dict[str, Any]:
    """Delete multiple data records from a database.

    Args:
        database_id: Database ID (integer).
        ids: List of data record IDs to delete.
        instance: Instance name (default: auto-select).

    Returns:
        dict with deleted confirmation and count.
    """
    try:
        async with GDBClient.for_instance(instance) as client:
            # Build URL with raw query string — httpx percent-encodes commas
            # in params= which breaks the backend's ids.split(',') parsing
            ids_csv = ",".join(str(i) for i in ids)
            result = await client.delete(
                f"/api/v1/database/{database_id}/data/delete?ids={ids_csv}",
            )
            return {
                "deleted": True,
                "database_id": database_id,
                "deleted_count": len(ids),
                "deleted_ids": ids,
                "detail": result,
            }
    except GDBClientError as e:
        raise translate_error(e, resource_type="data", resource_id=database_id) from e


# ---------- Registry API batch tools ----------


@mcp.tool()
async def gdb_data_create_batch(
    code: str,
    records: list[dict[str, Any]],
    version: str,
    instance: str | None = None,
) -> dict[str, Any]:
    """Create multiple data records in a single request.

    Uses the Registry API (BOT-facing). Max 500 records per call.

    Args:
        code: Database code (e.g. "ISIC", "HS").
        records: List of content dicts (field values matching database schema).
        version: Database version (numeric, e.g. '1.0').
        instance: Instance name (default: auto-select).

    Returns:
        dict with created_count and receive (list of created records).
    """
    if len(records) > MAX_BATCH_SIZE:
        raise ToolError(
            f"Batch size {len(records)} exceeds maximum of {MAX_BATCH_SIZE}."
        )
    payload = {"write": [{"content": rec} for rec in records]}
    try:
        async with GDBClient.for_instance(instance) as client:
            result: dict[str, Any] = await client.post(
                f"/data/{code.lower()}/{version}/create-entries",
                data=payload,
            )
            receive = result.get("receive", [])
            return {"created_count": len(receive), "receive": receive}
    except GDBClientError as e:
        raise translate_error(e, resource_type="data", resource_id=code) from e


@mcp.tool()
async def gdb_data_update_batch(
    code: str,
    queries: list[dict[str, Any]],
    records: list[dict[str, Any]],
    version: str,
    instance: str | None = None,
) -> dict[str, Any]:
    """Update multiple data records matched by query in a single request.

    Uses the Registry API (BOT-facing). Max 500 records per call.
    queries[i] maps to records[i] — lists must be the same length.

    Args:
        code: Database code (e.g. "ISIC", "HS").
        queries: List of query dicts, one per record.
        records: List of content dicts with updated field values.
        version: Database version (numeric, e.g. '1.0').
        instance: Instance name (default: auto-select).

    Returns:
        dict with updated (bool) and updated_count.
    """
    if len(records) > MAX_BATCH_SIZE:
        raise ToolError(
            f"Batch size {len(records)} exceeds maximum of {MAX_BATCH_SIZE}."
        )
    if len(queries) != len(records):
        raise ToolError(
            f"queries length ({len(queries)}) must equal "
            f"records length ({len(records)})."
        )
    payload = {
        "query": queries,
        "write": [{"content": rec} for rec in records],
    }
    try:
        async with GDBClient.for_instance(instance) as client:
            await client.put(
                f"/data/{code.lower()}/{version}/update-entries",
                data=payload,
            )
            return {"updated": True, "updated_count": len(records)}
    except GDBClientError as e:
        raise translate_error(e, resource_type="data", resource_id=code) from e


@mcp.tool()
async def gdb_data_upsert(
    code: str,
    content: dict[str, Any],
    version: str,
    query: dict[str, Any] | None = None,
    instance: str | None = None,
) -> dict[str, Any]:
    """Create or update a single data record (upsert).

    Uses the Registry API (BOT-facing). Matches by query first, then
    by primary key from content. Returns 409 on multiple matches.

    Args:
        code: Database code (e.g. "ISIC", "HS").
        content: Record data (field values matching database schema).
        version: Database version (numeric, e.g. '1.0').
        query: Optional query dict to match existing record.
        instance: Instance name (default: auto-select).

    Returns:
        dict with receive (created/updated record).
    """
    payload: dict[str, Any] = {"write": {"content": content}}
    if query is not None:
        payload["query"] = query
    try:
        async with GDBClient.for_instance(instance) as client:
            result: dict[str, Any] = await client.post(
                f"/data/{code.lower()}/{version}/update-or-create",
                data=payload,
            )
            return result
    except GDBClientError as e:
        raise translate_error(e, resource_type="data", resource_id=code) from e


@mcp.tool()
async def gdb_data_upsert_batch(
    code: str,
    records: list[dict[str, Any]],
    version: str,
    queries: list[dict[str, Any]] | None = None,
    instance: str | None = None,
) -> dict[str, Any]:
    """Create or update multiple data records in a single request (batch upsert).

    Uses the Registry API (BOT-facing). Max 500 records per call.
    If queries is provided, queries[i] maps to records[i].

    Args:
        code: Database code (e.g. "ISIC", "HS").
        records: List of content dicts (field values matching database schema).
        version: Database version (numeric, e.g. '1.0').
        queries: Optional list of query dicts, one per record.
        instance: Instance name (default: auto-select).

    Returns:
        dict with receive (list of created/updated records).
    """
    if len(records) > MAX_BATCH_SIZE:
        raise ToolError(
            f"Batch size {len(records)} exceeds maximum of {MAX_BATCH_SIZE}."
        )
    if queries is not None and len(queries) != len(records):
        raise ToolError(
            f"queries length ({len(queries)}) must equal "
            f"records length ({len(records)})."
        )
    payload: dict[str, Any] = {
        "write": [{"content": rec} for rec in records],
    }
    if queries is not None:
        payload["query"] = queries
    try:
        async with GDBClient.for_instance(instance) as client:
            result: dict[str, Any] = await client.post(
                f"/data/{code.lower()}/{version}/update-or-create-entries",
                data=payload,
            )
            return result
    except GDBClientError as e:
        raise translate_error(e, resource_type="data", resource_id=code) from e
