"""FastMCP server for NASA GIBS — tools, resources, and prompts.

Wires the GIBS client, geocoding, capabilities parser, and colormap
interpreter into a cohesive MCP server that lets LLMs discover and
fetch satellite imagery via natural language.

IMPORTANT: Do NOT use `from __future__ import annotations` here.
FastMCP needs runtime types for Literal, list[str], etc.
"""

import asyncio
import atexit
import json
import logging

import httpx
from fastmcp import FastMCP
from fastmcp.server.context import Context
from fastmcp.server.middleware import Middleware
from fastmcp.utilities.types import Image

from mcgibs.capabilities import search_layers
from mcgibs.client import GIBSClient
from mcgibs.constants import PROJECTIONS
from mcgibs.geo import expand_bbox
from mcgibs.models import BBox

log = logging.getLogger(__name__)

mcp = FastMCP(
    "mcgibs",
    instructions=(
        "NASA Global Imagery Browse Services (GIBS) — "
        "search 1000+ satellite visualization layers, fetch imagery by place name "
        "and date, and get natural-language explanations of what the colors mean."
    ),
)

# Shared client instance — initialized via on_initialize middleware
_client: GIBSClient | None = None


def _get_client() -> GIBSClient:
    """Retrieve the initialized GIBS client."""
    if _client is None:
        raise RuntimeError("GIBS client not initialized")
    return _client


def _shutdown_client() -> None:
    """Close the GIBS client on interpreter shutdown.

    FastMCP Middleware has no on_shutdown hook, so we use atexit to
    ensure the httpx connection pool is closed cleanly rather than
    relying on garbage collection.
    """
    global _client
    if _client is not None:
        client = _client
        _client = None
        try:
            loop = asyncio.new_event_loop()
            loop.run_until_complete(client.close())
            loop.close()
        except Exception:
            pass


atexit.register(_shutdown_client)


# --- Middleware: initialize client on session start ---


class GIBSInitMiddleware(Middleware):
    """Load GIBS capabilities when the first client connects."""

    async def on_initialize(self, context, call_next):
        global _client
        if _client is None:
            log.info("Initializing GIBS client and loading capabilities...")
            client = GIBSClient()
            try:
                await client.initialize()
            except Exception:
                # C2: clean up the HTTP client so we don't leak sockets,
                # then re-raise so the next connection can retry.
                await client.close()
                raise
            _client = client
            log.info("GIBS client ready with %d layers", len(_client.layer_index))
        return await call_next(context)


mcp.middleware.append(GIBSInitMiddleware())


# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#  TOOLS — Discovery
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


@mcp.tool(
    description="Search NASA GIBS satellite imagery layers by keyword. "
    "Returns matching layers with titles, identifiers, and date ranges."
)
async def search_gibs_layers(
    query: str,
    measurement: str | None = None,
    period: str | None = None,
    ongoing: bool | None = None,
    limit: int = 20,
) -> str:
    """Search across 1000+ GIBS visualization layers.

    Args:
        query: Keywords to search (e.g. "sea ice", "temperature", "MODIS fire").
        measurement: Filter by measurement category (e.g. "Sea Ice", "Temperature").
        period: Filter by temporal period ("Daily", "Monthly", "8-Day").
        ongoing: If True, only actively updated layers. If False, only ended datasets.
        limit: Maximum results to return.
    """
    client = _get_client()
    results = search_layers(
        client.layer_index,
        query,
        measurement,
        period,
        ongoing,
        limit,
    )

    if not results:
        return f"No layers found matching '{query}'."

    lines = [f"Found {len(results)} layer(s) matching '{query}':\n"]
    for layer in results:
        date_info = ""
        if layer.time:
            parts = []
            if layer.time.start:
                parts.append(f"from {layer.time.start}")
            if layer.time.end:
                parts.append(f"to {layer.time.end}")
            if layer.time.period:
                parts.append(f"({layer.time.period})")
            date_info = " — " + " ".join(parts) if parts else ""

        lines.append(f"  {layer.identifier}")
        lines.append(f"    {layer.title}{date_info}")
        if layer.has_colormap:
            lines.append("    [has colormap]")
        lines.append("")

    return "\n".join(lines)


