"""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.
"""

import logging
from typing import Any, Dict, List

from ..utils.exceptions import ValidationError
from ..utils.routing import get_metric_prefix

logger = logging.getLogger(__name__)

# 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_minimal_attributes(attributes: Dict[str, Any]) -> None:
    """Validate minimal required attributes (service.name only).

    v0.7.0: Non-strict mode only requires service.name to be present.

    Args:
        attributes: Dictionary of resource attributes to validate

    Raises:
        ValidationError: If service.name is missing or invalid
    """
    service_name = attributes.get("service.name")

    if service_name is None or service_name == "":
        raise ValidationError(
            "Missing required attribute: 'service.name'\n\n"
            "The 'service.name' attribute is always required (OpenTelemetry spec).\n"
            "Example:\n"
            "  client = MCAClient(service_name='my-model')\n"
        )

    # SECURITY: Enforce type checking to prevent downstream crashes
    if not isinstance(service_name, str):
        raise ValidationError(
            f"Attribute 'service.name' must be a string, got {type(service_name).__name__}."
        )

    # SECURITY: Validate service.name length
    if len(service_name) > 1024:
        raise ValidationError(
            f"Attribute 'service.name' value exceeds maximum length of 1024 characters. "
            f"Got {len(service_name)} characters."
        )


def _log_missing_recommended_attributes(
    attributes: Dict[str, Any],
    model_category: str,
    model_type: str,
    logger: logging.Logger,
) -> None:
    """Log INFO messages for missing model-type-specific attributes in non-strict mode.

    v0.7.0: Provides helpful guidance without blocking initialization.

    Args:
        attributes: Dictionary of resource attributes
        model_category: High-level category
        model_type: Specific model type
        logger: Logger instance
    """
    try:
        prefix = get_metric_prefix(model_category, model_type)
    except ValueError as e:
        raise ValidationError(f"Invalid model taxonomy provided: {e}")

    required = REQUIRED_ATTRIBUTES.get(prefix, [])
    if not required:
        return  # No required attributes mapped for this prefix
    missing = []

    for attr_name in required:
        value = attributes.get(attr_name)
        if value is None or value == "":
            # Skip service.name since it's already validated
            if attr_name != "service.name":
                missing.append(attr_name)

    if missing:
        logger.info(
            f"Optional fields not provided (model_category='{model_category}', model_type='{model_type}'): "
            f"{', '.join(missing)}. "
            f"Consider providing these fields to enrich your telemetry and improve metrics tracking."
        )


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.

    v0.7.0: Default changed from strict=True to strict=False for better developer experience.
    v0.7.1: Default reverted to strict=True for consistent validation behavior.

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

    Validation Modes:
    - strict=True (default): Requires all model-type-specific fields.
    - strict=False: Skips attribute validation; only validates taxonomy and logs missing fields.

    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. If False, only validate taxonomy.

    Raises:
        ValidationError: If taxonomy is invalid, or if required attributes are missing (strict mode)

    Example:
        >>> # Minimal config (strict=False)
        >>> validate_resource_attributes({}, "internal", "regression", strict=False)  # OK
        >>>
        >>> # Production config (strict=True, default)
        >>> 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
    """
    # SECURITY: Input validation
    if not isinstance(attributes, dict):
        raise ValidationError(f"Attributes must be a dictionary, got {type(attributes)}")

    # Always validate taxonomy (regardless of strict mode)
    try:
        prefix = get_metric_prefix(model_category, model_type)
    except ValueError as e:
        raise ValidationError(f"Invalid taxonomy combination: {e}") from e

    # Non-strict mode: skip attribute validation, just log missing fields
    if not strict:
        required = REQUIRED_ATTRIBUTES.get(prefix, [])
        missing = [a for a in required if a != "service.name" and not attributes.get(a)]
        if missing:
            logger.info(
                f"Optional fields not provided (model_category='{model_category}', model_type='{model_type}'): "
                f"{', '.join(missing)}. "
                f"Consider providing these fields to enrich your telemetry and improve metrics tracking."
            )
        return

    # Strict mode: Validate all required attributes
    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 += (
        "\nStrict validation is enabled. This happens when:\n"
        "  - registry_url is configured (auto-enabled for production)\n"
        "  - Or explicitly set via MCAClient(strict_validation=True)\n\n"
        "To bypass this while using a registry, you must explicitly pass "
        "strict_validation=False to your MCAClient, or provide the missing fields."
    )

    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
