"""Resource attribute validation for MCA SDK.

This module validates that all required OpenTelemetry resource attributes
are present based on the model type.

Resource attributes are key-value pairs attached to all telemetry (metrics,
logs, traces) that provide context about the source of the data.
"""

from typing import Any, Dict, List

from ..utils.exceptions import ValidationError

# Required resource attributes by metric prefix (determined by routing)
# Metric prefix is determined by get_metric_prefix(model_category, model_type)
REQUIRED_ATTRIBUTES = {
    "model": [  # Traditional ML models (internal + regression/time-series/classification)
        "service.name",  # Service name (required by OTel spec)
        "model.id",  # Unique model identifier
        "model.version",  # Model version string
        "team.name",  # Team responsible for model
    ],
    "genai": [  # AI/LLM models (internal + generative/agentic)
        "service.name",  # Service name (required by OTel spec)
        "llm.provider",  # LLM provider (openai, anthropic, etc.)
        "llm.model",  # Model name (gpt-4, claude-3, etc.)
        "team.name",  # Team responsible for integration
    ],
    "vendor": [  # Vendor models (vendor + any type)
        "service.name",  # Service name (required by OTel spec)
        "model.type",  # Type of vendor model
        "vendor.name",  # Vendor name (epic, cerner, etc.)
        "team.name",  # Team responsible for integration
    ],
}

# Optional but recommended attributes (for all model types)
RECOMMENDED_ATTRIBUTES = [
    "service.version",  # Service/application version
    "deployment.environment",  # Environment (dev, staging, prod)
]


def validate_resource_attributes(
    attributes: Dict[str, Any],
    model_category: str = "internal",
    model_type: str = "regression",
    strict: bool = True,
) -> None:
    """Validate that all required resource attributes are present.

    Uses the two-field taxonomy (model_category + model_type) to determine
    which set of required attributes to validate against.

    Args:
        attributes: Dictionary of resource attributes to validate
        model_category: High-level category ("internal" or "vendor")
        model_type: Specific type ("regression", "time-series", "classification", "generative", "agentic")
        strict: If True, raise error on missing required attributes

    Raises:
        ValidationError: If required attributes are missing (strict mode only)

    Example:
        >>> attrs = {
        ...     "service.name": "my-model",
        ...     "model.id": "mdl-001",
        ...     "model.version": "1.0.0",
        ...     "team.name": "ml-team"
        ... }
        >>> validate_resource_attributes(attrs, "internal", "regression")  # OK
        >>> validate_resource_attributes({"service.name": "test"}, "internal", "regression")  # Raises ValidationError
    """
    from ..utils.routing import get_metric_prefix

    # SECURITY: Input validation
    if not isinstance(attributes, dict):
        raise ValidationError(f"Attributes must be a dictionary, got {type(attributes)}")

    # Non-strict mode: skip validation (but still check input types)
    if not strict:
        return

    # Determine metric prefix from taxonomy (determines required attributes)
    try:
        prefix = get_metric_prefix(model_category, model_type)
    except ValueError as e:
        raise ValidationError(f"Invalid taxonomy combination: {e}") from e

    # Get required attributes for this prefix
    required = REQUIRED_ATTRIBUTES[prefix]

    # Check for missing attributes
    missing = []
    for attr_name in required:
        value = attributes.get(attr_name)

        # SECURITY: Check for None, empty strings, and validate string length
        if value is None or value == "":
            missing.append(attr_name)
        elif isinstance(value, str):
            # SECURITY: Limit attribute value length to prevent DoS
            MAX_ATTRIBUTE_VALUE_LENGTH = 1024
            if len(value) > MAX_ATTRIBUTE_VALUE_LENGTH:
                raise ValidationError(
                    f"Attribute '{attr_name}' value exceeds maximum length of "
                    f"{MAX_ATTRIBUTE_VALUE_LENGTH} characters. Got {len(value)} characters."
                )

    # Raise error if any required attributes are missing
    if missing:
        _raise_missing_attributes_error(missing, prefix, attributes, model_category, model_type)


