"""
Configuration management tools for Home Assistant zones.

This module provides tools for listing, creating/updating, and removing
Home Assistant zones (location-based areas for presence automation).
"""

import logging
from typing import Annotated, Any

from fastmcp.exceptions import ToolError
from fastmcp.tools import tool
from pydantic import Field

from ..errors import ErrorCode, create_error_response, create_validation_error
from .auto_backup import with_auto_backup
from .helpers import (
    exception_to_structured_error,
    log_tool_usage,
    raise_tool_error,
    register_tool_methods,
    validate_identifier_not_empty,
)

logger = logging.getLogger(__name__)


class ZoneTools:
    """Zone configuration management tools for Home Assistant."""

    def __init__(self, client: Any) -> None:
        self._client = client

    @tool(
        name="ha_get_zone",
        tags={"Zones"},
        annotations={"idempotentHint": True, "readOnlyHint": True, "title": "Get Zone"},
    )
    @log_tool_usage
    async def ha_get_zone(
        self,
        zone_id: Annotated[
            str | None,
            Field(
                description="Zone ID to get details for (from ha_get_zone() list). "
                "If omitted, lists all zones.",
                default=None,
            ),
        ] = None,
    ) -> dict[str, Any]:
        """
        Get zone information - list all zones or get details for a specific one.

        Without a zone_id: Lists all Home Assistant zones with their coordinates and radius.
        With a zone_id: Returns detailed configuration for a specific zone.

        ZONE PROPERTIES:
        - ID, name, icon
        - Latitude, longitude, radius
        - Passive mode setting

        EXAMPLES:
        - List all zones: ha_get_zone()
        - Get specific zone: ha_get_zone(zone_id="abc123")

        **NOTE:** This returns storage-based zones (created via UI/API), not YAML-defined zones.
        The 'home' zone is typically defined in YAML and may not appear in this list.
        """
        try:
            message: dict[str, Any] = {
                "type": "zone/list",
            }

            result = await self._client.send_websocket_message(message)

            if not result.get("success"):
                raise_tool_error(
                    create_error_response(
                        ErrorCode.SERVICE_CALL_FAILED,
                        result.get("error", "Failed to get zones"),
                        context={"zone_id": zone_id},
                    )
                )

            zones = result.get("result", [])

            if zone_id is None:
                return {
                    "success": True,
                    "count": len(zones),
                    "zones": zones,
                    "message": f"Found {len(zones)} zone(s)",
                }

            zone = next((z for z in zones if z.get("id") == zone_id), None)

            if zone is None:
                available_ids = [z.get("id") for z in zones[:10]]  # Show first 10
                raise_tool_error(
                    create_error_response(
                        ErrorCode.RESOURCE_NOT_FOUND,
                        f"Zone not found: {zone_id}",
                        context={
                            "zone_id": zone_id,
                            "available_zone_ids": available_ids,
                        },
                        suggestions=[
                            "Use ha_get_zone() without zone_id to see all available zones"
                        ],
                    )
                )

            return {
                "success": True,
                "zone_id": zone_id,
                "zone": zone,
            }

        except ToolError:
            raise
        except Exception as e:
            logger.error(f"Error getting zone(s) (zone_id={zone_id}): {e}")
            exception_to_structured_error(
                e,
                context={"zone_id": zone_id},
                suggestions=[
                    "Check Home Assistant connection",
                    "Verify WebSocket connection is active",
                    "Use ha_get_zone() without zone_id to see all available zones",
                ],
            )
            return None  # unreachable: exception_to_structured_error always raises

    @staticmethod
    def _validate_coordinates(
        latitude: float | None,
        longitude: float | None,
        radius: float | None,
    ) -> None:
        """Validate zone coordinate parameters, raising ToolError on invalid values."""
        if latitude is not None and not (-90 <= latitude <= 90):
            raise_tool_error(
                create_validation_error(
                    f"Invalid latitude: {latitude}. Must be between -90 and 90.",
                    parameter="latitude",
                )
            )
        if longitude is not None and not (-180 <= longitude <= 180):
            raise_tool_error(
                create_validation_error(
                    f"Invalid longitude: {longitude}. Must be between -180 and 180.",
                    parameter="longitude",
                )
            )
        if radius is not None and radius <= 0:
            raise_tool_error(
                create_validation_error(
                    f"Invalid radius: {radius}. Must be greater than 0.",
                    parameter="radius",
                )
            )

    @tool(
        name="ha_set_zone",
        tags={"Zones"},
        annotations={"destructiveHint": True, "title": "Set Zone"},
    )
    @with_auto_backup(
        domain="zone",
        id_fn=lambda kw: str(kw.get("zone_id") or kw.get("name") or ""),
    )
    @log_tool_usage
    async def ha_set_zone(
        self,
        name: Annotated[
            str | None,
            Field(
                description="Display name for the zone (required for create)",
                default=None,
            ),
        ] = None,
        latitude: Annotated[
            float | None,
            Field(
                description="Latitude coordinate of the zone center (required for create)",
                default=None,
            ),
        ] = None,
        longitude: Annotated[
            float | None,
            Field(
                description="Longitude coordinate of the zone center (required for create)",
                default=None,
            ),
        ] = None,
        zone_id: Annotated[
            str | None,
            Field(
                description="Zone ID to update (omit to create new zone, use ha_get_zone to find IDs)",
                default=None,
            ),
        ] = None,
        radius: Annotated[
            float | None,
            Field(
                description="Radius of the zone in meters (must be > 0, defaults to 100 on create)",
                default=None,
            ),
        ] = None,
        icon: Annotated[
            str | None,
            Field(
                description="Material Design Icon (e.g., 'mdi:briefcase', 'mdi:school')",
                default=None,
            ),
        ] = None,
        passive: Annotated[
            bool | None,
            Field(
                description="Passive mode - if True, zone will not trigger enter/exit automations (defaults to False on create)",
                default=None,
            ),
        ] = None,
    ) -> dict[str, Any]:
        """
        Create or update a Home Assistant zone.

        Omit zone_id to create a new zone (name, latitude, longitude required).
        Provide zone_id to update an existing zone (only specified fields change).

        EXAMPLES:
        - Create: ha_set_zone(name="Office", latitude=40.7128, longitude=-74.0060, radius=150, icon="mdi:briefcase")
        - Update name: ha_set_zone(zone_id="abc123", name="New Office")
        - Update radius: ha_set_zone(zone_id="abc123", radius=200)
        - Update location: ha_set_zone(zone_id="abc123", latitude=40.7128, longitude=-74.0060)

        Note: The 'home' zone is typically defined in YAML and cannot be modified via this API.
        """
        operation = "create"
        try:
            # ``None`` stays the documented "create-new" sentinel; explicit
            # empty/whitespace ``zone_id`` would silently route to the
            # create branch below and surface "name, latitude, longitude
            # required" instead of the actual cause (unusable ``zone_id``).
            if zone_id is not None:
                validate_identifier_not_empty(
                    zone_id,
                    "zone_id",
                    suggestions=[
                        "Omit zone_id entirely to create a new zone",
                        "Pass a valid zone_id to update an existing zone",
                    ],
                    context={"action": "set"},
                )
            if zone_id:
                # UPDATE operation
                operation = "update"
                update_fields = {
                    "name": name,
                    "latitude": latitude,
                    "longitude": longitude,
                    "radius": radius,
                    "icon": icon,
                    "passive": passive,
                }
                fields_to_update = {
                    k: v for k, v in update_fields.items() if v is not None
                }

                if not fields_to_update:
                    raise_tool_error(
                        create_validation_error(
                            "No fields to update. Provide at least one field to change.",
                            context={"zone_id": zone_id},
                        )
                    )

                self._validate_coordinates(latitude, longitude, radius)

                message: dict[str, Any] = {
                    "type": "zone/update",
                    "zone_id": zone_id,
                    **fields_to_update,
                }
            else:
                # CREATE operation
                if name is None or latitude is None or longitude is None:
                    raise_tool_error(
                        create_validation_error(
                            "name, latitude, and longitude are required when creating a zone.",
                        )
                    )

                self._validate_coordinates(latitude, longitude, radius)

                message = {
                    "type": "zone/create",
                    "name": name,
                    "latitude": latitude,
                    "longitude": longitude,
                    "radius": radius if radius is not None else 100,
                    "passive": passive if passive is not None else False,
                }
                if icon:
                    message["icon"] = icon

            result = await self._client.send_websocket_message(message)

            if result.get("success"):
                zone_data = result.get("result", {})
                zone_name = name or zone_data.get("name", zone_id)
                response: dict[str, Any] = {
                    "success": True,
                    "zone_data": zone_data,
                    "zone_id": zone_data.get("id", zone_id),
                    "message": f"Successfully {'updated' if zone_id else 'created'} zone: {zone_name}",
                }
                if zone_id and fields_to_update:
                    response["updated_fields"] = list(fields_to_update.keys())
                return response
            else:
                error_str = str(result.get("error", "")).lower()
                if "not found" in error_str or "doesn't exist" in error_str:
                    raise_tool_error(
                        create_error_response(
                            ErrorCode.RESOURCE_NOT_FOUND,
                            f"Zone not found: {zone_id}",
                            context={"zone_id": zone_id, "operation": operation},
                            suggestions=[
                                "Use ha_get_zone() without zone_id to see all available zones",
                            ],
                        )
                    )
                raise_tool_error(
                    create_error_response(
                        ErrorCode.SERVICE_CALL_FAILED,
                        f"Failed to {operation} zone: {result.get('error', 'Unknown error')}",
                        context={"zone_id": zone_id, "operation": operation},
                    )
                )

        except ToolError:
            raise
        except Exception as e:
            logger.error(
                f"Error in ha_set_zone ({operation}, zone_id={zone_id}, name={name}): {e}"
            )
            exception_to_structured_error(
                e,
                context={"zone_id": zone_id, "operation": operation},
                suggestions=[
                    "Check Home Assistant connection",
                    "Verify coordinates are valid"
                    if operation == "create"
                    else "Verify zone_id exists using ha_get_zone()",
                ],
            )
            return None  # unreachable: exception_to_structured_error always raises
        return None  # py/mixed-returns: explicit terminal; error handlers above always raise (NoReturn), unreachable

    @tool(
        name="ha_remove_zone",
        tags={"Zones"},
        annotations={
            "destructiveHint": True,
            "idempotentHint": True,
            "title": "Remove Zone",
        },
    )
    @with_auto_backup(domain="zone", id_param="zone_id")
    @log_tool_usage
    async def ha_remove_zone(
        self,
        zone_id: Annotated[
            str,
            Field(description="Zone ID to remove (use ha_get_zone to find IDs)"),
        ],
    ) -> dict[str, Any]:
        """
        Remove a Home Assistant zone.

        EXAMPLES:
        - Remove zone: ha_remove_zone("abc123")

        **WARNING:** Removing a zone used in automations may cause those automations to fail.
        Use ha_get_zone() to find the zone_id for the zone you want to remove.

        **NOTE:** The 'home' zone cannot be removed as it is typically defined in configuration.yaml.
        """
        try:
            # Empty/whitespace would surface as a misleading HA delete-failure.
            validate_identifier_not_empty(
                zone_id,
                "zone_id",
                suggestions=["Use ha_get_zone() to find existing zone_ids"],
                context={"operation": "remove_zone"},
            )
            message: dict[str, Any] = {
                "type": "zone/delete",
                "zone_id": zone_id,
            }

            result = await self._client.send_websocket_message(message)

            if result.get("success"):
                return {
                    "success": True,
                    "zone_id": zone_id,
                    "message": f"Successfully removed zone: {zone_id}",
                }
            else:
                error_str = str(result.get("error", "")).lower()
                if "not found" in error_str or "doesn't exist" in error_str:
                    raise_tool_error(
                        create_error_response(
                            ErrorCode.RESOURCE_NOT_FOUND,
                            f"Zone not found: {zone_id}",
                            context={"zone_id": zone_id},
                            suggestions=[
                                "Use ha_get_zone() without zone_id to see all available zones",
                            ],
                        )
                    )
                raise_tool_error(
                    create_error_response(
                        ErrorCode.SERVICE_CALL_FAILED,
                        f"Failed to remove zone: {result.get('error', 'Unknown error')}",
                        context={"zone_id": zone_id},
                    )
                )

        except ToolError:
            raise
        except Exception as e:
            logger.error(f"Error removing zone (zone_id={zone_id}): {e}")
            exception_to_structured_error(
                e,
                context={"zone_id": zone_id},
                suggestions=[
                    "Check Home Assistant connection",
                    "Verify zone_id exists using ha_get_zone()",
                    "Ensure zone is not the 'home' zone (YAML-defined)",
                ],
            )
            return None  # unreachable: exception_to_structured_error always raises
        return None  # py/mixed-returns: explicit terminal; error handlers above always raise (NoReturn), unreachable


def register_zone_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
    """Register Home Assistant zone configuration tools."""
    register_tool_methods(mcp, ZoneTools(client))