@mcp.tool(
    description="Get detailed metadata for a specific GIBS layer including "
    "dates, resolution, formats, projections, and colormap availability."
)
async def get_layer_info(layer_id: str) -> str:
    """Fetch full metadata for a layer.

    Args:
        layer_id: The GIBS layer identifier (e.g. "MODIS_Terra_CorrectedReflectance_TrueColor").
    """
    client = _get_client()
    layer = client.get_layer(layer_id)
    if layer is None:
        return f"Layer '{layer_id}' not found. Use search_gibs_layers to find valid identifiers."

    # Enrich with layer-metadata JSON
    await client.fetch_layer_metadata(layer_id)

    info = {
        "identifier": layer.identifier,
        "title": layer.title,
        "formats": layer.formats,
        "tile_matrix_sets": layer.tile_matrix_sets,
        "has_colormap": layer.has_colormap,
    }

    if layer.abstract:
        info["abstract"] = layer.abstract
    if layer.measurement:
        info["measurement"] = layer.measurement
    if layer.instrument:
        info["instrument"] = layer.instrument
    if layer.platform:
        info["platform"] = layer.platform
    if layer.period:
        info["period"] = layer.period
    if layer.ongoing is not None:
        info["ongoing"] = layer.ongoing
    if layer.day_night:
        info["day_night"] = layer.day_night
    if layer.description:
        info["description"] = layer.description
    if layer.time:
        info["time"] = {
            "start": layer.time.start,
            "end": layer.time.end,
            "period": layer.time.period,
            "default": layer.time.default,
        }
    if layer.bbox:
        info["bbox"] = {
            "west": layer.bbox.west,
            "south": layer.bbox.south,
            "east": layer.bbox.east,
            "north": layer.bbox.north,
        }

    return json.dumps(info, indent=2)


@mcp.tool(description="List all measurement categories available in GIBS with layer counts.")
async def list_measurements() -> str:
    """List measurement categories across all layers."""
    client = _get_client()

    # Collect measurements — we need to fetch metadata for layers
    # that haven't been enriched yet. For efficiency, work from
    # the layer titles which often encode the measurement.
    measurements: dict[str, int] = {}

    for layer in client.layer_index.values():
        key = layer.measurement or "Unknown"
        measurements[key] = measurements.get(key, 0) + 1

    if not measurements:
        return "No measurement categories available."

    lines = ["Measurement categories:\n"]
    for name, count in sorted(measurements.items()):
        lines.append(f"  {name}: {count} layer(s)")

    return "\n".join(lines)


@mcp.tool(description="Check available date ranges for a GIBS layer via WMTS DescribeDomains.")
async def check_layer_dates(layer_id: str) -> str:
    """Query what dates are available for a specific layer.

    Args:
        layer_id: The GIBS layer identifier.
    """
    client = _get_client()
    layer = client.get_layer(layer_id)
    if layer is None:
        return f"Layer '{layer_id}' not found."

    # First, report what the capabilities document says
    lines = [f"Date information for {layer_id}:"]

    if layer.time:
        if layer.time.start:
            lines.append(f"  Start: {layer.time.start}")
        if layer.time.end:
            lines.append(f"  End: {layer.time.end}")
        if layer.time.period:
            lines.append(f"  Period: {layer.time.period}")
        if layer.time.default:
            lines.append(f"  Default/latest: {layer.time.default}")
    else:
        lines.append("  No time dimension (static layer)")

    # Try DescribeDomains for more precise info
    try:
        domains = await client.describe_domains(layer_id)
        if "time_domain" in domains:
            lines.append(f"  Live time domain: {domains['time_domain']}")
    except (httpx.HTTPError, RuntimeError) as exc:
        log.debug("DescribeDomains failed for %s: %s", layer_id, exc)

    return "\n".join(lines)


# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#  TOOLS — Imagery
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


async def _resolve_bbox(
    client: GIBSClient,
    bbox: list[float] | None,
    place: str | None,
) -> BBox:
    """Resolve a bounding box from either explicit coordinates or a place name."""
    if bbox and len(bbox) == 4:
        return BBox(west=bbox[0], south=bbox[1], east=bbox[2], north=bbox[3])

    if place:
        result = await client.resolve_place(place)
        if result:
            return expand_bbox(result.bbox, factor=0.1)
        raise ValueError(f"Could not geocode '{place}'. Try providing explicit bbox coordinates.")

    raise ValueError("Provide either 'bbox' [west, south, east, north] or 'place' name.")