def _raise_missing_attributes_error(
    missing: List[str], prefix: str, provided: Dict[str, Any], model_category: str, model_type: str
) -> None:
    """Raise a detailed ValidationError for missing attributes.

    Args:
        missing: List of missing attribute names
        prefix: The metric prefix (model, genai, vendor)
        provided: The attributes that were provided
        model_category: High-level category
        model_type: Specific model type
    """
    error_msg = (
        f"Missing required resource attributes for {prefix}.* metrics "
        f"(model_category='{model_category}', model_type='{model_type}'):\n\n"
    )

    # List missing attributes
    for attr in missing:
        error_msg += f"  - {attr}\n"

    error_msg += "\n"
    error_msg += f"Required attributes for '{prefix}.*' metrics:\n"
    for attr in REQUIRED_ATTRIBUTES[prefix]:
        status = "✓" if attr in provided and provided[attr] is not None else "✗"
        error_msg += f"  {status} {attr}\n"

    # Provide configuration example
    error_msg += "\n"
    error_msg += "Example configuration:\n"
    if prefix == "model":
        error_msg += (
            "  client = MCAClient(\n"
            "      service_name='readmission-model',\n"
            "      model_category='internal',\n"
            "      model_type='regression',\n"
            "      model_id='mdl-001',\n"
            "      model_version='1.0.0',\n"
            "      team_name='clinical-ai'\n"
            "  )\n"
        )
    elif prefix == "genai":
        error_msg += (
            "  client = MCAClient(\n"
            "      service_name='clinical-note-summarizer',\n"
            "      model_category='internal',\n"
            "      model_type='generative',\n"
            "      llm_provider='openai',\n"
            "      llm_model='gpt-4',\n"
            "      team_name='clinical-ai'\n"
            "  )\n"
        )
    else:  # vendor
        error_msg += (
            "  client = MCAClient(\n"
            "      service_name='vendor-epic-sepsis',\n"
            "      model_category='vendor',\n"
            "      model_type='classification',\n"
            "      vendor_name='epic',\n"
            "      team_name='integrations'\n"
            "  )\n"
        )

    error_msg += "\n" "To disable validation, set strict_validation=False in MCAClient."

    raise ValidationError(error_msg)


def get_required_attributes(
    model_category: str = "internal", model_type: str = "regression"
) -> List[str]:
    """Get list of required attributes for a model taxonomy.

    Uses the two-field taxonomy to determine the metric prefix and
    return the corresponding required attributes.

    Args:
        model_category: High-level category ("internal" or "vendor")
        model_type: Specific type ("regression", "time-series", "classification", "generative", "agentic")

    Returns:
        List of required attribute names

    Raises:
        ValidationError: If taxonomy combination is invalid

    Example:
        >>> get_required_attributes("internal", "regression")
        ['service.name', 'model.id', 'model.version', 'team.name']
        >>> get_required_attributes("internal", "generative")
        ['service.name', 'llm.provider', 'llm.model', 'team.name']
    """
    from ..utils.routing import get_metric_prefix

    try:
        prefix = get_metric_prefix(model_category, model_type)
    except ValueError as e:
        raise ValidationError(f"Invalid taxonomy combination: {e}") from e

    return REQUIRED_ATTRIBUTES[prefix].copy()


def get_recommended_attributes() -> List[str]:
    """Get list of recommended attributes for all model types.

    Returns:
        List of recommended attribute names

    Example:
        >>> get_recommended_attributes()
        ['service.version', 'deployment.environment']
    """
    return RECOMMENDED_ATTRIBUTES.copy()


# Additional security validation functions for attribute overflow protection
MAX_ATTRIBUTE_LENGTH = 1024  # Maximum characters per attribute value
MAX_ATTRIBUTE_COUNT = 128  # Maximum attributes per span/metric
MAX_KEY_LENGTH = 128  # Maximum characters per attribute key
MAX_NESTING_DEPTH = 5  # Maximum nesting depth for structured attributes


def validate_attribute_length(key: str, value: Any) -> bool:
    """Validate attribute key and value lengths.

    Args:
        key: Attribute key to validate
        value: Attribute value to validate

    Returns:
        True if valid

    Raises:
        ValidationError: If key or value exceeds limits
    """
    # Validate key length
    if len(key) > MAX_KEY_LENGTH:
        raise ValidationError(
            f"Attribute key exceeds maximum length ({MAX_KEY_LENGTH} characters). "
            f"Key: {key[:50]}..."
        )

    # Validate value length
    value_str = str(value)
    if len(value_str) > MAX_ATTRIBUTE_LENGTH:
        raise ValidationError(
            f"Attribute value for '{key}' exceeds maximum length "
            f"({MAX_ATTRIBUTE_LENGTH} characters). Got {len(value_str)} characters."
        )

    return True


def validate_attribute_count(attributes: Dict[str, Any]) -> bool:
    """Validate attribute count per span/metric.

    Args:
        attributes: Dictionary of attributes to validate

    Returns:
        True if valid

    Raises:
        ValidationError: If attribute count exceeds limit
    """
    if len(attributes) > MAX_ATTRIBUTE_COUNT:
        raise ValidationError(
            f"Attribute count exceeds maximum ({MAX_ATTRIBUTE_COUNT}). "
            f"Got {len(attributes)} attributes."
        )

    return True


def validate_attribute_structure(attributes: Any, depth: int = 0) -> bool:
    """Validate attribute structure to prevent deep nesting.

    Args:
        attributes: Attribute structure to validate
        depth: Current nesting depth (internal)

    Returns:
        True if valid

    Raises:
        ValidationError: If nesting depth exceeds limit
    """
    if depth > MAX_NESTING_DEPTH:
        raise ValidationError(f"Attribute nesting depth exceeds maximum ({MAX_NESTING_DEPTH}).")

    if isinstance(attributes, dict):
        for key, value in attributes.items():
            if isinstance(value, dict):
                validate_attribute_structure(value, depth + 1)

    return True
