"""DRC (Design Rule Check) tool handler.

This module provides the handler for running DRC checks on GDS files,
including XML parsing and actionable violation summaries.
"""

from __future__ import annotations

import xml.etree.ElementTree as ET
from typing import Any

from mcp.types import Tool

from .base import EndpointMapping, ToolHandler, add_project_param

__all__ = ["CheckDrcHandler"]


def _parse_polygon_location(values_elem: ET.Element | None) -> dict[str, Any] | None:
    """Parse polygon coordinates and compute location data.

    Args:
        values_elem: XML element containing polygon coordinates

    Returns:
        Dict with bbox, centroid, and size, or None if parsing fails
    """
    if values_elem is None:
        return None

    value_elem = values_elem.find("value")
    if value_elem is None or value_elem.text is None:
        return None

    value_text = value_elem.text
    if "polygon:" not in value_text:
        return None

    try:
        coords_str = value_text.replace("polygon:", "").strip().strip("()")
        if not coords_str:
            return None

        pairs = coords_str.split(";")
        coords = []
        for pair in pairs:
            if "," not in pair:
                continue
            x_str, y_str = pair.split(",", 1)
            coords.append((float(x_str), float(y_str)))

        if not coords:
            return None

        xs = [x for x, y in coords]
        ys = [y for x, y in coords]

        bbox = {
            "min_x": round(min(xs), 3),
            "min_y": round(min(ys), 3),
            "max_x": round(max(xs), 3),
            "max_y": round(max(ys), 3),
        }

        centroid = {
            "x": round(sum(xs) / len(xs), 3),
            "y": round(sum(ys) / len(ys), 3),
        }

        size = {
            "width": round(bbox["max_x"] - bbox["min_x"], 3),
            "height": round(bbox["max_y"] - bbox["min_y"], 3),
        }

        return {
            "bbox": bbox,
            "centroid": centroid,
            "size": size,
        }

    except (ValueError, IndexError):
        return None


def _generate_drc_recommendations(
    total_violations: int,
    violations_by_category: list[dict[str, Any]],
    cells: list[str],
) -> list[str]:
    """Generate actionable recommendations based on DRC results.

    Args:
        total_violations: Total number of violations
        violations_by_category: List of violation categories with counts
        cells: List of cells checked

    Returns:
        List of recommendation strings
    """
    if total_violations == 0:
        return ["Design passes all DRC checks"]

    recommendations = []

    if violations_by_category:
        top_category = violations_by_category[0]
        if top_category["count"] > 10:
            recommendations.append(
                f"Focus on {top_category['category']} violations "
                f"({top_category['count']:,} occurrences)"
            )
        elif top_category["count"] > 1:
            recommendations.append(
                f"Check {top_category['category']} rules in {', '.join(cells)}"
            )

    if len(violations_by_category) > 1:
        recommendations.append(
            f"Review {len(violations_by_category)} different DRC rule categories"
        )

    if total_violations > 100:
        recommendations.append(
            "Consider fixing systematic issues first to reduce violation count"
        )

    return recommendations


