"""JSON Schema Validation for ADRI-generated schemas.

This module provides JSON Schema validation functionality using the jsonschema
library. It validates JSON data against JSON Schema specifications, particularly
those generated by ADRI's convert_adri_to_json_schema function.

The validator supports all Draft 7 JSON Schema features including:
- Required field validation
- Type checking (string, number, integer, boolean, array, object)
- Enum/allowed values validation
- Pattern matching for strings
- Numeric constraints (minimum, maximum)
- String length constraints (minLength, maxLength)
- Array constraints (minItems, maxItems, uniqueItems)
"""

from typing import Dict, Any, List, Tuple
import logging

logger = logging.getLogger(__name__)

try:
    import jsonschema  # noqa: F401
    from jsonschema import Draft7Validator

    JSONSCHEMA_AVAILABLE = True
except ImportError:
    JSONSCHEMA_AVAILABLE = False
    logger.warning(
        "jsonschema library not available - JSON Schema validation will be skipped"
    )


def validate_json_against_schema(
    data: Any, json_schema: Dict[str, Any]
) -> Tuple[bool, List[str]]:
    """
    Validate JSON data against a JSON Schema specification.

    This function uses the jsonschema library's Draft7Validator to check if
    the provided data conforms to the given schema. It returns a tuple containing
    a boolean indicating validity and a list of user-friendly violation messages.

    Args:
        data: JSON data to validate (can be dict, list, or primitive types)
        json_schema: JSON Schema specification (Draft 7 compatible)

    Returns:
        Tuple of (is_valid, violation_messages):
        - is_valid: True if data passes all validations, False otherwise
        - violation_messages: List of human-readable error messages
                             Empty list if validation passes

    Examples:
        >>> schema = {
        ...     "type": "object",
        ...     "properties": {"name": {"type": "string"}},
        ...     "required": ["name"]
        ... }
        >>> validate_json_against_schema({"name": "test"}, schema)
        (True, [])
        >>> validate_json_against_schema({}, schema)
        (False, ["root: Missing required field 'name'"])

    Note:
        If the jsonschema library is not available, this function will log a
        warning and return (True, []) to avoid blocking execution.
    """
    if not JSONSCHEMA_AVAILABLE:
        logger.warning("jsonschema not available - skipping validation")
        return (True, [])

    if not json_schema:
        logger.debug("No JSON schema provided - skipping validation")
        return (True, [])

    violations = []

    try:
        # Create Draft 7 validator
        validator = Draft7Validator(json_schema)

        # Validate data and collect errors
        errors = list(validator.iter_errors(data))

        if not errors:
            return (True, [])

        # Convert validation errors to user-friendly messages
        for error in errors:
            violation_msg = _format_validation_error(error)
            violations.append(violation_msg)

        logger.debug(f"Validation found {len(violations)} violations")
        return (False, violations)

    except Exception as e:
        logger.error(f"Error during JSON Schema validation: {e}")
        # Return True to avoid blocking on validation errors
        return (True, [f"Validation error: {str(e)}"])


def _format_validation_error(error) -> str:
    """
    Format a jsonschema ValidationError into a readable message.

    Converts jsonschema's ValidationError objects into user-friendly strings
    that clearly indicate the location and nature of the validation failure.
    Top-level errors (no path) are formatted without a path prefix.

    Args:
        error: jsonschema ValidationError object

    Returns:
        Formatted error message string

    Examples:
        For a missing required field at top-level: "Missing required field 'name'"
        For a type mismatch in nested field: "[0].age: 'twenty' is not of type 'integer'"
        For an enum violation: "[0].status: Value must be one of ['active', 'inactive']"
    """
    # Build path to the problematic field
    path_parts = []
    for part in error.path:
        if isinstance(part, int):
            path_parts.append(f"[{part}]")
        else:
            if path_parts:
                path_parts.append(f".{part}")
            else:
                path_parts.append(str(part))

    # For top-level errors (no path), don't use a path prefix
    has_path = bool(path_parts)
    path_str = "".join(path_parts) if has_path else ""

    # Format message based on error type
    if error.validator == "required":
        # Extract missing field name from message
        missing_field = (
            error.message.split("'")[1] if "'" in error.message else "unknown"
        )
        if has_path:
            return f"{path_str}: Missing required field '{missing_field}'"
        else:
            return f"Missing required field '{missing_field}'"

    elif error.validator == "type":
        if has_path:
            return f"{path_str}: {error.message}"
        else:
            return error.message

    elif error.validator == "enum":
        allowed = error.validator_value
        if has_path:
            return f"{path_str}: Value must be one of {allowed}, got {error.instance!r}"
        else:
            return f"Value must be one of {allowed}, got {error.instance!r}"

    elif error.validator == "pattern":
        if has_path:
            return f"{path_str}: Value {error.instance!r} does not match pattern {error.validator_value}"
        else:
            return f"Value {error.instance!r} does not match pattern {error.validator_value}"

    elif error.validator in ("minimum", "maximum"):
        if has_path:
            return f"{path_str}: {error.message}"
        else:
            return error.message

    elif error.validator in ("minLength", "maxLength"):
        if has_path:
            return f"{path_str}: {error.message}"
        else:
            return error.message

    elif error.validator in ("minItems", "maxItems"):
        if has_path:
            return f"{path_str}: {error.message}"
        else:
            return error.message

    elif error.validator == "uniqueItems":
        if has_path:
            return f"{path_str}: Array items must be unique"
        else:
            return "Array items must be unique"

    else:
        # Generic error message for other validator types
        if has_path:
            return f"{path_str}: {error.message}"
        else:
            return error.message