@mcp.tool(
    description="Fetch satellite imagery for a specific layer, date, and region. "
    "Provide either a place name or bbox coordinates. Returns the image."
)
async def get_imagery(
    layer_id: str,
    date: str,
    ctx: Context,
    bbox: list[float] | None = None,
    place: str | None = None,
    width: int = 1024,
    height: int = 1024,
    format: str = "jpeg",
):
    """Fetch GIBS imagery via WMS.

    Args:
        layer_id: GIBS layer identifier.
        date: Date in YYYY-MM-DD format.
        bbox: Bounding box as [west, south, east, north] in decimal degrees.
        place: Place name to geocode (e.g. "California", "Amazon Basin").
        width: Image width in pixels.
        height: Image height in pixels.
        format: Image format — "jpeg" or "png".
    """
    client = _get_client()

    layer = client.get_layer(layer_id)
    if layer is None:
        return f"Layer '{layer_id}' not found."

    await ctx.report_progress(1, 4, "Resolving location...")
    try:
        resolved_bbox = await _resolve_bbox(client, bbox, place)
    except Exception as exc:
        return str(exc)

    await ctx.report_progress(2, 4, "Fetching imagery from GIBS...")
    image_bytes = await client.get_wms_image(
        layer_id,
        date,
        resolved_bbox,
        width,
        height,
        f"image/{format}",
    )

    description = (
        f"{layer.title} — {date}\n"
        f"Region: {place or resolved_bbox.wms_bbox}\n"
        f"Size: {width}x{height}"
    )
    if layer.has_colormap:
        description += "\nTip: use explain_layer_colormap to understand what the colors represent."

    await ctx.report_progress(4, 4, "Complete")
    return [description, Image(data=image_bytes, format=format)]


@mcp.tool(
    description="Compare a layer across two dates with a side-by-side image. "
    "Useful for tracking changes over time (fires, floods, ice extent)."
)
async def compare_dates(
    layer_id: str,
    date_before: str,
    date_after: str,
    ctx: Context,
    bbox: list[float] | None = None,
    place: str | None = None,
):
    """Side-by-side comparison of two dates.

    Args:
        layer_id: GIBS layer identifier.
        date_before: Earlier date (YYYY-MM-DD).
        date_after: Later date (YYYY-MM-DD).
        bbox: Bounding box as [west, south, east, north].
        place: Place name to geocode.
    """
    client = _get_client()

    layer = client.get_layer(layer_id)
    if layer is None:
        return f"Layer '{layer_id}' not found."

    await ctx.report_progress(1, 5, "Resolving location...")
    try:
        resolved_bbox = await _resolve_bbox(client, bbox, place)
    except Exception as exc:
        return str(exc)

    await ctx.report_progress(2, 5, f"Fetching imagery for {date_before}...")
    await ctx.report_progress(3, 5, f"Fetching imagery for {date_after}...")
    composite_bytes = await client.compare_dates(
        layer_id,
        date_before,
        date_after,
        resolved_bbox,
    )

    description = (
        f"{layer.title} — comparison\n"
        f"Left: {date_before} | Right: {date_after}\n"
        f"Region: {place or resolved_bbox.wms_bbox}"
    )

    await ctx.report_progress(5, 5, "Complete")
    return [description, Image(data=composite_bytes, format="jpeg")]


@mcp.tool(
    description="Overlay multiple GIBS layers into a single composite image. "
    "WMS supports up to 5 layers composited together."
)
async def get_imagery_composite(
    layer_ids: list[str],
    date: str,
    ctx: Context,
    bbox: list[float] | None = None,
    place: str | None = None,
    width: int = 1024,
    height: int = 1024,
):
    """Multi-layer composite image.

    Args:
        layer_ids: List of GIBS layer identifiers to overlay (max 5).
        date: Date in YYYY-MM-DD format.
        bbox: Bounding box as [west, south, east, north].
        place: Place name to geocode.
        width: Image width in pixels.
        height: Image height in pixels.
    """
    client = _get_client()

    if len(layer_ids) > 5:
        return "WMS supports at most 5 layers per composite."

    await ctx.report_progress(1, 3, "Resolving location...")
    try:
        resolved_bbox = await _resolve_bbox(client, bbox, place)
    except Exception as exc:
        return str(exc)

    layer_names = ", ".join(layer_ids)
    await ctx.report_progress(2, 3, f"Fetching {len(layer_ids)}-layer composite...")
    image_bytes = await client.get_wms_composite(
        layer_ids,
        date,
        resolved_bbox,
        width,
        height,
    )

    description = (
        f"Composite: {layer_names}\nDate: {date}\nRegion: {place or resolved_bbox.wms_bbox}"
    )

    await ctx.report_progress(3, 3, "Complete")
    return [description, Image(data=image_bytes, format="jpeg")]


# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#  TOOLS — Interpretation
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


@mcp.tool(
    description="Explain what the colors in a GIBS layer mean. "
    "Returns a natural-language description mapping colors to scientific values and units."
)
async def explain_layer_colormap(layer_id: str) -> str:
    """Get natural-language explanation of a layer's colormap.

    Args:
        layer_id: GIBS layer identifier.
    """
    client = _get_client()
    return await client.explain_layer_colormap(layer_id)


@mcp.tool(description="Fetch the pre-rendered legend image for a GIBS layer.")
async def get_legend(
    layer_id: str,
    orientation: str = "horizontal",
):
    """Fetch the legend graphic for a layer.

    Args:
        layer_id: GIBS layer identifier.
        orientation: "horizontal" or "vertical".
    """
    client = _get_client()
    legend_bytes = await client.get_legend_image(layer_id, orientation)

    if legend_bytes is None:
        return f"No legend available for '{layer_id}'."

    return [f"Legend for {layer_id}", Image(data=legend_bytes, format="png")]


# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#  TOOLS — Utility
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


@mcp.tool(
    description="Geocode a place name to geographic coordinates and bounding box. "
    "Uses OpenStreetMap Nominatim."
)
async def resolve_place(place: str) -> str:
    """Resolve a place name to coordinates and bounding box.

    Args:
        place: Place name (e.g. "Tokyo", "California", "Great Barrier Reef").
    """
    client = _get_client()
    result = await client.resolve_place(place)

    if result is None:
        return f"Could not geocode '{place}'. Try a more specific name."

    return json.dumps(
        {
            "display_name": result.display_name,
            "lat": result.lat,
            "lon": result.lon,
            "bbox": {
                "west": result.bbox.west,
                "south": result.bbox.south,
                "east": result.bbox.east,
                "north": result.bbox.north,
            },
        },
        indent=2,
    )


@mcp.tool(
    description="Construct a direct WMTS tile URL for a specific "
    "layer, date, zoom level, and tile coordinates."
)
async def build_tile_url(
    layer_id: str,
    date: str,
    zoom: int,
    row: int,
    col: int,
    projection: str = "4326",
) -> str:
    """Build a WMTS REST tile URL.

    Args:
        layer_id: GIBS layer identifier.
        date: Date in YYYY-MM-DD format.
        zoom: Zoom level (0-12 depending on resolution).
        row: Tile row.
        col: Tile column.
        projection: EPSG code ("4326", "3857", "3413", "3031").
    """
    client = _get_client()
    layer = client.get_layer(layer_id)

    ext = "jpg"
    tile_matrix_set = "250m"
    if layer:
        if layer.formats and "image/png" in layer.formats:
            ext = "png"
        if layer.tile_matrix_sets:
            tile_matrix_set = layer.tile_matrix_sets[0]

    url = client.build_tile_url(
        layer_id,
        date,
        zoom,
        row,
        col,
        tile_matrix_set,
        ext,
        projection,
    )
    return url


# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#  RESOURCES
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


@mcp.resource("gibs://catalog")
async def catalog_resource() -> str:
    """Full GIBS layer catalog grouped by measurement category."""
    client = _get_client()

    by_measurement: dict[str, list[dict]] = {}
    for layer in client.layer_index.values():
        key = layer.measurement or "Unknown"
        if key not in by_measurement:
            by_measurement[key] = []
        by_measurement[key].append(
            {
                "id": layer.identifier,
                "title": layer.title,
                "has_colormap": layer.has_colormap,
            }
        )

    return json.dumps(by_measurement, indent=2)


@mcp.resource("gibs://layer/{layer_id}")
async def layer_resource(layer_id: str) -> str:
    """Individual layer metadata as JSON."""
    client = _get_client()
    layer = client.get_layer(layer_id)
    if layer is None:
        return json.dumps({"error": f"Layer '{layer_id}' not found"})

    await client.fetch_layer_metadata(layer_id)
    return layer.model_dump_json(indent=2)


