"""JSON Schema validation engine for MCP tool inputs and outputs."""

from __future__ import annotations

import json
from typing import Any

from jsonschema import Draft7Validator, ValidationError, validate

from mcp_forge._types import ToolInfo, ToolResult


class SchemaValidationError(Exception):
    """Raised when schema validation fails."""

    def __init__(self, message: str, errors: list[str] | None = None) -> None:
        self.errors = errors or []
        super().__init__(message)


def validate_tool_input(tool: ToolInfo, arguments: dict[str, Any]) -> list[str]:
    """Validate tool arguments against the tool's input schema.

    Returns a list of validation error messages (empty if valid).
    """
    schema = tool.input_schema
    if not schema:
        return []

    errors: list[str] = []
    validator = Draft7Validator(schema)
    for error in sorted(validator.iter_errors(arguments), key=lambda e: list(e.path)):
        path = ".".join(str(p) for p in error.absolute_path)
        prefix = f"  {path}: " if path else "  "
        errors.append(f"{prefix}{error.message}")

    return errors


def validate_tool_output(result: ToolResult, schema: dict[str, Any]) -> list[str]:
    """Validate a tool result's JSON content against a schema.

    Parses the text content as JSON and validates.
    Returns a list of validation error messages (empty if valid).
    """
    text = result.text
    if not text:
        return ["Tool returned empty text content"]

    try:
        data = json.loads(text)
    except json.JSONDecodeError as exc:
        return [f"Tool output is not valid JSON: {exc}"]

    errors: list[str] = []
    validator = Draft7Validator(schema)
    for error in sorted(validator.iter_errors(data), key=lambda e: list(e.path)):
        path = ".".join(str(p) for p in error.absolute_path)
        prefix = f"  {path}: " if path else "  "
        errors.append(f"{prefix}{error.message}")

    return errors


def validate_schema_is_valid(schema: dict[str, Any]) -> list[str]:
    """Check that a JSON Schema itself is valid (meta-validation).

    Returns a list of issues found with the schema definition.
    """
    errors: list[str] = []

    if not isinstance(schema, dict):
        return ["Schema must be a JSON object"]

    # Check for common issues
    if "type" not in schema:
        errors.append("Schema missing 'type' field")

    if schema.get("type") == "object":
        if "properties" not in schema:
            errors.append("Object schema missing 'properties'")

        # Check required fields reference existing properties
        required = schema.get("required", [])
        props = schema.get("properties", {})
        for req in required:
            if req not in props:
                errors.append(
                    f"Required field '{req}' not defined in properties"
                )

    # Validate as a JSON Schema
    try:
        Draft7Validator.check_schema(schema)
    except Exception as exc:
        errors.append(f"Invalid JSON Schema: {exc}")

    return errors


def diff_schemas(
    old_schema: dict[str, Any], new_schema: dict[str, Any]
) -> list[str]:
    """Compare two tool schemas and return human-readable differences.

    Useful for detecting when a server's API surface changes.
    """
    from deepdiff import DeepDiff

    diff = DeepDiff(old_schema, new_schema, verbose_level=2)
    if not diff:
        return []

    changes: list[str] = []
    diff_dict = dict(diff)

    if "values_changed" in diff_dict:
        for path, change in diff_dict["values_changed"].items():
            changes.append(
                f"Changed: {path} ({change['old_value']!r} → {change['new_value']!r})"
            )

    if "dictionary_item_added" in diff_dict:
        for item in diff_dict["dictionary_item_added"]:
            changes.append(f"Added: {item}")

    if "dictionary_item_removed" in diff_dict:
        for item in diff_dict["dictionary_item_removed"]:
            changes.append(f"Removed: {item}")

    if "type_changes" in diff_dict:
        for path, change in diff_dict["type_changes"].items():
            changes.append(
                f"Type changed: {path} "
                f"({change['old_type'].__name__} → {change['new_type'].__name__})"
            )

    return changes


def generate_sample_input(schema: dict[str, Any]) -> dict[str, Any]:
    """Generate a sample valid input from a JSON Schema.

    Creates minimal valid input by filling required fields with
    sensible defaults based on their types.
    """
    if schema.get("type") != "object":
        return {}

    sample: dict[str, Any] = {}
    props = schema.get("properties", {})
    required = set(schema.get("required", []))

    for name, prop_schema in props.items():
        # Always include required fields; skip optional ones
        if name not in required:
            continue

        prop_type = prop_schema.get("type", "string")

        if prop_type == "string":
            if "enum" in prop_schema:
                sample[name] = prop_schema["enum"][0]
            elif "default" in prop_schema:
                sample[name] = prop_schema["default"]
            else:
                sample[name] = f"test_{name}"
        elif prop_type == "integer":
            sample[name] = prop_schema.get("default", prop_schema.get("minimum", 0))
        elif prop_type == "number":
            sample[name] = prop_schema.get("default", prop_schema.get("minimum", 0.0))
        elif prop_type == "boolean":
            sample[name] = prop_schema.get("default", True)
        elif prop_type == "array":
            sample[name] = prop_schema.get("default", [])
        elif prop_type == "object":
            sample[name] = prop_schema.get("default", {})
        elif prop_type == "null":
            sample[name] = None
        else:
            sample[name] = None

    return sample
