"""Failure Taxonomy library for categorizing and analyzing eval failures."""
from __future__ import annotations

import json
import re
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any

try:
    import yaml
    YAML_AVAILABLE = True
except ImportError:
    YAML_AVAILABLE = False


@dataclass
class FailureType:
    """Represents a specific type of failure that can occur during evaluation."""
    failure_id: str
    name: str
    category: str
    severity: str
    description: str
    detection_probes: list[str] = field(default_factory=list)
    mitigation: str = ""
    remediation: str = ""


class FailureTaxonomy:
    """Manages a library of failure types and provides classification capabilities."""

    def __init__(self) -> None:
        """Initialize an empty failure taxonomy."""
        self.types: dict[str, FailureType] = {}

    def add(self, failure_type: FailureType) -> None:
        """Add a failure type to the taxonomy."""
        self.types[failure_type.failure_id] = failure_type

    def get(self, failure_id: str) -> FailureType | None:
        """Get a failure type by its ID.

        Args:
            failure_id: The failure ID to look up

        Returns:
            The FailureType if found, None otherwise
        """
        return self.types.get(failure_id)

    def by_category(self, category: str) -> list[FailureType]:
        """Get all failure types in a specific category.

        Args:
            category: The category to filter by (e.g., 'correctness', 'governance')

        Returns:
            List of FailureType objects in the specified category
        """
        return [ft for ft in self.types.values() if ft.category == category]

    def by_severity(self, severity: str) -> list[FailureType]:
        """Get all failure types with a specific severity level.

        Args:
            severity: The severity level to filter by (e.g., 'critical', 'high', 'medium')

        Returns:
            List of FailureType objects with the specified severity
        """
        return [ft for ft in self.types.values() if ft.severity == severity]

    def classify(self, error_text: str) -> list[FailureType]:
        """Classify an error by matching detection probes against error text.

        Uses simple keyword matching to identify potential failure types
        based on the error output.

        Args:
            error_text: The error message or output to classify

        Returns:
            List of FailureType objects that match the error text
        """
        matches = []
        error_lower = error_text.lower()

        for failure_type in self.types.values():
            for probe in failure_type.detection_probes:
                # Simple case-insensitive substring matching
                if probe.lower() in error_lower:
                    matches.append(failure_type)
                    break  # Only add once per failure type

        return matches

    @classmethod
    def from_yaml(cls, yaml_path: str | Path) -> FailureTaxonomy:
        """Load a failure taxonomy from a YAML file.

        Args:
            yaml_path: Path to the YAML file containing failure definitions

        Returns:
            A FailureTaxonomy instance populated with the loaded failures

        Raises:
            ImportError: If PyYAML is not installed
            FileNotFoundError: If the YAML file doesn't exist
        """
        if not YAML_AVAILABLE:
            msg = "PyYAML is required to load from YAML files. Install with: pip install pyyaml"
            raise ImportError(msg)

        yaml_path = Path(yaml_path)
        if not yaml_path.exists():
            msg = f"YAML file not found: {yaml_path}"
            raise FileNotFoundError(msg)

        with open(yaml_path, encoding="utf-8") as f:
            data = yaml.safe_load(f)

        taxonomy = cls()

        # Handle both list and dict formats
        failures = data if isinstance(data, list) else data.get("failures", [])

        for item in failures:
            failure_type = FailureType(
                failure_id=item["failure_id"],
                name=item["name"],
                category=item["category"],
                severity=item["severity"],
                description=item["description"],
                detection_probes=item.get("detection_probes", []),
                mitigation=item.get("mitigation", ""),
                remediation=item.get("remediation", item.get("mitigation", ""))
            )
            taxonomy.add(failure_type)

        return taxonomy

    @classmethod
    def from_json(cls, json_path: str | Path) -> FailureTaxonomy:
        """Load a failure taxonomy from a JSON file.

        Args:
            json_path: Path to the JSON file containing failure definitions

        Returns:
            A FailureTaxonomy instance populated with the loaded failures

        Raises:
            FileNotFoundError: If the JSON file doesn't exist
        """
        json_path = Path(json_path)
        if not json_path.exists():
            msg = f"JSON file not found: {json_path}"
            raise FileNotFoundError(msg)

        with open(json_path, encoding="utf-8") as f:
            data = json.load(f)

        taxonomy = cls()

        # Handle both list and dict formats
        failures = data if isinstance(data, list) else data.get("failures", [])

        for item in failures:
            failure_type = FailureType(
                failure_id=item["failure_id"],
                name=item["name"],
                category=item["category"],
                severity=item["severity"],
                description=item["description"],
                detection_probes=item.get("detection_probes", []),
                mitigation=item.get("mitigation", ""),
                remediation=item.get("remediation", item.get("mitigation", ""))
            )
            taxonomy.add(failure_type)

        return taxonomy

    def validate_against_schema(self, schema_path: str | Path) -> tuple[bool, list[str]]:
        """Validate the taxonomy against a JSON schema.

        Args:
            schema_path: Path to the JSON schema file

        Returns:
            Tuple of (is_valid, list_of_errors)
        """
        try:
            import jsonschema
        except ImportError:
            return False, ["jsonschema is required for validation. Install with: pip install jsonschema"]

        schema_path = Path(schema_path)
        if not schema_path.exists():
            return False, [f"Schema file not found: {schema_path}"]

        with open(schema_path, encoding="utf-8") as f:
            schema = json.load(f)

        # Convert taxonomy to dict format for validation
        data = {
            "failures": [
                {
                    "failure_id": ft.failure_id,
                    "name": ft.name,
                    "category": ft.category,
                    "severity": ft.severity,
                    "description": ft.description,
                    "detection_probes": ft.detection_probes,
                    "mitigation": ft.mitigation,
                    "remediation": ft.remediation
                }
                for ft in self.types.values()
            ]
        }

        errors = []
        try:
            jsonschema.validate(data, schema)
            return True, []
        except jsonschema.ValidationError as e:
            errors.append(f"Validation error: {e.message}")
            return False, errors
        except jsonschema.SchemaError as e:
            errors.append(f"Schema error: {e.message}")
            return False, errors

    def to_dict(self) -> dict[str, Any]:
        """Convert the taxonomy to a dictionary format.

        Returns:
            Dictionary representation of the taxonomy
        """
        return {
            "failures": [
                {
                    "failure_id": ft.failure_id,
                    "name": ft.name,
                    "category": ft.category,
                    "severity": ft.severity,
                    "description": ft.description,
                    "detection_probes": ft.detection_probes,
                    "mitigation": ft.mitigation,
                    "remediation": ft.remediation
                }
                for ft in self.types.values()
            ]
        }

    def __len__(self) -> int:
        """Return the number of failure types in the taxonomy."""
        return len(self.types)