@mcp.resource("gibs://colormap/{layer_id}")
async def colormap_resource(layer_id: str) -> str:
    """Natural-language explanation of a layer's colormap."""
    client = _get_client()
    return await client.explain_layer_colormap(layer_id)


@mcp.resource("gibs://dates/{layer_id}")
async def dates_resource(layer_id: str) -> str:
    """Available date range for a layer."""
    client = _get_client()
    layer = client.get_layer(layer_id)
    if layer is None:
        return json.dumps({"error": f"Layer '{layer_id}' not found"})

    info: dict = {}
    if layer.time:
        info["start"] = layer.time.start
        info["end"] = layer.time.end
        info["period"] = layer.time.period
        info["default"] = layer.time.default

    try:
        domains = await client.describe_domains(layer_id)
        if "time_domain" in domains:
            info["live_time_domain"] = domains["time_domain"]
    except (httpx.HTTPError, RuntimeError):
        pass

    return json.dumps(info, indent=2)


@mcp.resource("gibs://projections")
async def projections_resource() -> str:
    """Available GIBS projections with endpoint information."""
    return json.dumps(PROJECTIONS, indent=2)


# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#  PROMPTS
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


@mcp.prompt
def investigate_event(
    event_type: str,
    location: str,
    date: str,
) -> str:
    """Guided workflow for investigating a natural event with GIBS imagery.

    Args:
        event_type: Type of event (e.g. "wildfire", "hurricane", "flood", "volcanic eruption").
        location: Where the event occurred.
        date: Approximate date of the event (YYYY-MM-DD).
    """
    lines = [
        f"Investigate the {event_type} near {location} "
        f"around {date} using NASA GIBS satellite imagery.",
        "",
        "Follow this workflow:",
        "",
        f"1. **Find relevant layers**: Search for layers related to "
        f'"{event_type}" (e.g. fire layers for wildfires, '
        f'precipitation for floods). Also search for "true color" '
        f'or "corrected reflectance" for visual context.',
        "",
        f"2. **Verify dates**: Check that the layers have data "
        f"available for {date}. Adjust the date if needed.",
        "",
        f'3. **Get imagery**: Fetch imagery for "{location}" on '
        f"{date}. Use the true color layer first for visual context.",
        "",
        "4. **Compare before/after**: Get imagery from a few days "
        "or weeks before the event and compare with imagery "
        "during/after to show the impact.",
        "",
        "5. **Explain the data**: If the layers have colormaps, use "
        "explain_colormap to understand what the colors represent.",
        "",
        f"6. **Summarize findings**: Describe what the satellite "
        f"data reveals about the {event_type} — extent, intensity, "
        f"progression, or impact visible from space.",
    ]
    return "\n".join(lines)


@mcp.prompt
def earth_overview() -> str:
    """Introduction to GIBS and suggested explorations."""
    lines = [
        "You have access to NASA's Global Imagery Browse Services "
        "(GIBS) through the mcgibs MCP server. GIBS provides 1000+ "
        "satellite visualization layers covering the entire Earth.",
        "",
        "Here are some things you can explore:",
        "",
        '**Recent events**: Search for "fire", "aerosol", or '
        '"flood" layers to investigate recent natural events.',
        "",
        '**Climate data**: Search for "temperature", "sea ice", '
        '"snow cover", or "vegetation" to explore climate '
        "indicators.",
        "",
        "**True color imagery**: The "
        '"MODIS_Terra_CorrectedReflectance_TrueColor" layer '
        "provides daily true-color Earth imagery — great for "
        "seeing cloud patterns, smoke plumes, or algal blooms.",
        "",
        "**Time comparisons**: Use compare_dates to see how a "
        "region has changed over time — glacier retreat, urban "
        "expansion, seasonal changes.",
        "",
        "**Understanding the data**: Use explain_colormap on any "
        "scientific layer to understand what the colors represent "
        "in physical units.",
        "",
        "Start by searching for a topic that interests you, "
        "or ask about a specific location and date!",
    ]
    return "\n".join(lines)