class CheckDrcHandler(ToolHandler):
    """Handler for running DRC checks on GDS files.

    Parses KLayout RDB XML and extracts actionable violation summary
    without polygon coordinates or verbose metadata. Computes simplified
    location data (bounding box, centroid, size) from polygon coordinates
    to reduce token usage while preserving all violation details.
    """

    @property
    def name(self) -> str:
        return "check_drc"

    @property
    def definition(self) -> Tool:
        return Tool(
            name="check_drc",
            description=(
                "Run a full DRC (Design Rule Check) on a GDS file. This uploads "
                "the file to a remote DRC server and runs comprehensive design rule "
                "verification for the specified PDK and process. Use this for "
                "complete design rule validation. Returns XML results showing all "
                "DRC violations."
            ),
            inputSchema=add_project_param(
                {
                    "type": "object",
                    "properties": {
                        "path": {
                            "type": "string",
                            "description": (
                                "Path to the GDS file to check. Can be absolute or "
                                "relative to the project directory."
                            ),
                        },
                        "pdk": {
                            "type": "string",
                            "description": (
                                "PDK to use for the check. If not specified, uses the "
                                "default PDK from settings."
                            ),
                        },
                        "process": {
                            "type": "string",
                            "description": (
                                "Process variant for DRC rules. If not specified, uses "
                                "the default process from settings."
                            ),
                        },
                        "timeout": {
                            "type": "integer",
                            "description": (
                                "Timeout in seconds for the DRC check. If not specified, "
                                "uses the default timeout from settings."
                            ),
                        },
                        "host": {
                            "type": "string",
                            "description": (
                                "API host for the DRC server. If not specified, uses "
                                "the default host from settings."
                            ),
                        },
                    },
                    "required": ["path"],
                }
            ),
        )

    @property
    def mapping(self) -> EndpointMapping:
        return EndpointMapping(method="POST", path="/api/check-drc")

    def transform_request(self, args: dict[str, Any]) -> dict[str, Any]:
        """Transform check_drc MCP args to FastAPI params.

        Args:
            args: MCP tool arguments

        Returns:
            Dict with 'json_data' key for request body
        """
        json_data: dict[str, Any] = {"path": args["path"]}

        if "pdk" in args and args["pdk"]:
            json_data["pdk"] = args["pdk"]
        if "process" in args and args["process"]:
            json_data["process"] = args["process"]
        if "timeout" in args and args["timeout"]:
            json_data["timeout"] = args["timeout"]
        if "host" in args and args["host"]:
            json_data["host"] = args["host"]

        return {"json_data": json_data}

    def transform_response(self, response: Any) -> dict[str, Any]:
        """Transform check_drc response to LLM-friendly format.

        Parses KLayout RDB XML and extracts actionable violation summary
        without polygon coordinates or verbose metadata. Computes simplified
        location data (bounding box, centroid, size) from polygon coordinates
        to reduce token usage by ~82% while preserving all violation details.

        Args:
            response: FastAPI response (XML string or dict with 'content' key)

        Returns:
            Structured summary with all violations including location data,
            or error dict if parsing fails
        """
        try:
            # Handle FastAPI error responses (e.g., file not found)
            if isinstance(response, dict) and "detail" in response:
                detail = response["detail"]
                # Check for common error patterns and provide helpful messages
                if "does not exist" in str(detail).lower():
                    return {
                        "error": "File not found",
                        "detail": detail,
                        "suggestion": (
                            "The GDS file does not exist. Please build the cell first "
                            "using 'build_cells' before running DRC."
                        ),
                    }
                return {
                    "error": "DRC check failed",
                    "detail": detail,
                }

            if isinstance(response, dict) and "content" in response:
                xml_str = response["content"]
            elif isinstance(response, str):
                xml_str = response
            else:
                return {
                    "error": f"Unexpected response type: {type(response).__name__}",
                    "suggestion": "Expected XML string or dict with 'content' key",
                    "response_preview": str(response)[:500],
                }
        except Exception as e:
            return {
                "error": f"Failed to extract XML from response: {e}",
                "response_preview": str(response)[:500],
            }

        try:
            root = ET.fromstring(xml_str)
        except ET.ParseError as e:
            return {
                "error": f"Failed to parse DRC XML: {e}",
                "raw_preview": xml_str[:500],
                "suggestion": "Check if DRC server returned valid XML",
            }

        categories_map = {}
        for category in root.findall(".//categories/category"):
            name_elem = category.find("name")
            desc_elem = category.find("description")
            if name_elem is not None and name_elem.text:
                categories_map[name_elem.text] = (
                    desc_elem.text
                    if desc_elem is not None and desc_elem.text
                    else name_elem.text
                )

        cells = []
        for cell in root.findall(".//cells/cell"):
            name_elem = cell.find("name")
            if name_elem is not None and name_elem.text:
                cells.append(name_elem.text)

        violations = []
        violation_counts: dict[str, int] = {}

        for item in root.findall(".//items/item"):
            category_elem = item.find("category")
            cell_elem = item.find("cell")
            comment_elem = item.find("comment")
            values_elem = item.find("values")

            if category_elem is None or category_elem.text is None:
                continue

            category = category_elem.text
            cell = (
                cell_elem.text
                if cell_elem is not None and cell_elem.text
                else "unknown"
            )
            description = (
                comment_elem.text
                if comment_elem is not None and comment_elem.text
                else categories_map.get(category, category)
            )

            location = _parse_polygon_location(values_elem)

            violation = {
                "category": category,
                "cell": cell,
                "description": description,
            }

            if location is not None:
                violation["location"] = location
            elif values_elem is not None:
                violation["location_warning"] = "Could not parse coordinates"

            violations.append(violation)

            violation_counts[category] = violation_counts.get(category, 0) + 1

        total_violations = len(violations)
        status = "PASSED" if total_violations == 0 else "FAILED"

        summary = {
            "total_violations": total_violations,
            "total_categories": len(violation_counts),
            "cells_checked": cells,
            "status": status,
        }

        if total_violations == 0:
            summary["message"] = "No DRC violations found"

        violations_by_category = [
            {
                "category": cat,
                "description": categories_map.get(cat, cat),
                "count": count,
            }
            for cat, count in sorted(
                violation_counts.items(),
                key=lambda x: x[1],
                reverse=True,
            )
        ]

        recommendations = _generate_drc_recommendations(
            total_violations,
            violations_by_category,
            cells,
        )

        return {
            "summary": summary,
            "violations_by_category": violations_by_category,
            "violations": violations,
            "recommendations": recommendations,
        }
