"""Metadata tools: inspect dataset and resource details before querying.

These four tools answer "what is in this dataset and how is it shaped?"
before the model commits to a download or writes a SQL query.

Why separate tools instead of folding into get_dataset_info?

- ``get_dataset_info`` — full dataset metadata: description, tags, license,
  maintainer, all resources with URLs. High token cost. Use when the model
  needs to understand what a dataset is before acting on it.
- ``list_resources`` — concise resource listing with download URLs and
  data-range fields only. Use when ``get_dataset_info`` has already been
  called and the model just needs to pick a specific resource file to
  download. Avoids re-emitting the full metadata blob.
- ``get_resource_schema`` — column names, types, and sample values via the
  CKAN datastore API (no download required). Critical for writing correct
  SQL — column names and types vary unpredictably across resources in the
  same dataset (e.g. "TotalEV" vs "Total EV"). Cannot be folded into
  get_dataset_info because it requires a separate datastore API call.
- ``compare_datasets`` — side-by-side diff of two or more datasets.
  Useful for cross-portal comparisons before deciding which to download.
  Resolves multiple dataset IDs concurrently; separate tool avoids
  making get_dataset_info's signature handle lists.

Consolidation considered: list_resources could theoretically be removed
since get_dataset_info includes resources. Kept because get_dataset_info
returns substantially more tokens (description, maintainer, tags, license)
that are noise when the model already knows what dataset it has and just
needs to pick a file. The token cost difference is meaningful on large datasets
with many resources.
"""
from __future__ import annotations

from fastmcp import Context
from fastmcp.tools.tool import ToolResult

from ontario_data.formatting import md_response
from ontario_data.server import READONLY, mcp
from ontario_data.utils import (
    get_lifespan_state,
    arcgis_guard,
    fan_out,
    get_deps,
    is_arcgis_portal,
    parse_portal_id,
    resolve_dataset,
    unwrap_first_match,
)

_RESOURCE_SCHEMA_OUTPUT_SCHEMA = {
    "type": "object",
    "properties": {
        "resource_id": {"type": "string"},
        "total_records": {"type": "integer"},
        "num_columns": {"type": "integer"},
        "fields": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "name": {"type": "string"},
                    "type": {"type": "string"},
                    "sample_values": {
                        "type": "array",
                        "items": {"type": "string"},
                    },
                },
                "required": ["name", "type"],
            },
        },
        "datastore_active": {"type": "boolean"},
        "hint": {"type": "string"},
    },
    "required": ["resource_id"],
}


@mcp.tool(annotations=READONLY)
async def get_dataset_info(
    dataset_id: str,
    ctx: Context = None,
) -> str:
    """Get full metadata for a dataset including all resources.

    Args:
        dataset_id: Prefixed dataset ID (e.g. "toronto:ttc-ridership") or bare ID
    """
    portal, bare_id, ds = await resolve_dataset(ctx, dataset_id)
    _, cache = get_deps(ctx, portal)
    if not cache.get_dataset_metadata(ds["id"]):
        cache.store_dataset_metadata(ds["id"], ds)

    resources = []
    for r in ds.get("resources", []):
        resources.append({
            "id": r["id"],
            "name": r.get("name"),
            "format": r.get("format"),
            "size_bytes": r.get("size"),
            "url": r.get("url"),
            "last_modified": r.get("last_modified") or r.get("data_last_updated"),
            "datastore_active": r.get("datastore_active", False),
        })

    return md_response(
        id=f"{portal}:{ds['id']}",
        name=ds.get("name"),
        title=ds.get("title"),
        description=ds.get("notes"),
        organization=ds.get("organization", {}).get("title"),
        maintainer=ds.get("maintainer_translated", {}).get("en") or ds.get("maintainer"),
        license=ds.get("license_title"),
        tags=[t["name"] for t in ds.get("tags", [])],
        update_frequency=ds.get("update_frequency"),
        created=ds.get("metadata_created"),
        last_modified=ds.get("metadata_modified"),
        access_level=ds.get("access_level"),
        geographic_coverage=ds.get("geographic_coverage"),
        resources=resources,
    )