@mcp.prompt
def satellite_snapshot(
    place: str,
    date: str = "today",
) -> str:
    """Quick satellite view of any place on Earth.

    Args:
        place: Location to view (e.g. "Tokyo", "Amazon Rainforest", "Sahara Desert").
        date: Date to view (YYYY-MM-DD), or "today" for the most recent imagery.
    """
    lines = [
        f"Show me a satellite view of {place} on {date}.",
        "",
        "Follow this workflow:",
        "",
        f'1. **Geocode**: Use resolve_place to get coordinates for "{place}".',
        "",
        f"2. **True color first**: Fetch imagery for "
        f'"MODIS_Terra_CorrectedReflectance_TrueColor" '
        f"on {date} at that location. This gives a natural-looking view.",
        "",
        f"3. **Find science layers**: Search for layers relevant to "
        f'"{place}" — consider what would be scientifically interesting '
        f"for this region (vegetation for forests, sea surface temperature "
        f"for coastal areas, snow cover for mountains, etc.).",
        "",
        "4. **Check dates**: Verify the science layers have data for "
        f"{date} using check_layer_dates.",
        "",
        "5. **Overlay**: If a relevant science layer is available, "
        "use get_imagery_composite to overlay it on the true color base.",
        "",
        "6. **Interpret**: If the overlaid layer has a colormap, "
        "use explain_layer_colormap to describe what the colors mean.",
        "",
        f"7. **Describe**: Summarize what's visible in the satellite view "
        f"of {place} — notable features, weather patterns, land use, "
        f"or any phenomena visible from orbit.",
    ]
    return "\n".join(lines)


@mcp.prompt
def climate_monitor(
    indicator: str,
    location: str,
    start_date: str,
    end_date: str,
) -> str:
    """Track a climate indicator over time at a specific location.

    Args:
        indicator: What to monitor (e.g. "sea ice", "vegetation", "temperature", "snow cover").
        location: Region to monitor (e.g. "Arctic", "Amazon Basin", "Greenland").
        start_date: Beginning of monitoring period (YYYY-MM-DD).
        end_date: End of monitoring period (YYYY-MM-DD).
    """
    lines = [
        f"Monitor {indicator} changes near {location} "
        f"from {start_date} to {end_date}.",
        "",
        "Follow this workflow:",
        "",
        f'1. **Find layers**: Search for GIBS layers related to "{indicator}". '
        f"Look for layers with colormaps — these encode quantitative data.",
        "",
        "2. **Check availability**: Use check_layer_dates on the best "
        f"matching layer to confirm data exists for both {start_date} "
        f"and {end_date}.",
        "",
        "3. **Understand the scale**: Use explain_layer_colormap to learn "
        "what the color values represent (units, range). "
        "Also fetch the legend with get_legend for visual reference.",
        "",
        f"4. **Compare endpoints**: Use compare_dates to create a "
        f"side-by-side view of {start_date} vs {end_date} "
        f'for "{location}". This immediately shows the magnitude of change.',
        "",
        "5. **Intermediate snapshots**: Fetch 1-2 additional dates between "
        "start and end using get_imagery to show the progression "
        "(e.g. mid-point of the range).",
        "",
        "6. **Synthesize**: Describe the observed trend, referencing "
        "specific colormap values where possible. Is the indicator "
        "increasing, decreasing, or showing seasonal variation? "
        "What does the spatial pattern reveal?",
    ]
    return "\n".join(lines)


@mcp.prompt
def layer_deep_dive(
    layer_id: str,
    location: str = "global",
    date: str = "",
) -> str:
    """Full scientific analysis of a single GIBS layer.

    Args:
        layer_id: The GIBS layer identifier (e.g. "AIRS_L3_Surface_Air_Temperature_Daily_Day").
        location: Region to focus on, or "global" for worldwide view.
        date: Specific date (YYYY-MM-DD), or empty for the most recent available.
    """
    location_desc = f'for "{location}"' if location != "global" else "at global scale"
    date_desc = f"on {date}" if date else "using the most recent available date"

    lines = [
        f"Do a deep scientific analysis of the {layer_id} layer "
        f"{location_desc} {date_desc}.",
        "",
        "Follow this workflow:",
        "",
        "1. **Layer metadata**: Use get_layer_info to retrieve full metadata "
        f'for "{layer_id}" — instrument, platform, resolution, date range, '
        "and scientific description.",
        "",
        "2. **Color interpretation**: Use explain_layer_colormap to get a "
        "detailed description of what each color represents, including "
        "physical units and value ranges. Fetch the legend with get_legend.",
        "",
        f"3. **Sample imagery**: Use get_imagery to fetch the layer "
        f"{location_desc} {date_desc}. If no date was specified, "
        "use check_layer_dates to find the latest available date first.",
        "",
        "4. **Image interpretation**: Walk through what's visible in the "
        "image, referencing specific colormap values. Identify notable "
        "spatial patterns, anomalies, or features.",
        "",
        "5. **Scientific context**: Explain what this layer measures, "
        "why it matters, what instrument collects the data, and how "
        "scientists use it. Connect the visualization to the underlying "
        "Earth science.",
    ]
    return "\n".join(lines)


@mcp.prompt
def multi_layer_story(
    topic: str,
    location: str,
    date: str,
) -> str:
    """Compose a data-driven narrative by combining multiple GIBS layers.

    Args:
        topic: Story theme (e.g. "wildfire smoke", "ocean productivity", "dust storm").
        location: Region of interest.
        date: Date for the story (YYYY-MM-DD).
    """
    lines = [
        f'Tell the story of "{topic}" near {location} on {date} '
        f"by compositing multiple satellite layers.",
        "",
        "Follow this workflow:",
        "",
        f'1. **Find related layers**: Search for 2-4 layers related to "{topic}". '
        f"Choose layers that complement each other — for example, "
        f"fire detections + aerosol optical depth + true color for wildfires, "
        f"or SST + chlorophyll + true color for ocean events.",
        "",
        "2. **Verify dates**: Use check_layer_dates on each candidate "
        f"to confirm data is available for {date}.",
        "",
        "3. **Understand each layer**: For each layer with a colormap, "
        "use explain_layer_colormap to understand the value encoding.",
        "",
        "4. **Composite view**: Use get_imagery_composite to overlay "
        "the layers into a single image. Put the base layer "
        "(true color or reference) first in the list.",
        "",
        "5. **Individual layers**: Also fetch get_imagery for each layer "
        "separately so you can describe what each one contributes.",
        "",
        "6. **Narrative**: Write a data-journalism style narrative "
        "describing how the layers interact and what story they tell "
        "together. Reference specific colormap values and spatial "
        "patterns. Explain cause and effect where the data supports it.",
    ]
    return "\n".join(lines)


@mcp.prompt
def polar_watch(
    pole: str,
    date: str,
    compare_date: str = "",
) -> str:
    """Monitor Arctic or Antarctic ice and snow conditions.

    Args:
        pole: Which pole — "arctic" or "antarctic".
        date: Primary observation date (YYYY-MM-DD).
        compare_date: Optional second date for before/after comparison (YYYY-MM-DD).
    """
    if pole.lower().startswith("ant"):
        region = "Antarctic"
        bbox = "[-180, -90, 180, -60]"
    else:
        region = "Arctic"
        bbox = "[-180, 60, 180, 90]"

    lines = [
        f"Monitor {region} ice and snow conditions on {date}.",
        "",
        "Follow this workflow:",
        "",
        f'1. **Find polar layers**: Search for "sea ice", "snow", '
        f'and "ice sheet" layers. Focus on layers that cover the '
        f"{region} region.",
        "",
        f"2. **Check availability**: Use check_layer_dates to verify "
        f"data exists for {date} on the selected layers.",
        "",
        f"3. **Polar imagery**: Fetch imagery using the {region} "
        f"bounding box {bbox}. Get true color first, then ice/snow "
        f"concentration layers.",
        "",
        "4. **Color interpretation**: Use explain_layer_colormap on each "
        "science layer and fetch legends with get_legend to understand "
        "concentration values and classifications.",
        "",
        "5. **Embeddable tiles**: Use build_tile_url to generate a "
        "WMTS URL for the primary ice layer — useful for embedding "
        "in reports or dashboards.",
    ]

    if compare_date:
        lines.extend([
            "",
            f"6. **Temporal comparison**: Use compare_dates to show "
            f"{compare_date} vs {date} side-by-side. Describe the "
            f"change in ice extent and concentration between these dates.",
        ])
    else:
        lines.extend([
            "",
            f"6. **Summarize**: Describe the current {region} ice/snow "
            f"state visible in the imagery, referencing colormap values "
            f"for concentration percentages.",
        ])

    return "\n".join(lines)


# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#  Entry point
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


def main():
    mcp.run()