@mcp.tool(annotations=READONLY)
async def list_resources(
    dataset_id: str,
    ctx: Context = None,
) -> str:
    """List all resources (files) in a dataset with their formats and sizes.

    Args:
        dataset_id: Prefixed dataset ID (e.g. "toronto:ttc-ridership") or bare ID
    """
    portal, bare_id, ds = await resolve_dataset(ctx, dataset_id)

    resources = []
    for r in ds.get("resources", []):
        resources.append({
            "id": r["id"],
            "name": r.get("name"),
            "format": r.get("format"),
            "size_bytes": r.get("size"),
            "url": r.get("url"),
            "last_modified": r.get("last_modified") or r.get("data_last_updated"),
            "datastore_active": r.get("datastore_active", False),
            "data_range": f"{r.get('data_range_start', '?')} to {r.get('data_range_end', '?')}",
        })
    return md_response(
        dataset=ds.get("title"),
        dataset_id=f"{portal}:{ds['id']}",
        num_resources=len(resources),
        resources=resources,
    )


@mcp.tool(annotations=READONLY, output_schema=_RESOURCE_SCHEMA_OUTPUT_SCHEMA)
async def get_resource_schema(
    resource_id: str,
    sample_size: int = 5,
    ctx: Context = None,
) -> ToolResult:
    """Get the column schema and sample values for a datastore resource.

    Args:
        resource_id: Prefixed resource ID (e.g. "toronto:abc123") or bare ID
        sample_size: Number of sample rows to include
    """
    configs = get_lifespan_state(ctx)["portal_configs"]
    portal, bare_id = parse_portal_id(resource_id, set(configs.keys()))

    if portal and is_arcgis_portal(ctx, portal):
        return arcgis_guard(
            resource_id,
            alternative=f"download_resource(resource_id='{resource_id}') to cache it, then query_cached to inspect columns",
        )

    async def _schema(pk: str):
        ckan, _ = get_deps(ctx, pk)
        resource = await ckan.resource_show(bare_id)
        if not resource.get("datastore_active"):
            fmt = (resource.get("format") or "unknown").upper()
            return {
                "resource_id": bare_id,
                "datastore_active": False,
                "format": fmt,
                "hint": f"This {fmt} resource has no datastore. Use download_resource to cache it locally, then query_cached to analyze.",
            }
        result = await ckan.datastore_search(bare_id, limit=sample_size)
        fields = []
        for f in result.get("fields", []):
            if f["id"].startswith("_"):
                continue
            sample_values = [str(r.get(f["id"], "")) for r in result.get("records", [])]
            fields.append({
                "name": f["id"],
                "type": f.get("type", "unknown"),
                "sample_values": sample_values[:sample_size],
            })
        return {
            "resource_id": bare_id,
            "total_records": result.get("total", 0),
            "num_columns": len(fields),
            "fields": fields,
        }

    if portal:
        data = await _schema(portal)
    else:
        results = await fan_out(ctx, None, _schema, first_match=True)
        _, data = unwrap_first_match(results, bare_id, "Resource")

    markdown = md_response(**data)
    return ToolResult(content=markdown, structured_content=data)


@mcp.tool(annotations=READONLY)
async def compare_datasets(
    dataset_ids: list[str],
    ctx: Context = None,
) -> str:
    """Compare metadata side-by-side for multiple datasets (can be cross-portal).

    Args:
        dataset_ids: List of prefixed dataset IDs (e.g. ["toronto:abc", "ontario:def"]) to compare (2-5)
    """
    comparisons = []
    for ds_id in dataset_ids[:5]:
        portal, bare_id, ds = await resolve_dataset(ctx, ds_id)

        resources = ds.get("resources", [])
        formats = sorted(set(r.get("format", "").upper() for r in resources if r.get("format")))
        comparisons.append({
            "id": f"{portal}:{ds['id']}",
            "title": ds.get("title"),
            "organization": ds.get("organization", {}).get("title"),
            "num_resources": len(resources),
            "formats": formats,
            "update_frequency": ds.get("update_frequency"),
            "last_modified": ds.get("metadata_modified"),
            "tags": [t["name"] for t in ds.get("tags", [])],
            "license": ds.get("license_title"),
            "geographic_coverage": ds.get("geographic_coverage"),
        })

    all_tags = [set(c["tags"]) for c in comparisons]
    shared_tags = list(set.intersection(*all_tags)) if all_tags else []

    return md_response(datasets=comparisons, shared_tags=shared_tags)
