"""
Alerts Pydantic Schemas.

Type-safe models for the LightWave alert and notification system,
validated against schema/definitions/alerts.yaml at runtime.

This module provides:
- AlertsConfig: Runtime access to YAML configuration
- Alert: A single alert instance
- NotificationChannel: Channel for delivering alerts
- AlertRule: Rules for escalation, aggregation, quiet hours
- DigestConfig: Scheduled digest configuration

Usage:
    from lightwave.schema.pydantic.models.alerts import (
        AlertsConfig,
        Alert,
        AlertDelivery,
    )

    # Check valid alert types
    alert_types = AlertsConfig.get_alert_types()

    # Create an alert
    alert = Alert(
        alert_type="sprint_at_risk",
        severity="high",
        title="Sprint Alpha at risk",
        data={"sprint_name": "Alpha", "completion_percentage": 45},
    )
"""

from __future__ import annotations

from datetime import datetime
from functools import lru_cache
from pathlib import Path
from typing import Any

import yaml
from pydantic import Field, field_validator, model_validator

from lightwave.schema.pydantic.core.base import LightwaveBaseSchema

# =============================================================================
# YAML CONFIGURATION LOADER
# =============================================================================


def _get_yaml_path() -> Path:
    """Get path to alerts.yaml (from __index.yaml registry)."""
    from lightwave.schema.core.paths import get_sst_path

    return get_sst_path("alerts")


@lru_cache(maxsize=1)
def _load_alerts_yaml() -> dict[str, Any]:
    """Load and cache alerts.yaml."""
    yaml_path = _get_yaml_path()
    if not yaml_path.exists():
        return {
            "severity_levels": {"options": []},
            "channels": {"options": []},
            "categories": {},
            "alert_types": {},
            "rules": {},
            "digests": {},
            "retention": {},
        }
    with open(yaml_path) as f:
        return yaml.safe_load(f)


class AlertsConfig:
    """
    Runtime configuration loaded from alerts.yaml.

    Provides type-safe access to YAML-defined values with caching.
    """

    @classmethod
    def _yaml(cls) -> dict[str, Any]:
        """Get cached YAML data."""
        return _load_alerts_yaml()

    # -------------------------------------------------------------------------
    # SEVERITY LEVELS
    # -------------------------------------------------------------------------

    @classmethod
    def get_severity_levels(cls) -> list[str]:
        """Get all valid severity levels."""
        options = cls._yaml().get("severity_levels", {}).get("options", [])
        return [opt["value"] for opt in options]

    @classmethod
    def is_valid_severity(cls, severity: str) -> bool:
        """Check if a severity level is valid."""
        return severity in cls.get_severity_levels()

    @classmethod
    def get_severity_config(cls, severity: str) -> dict[str, Any]:
        """Get configuration for a severity level."""
        options = cls._yaml().get("severity_levels", {}).get("options", [])
        for opt in options:
            if opt["value"] == severity:
                return opt
        return {}

    @classmethod
    def requires_immediate_action(cls, severity: str) -> bool:
        """Check if severity requires immediate action."""
        config = cls.get_severity_config(severity)
        return config.get("requires_immediate_action", False)

    # -------------------------------------------------------------------------
    # CHANNELS
    # -------------------------------------------------------------------------

    @classmethod
    def get_channels(cls) -> list[str]:
        """Get all channel names."""
        options = cls._yaml().get("channels", {}).get("options", [])
        return [opt["value"] for opt in options]

    @classmethod
    def get_enabled_channels(cls) -> list[str]:
        """Get only enabled channels."""
        options = cls._yaml().get("channels", {}).get("options", [])
        return [opt["value"] for opt in options if opt.get("enabled", True)]

    @classmethod
    def is_valid_channel(cls, channel: str) -> bool:
        """Check if a channel is valid."""
        return channel in cls.get_channels()

    @classmethod
    def is_channel_enabled(cls, channel: str) -> bool:
        """Check if a channel is enabled."""
        return channel in cls.get_enabled_channels()

    @classmethod
    def get_channel_config(cls, channel: str) -> dict[str, Any]:
        """Get configuration for a channel."""
        options = cls._yaml().get("channels", {}).get("options", [])
        for opt in options:
            if opt["value"] == channel:
                return opt.get("config", {})
        return {}

    # -------------------------------------------------------------------------
    # CATEGORIES
    # -------------------------------------------------------------------------

    @classmethod
    def get_categories(cls) -> list[str]:
        """Get all category names."""
        return list(cls._yaml().get("categories", {}).keys())

    @classmethod
    def is_valid_category(cls, category: str) -> bool:
        """Check if a category is valid."""
        return category in cls.get_categories()

    @classmethod
    def get_category_config(cls, category: str) -> dict[str, Any]:
        """Get configuration for a category."""
        return cls._yaml().get("categories", {}).get(category, {})

    @classmethod
    def get_category_default_channels(cls, category: str) -> list[str]:
        """Get default channels for a category."""
        config = cls.get_category_config(category)
        return config.get("default_channels", ["in_app"])

    @classmethod
    def get_category_default_severity(cls, category: str) -> str:
        """Get default severity for a category."""
        config = cls.get_category_config(category)
        return config.get("default_severity", "medium")

    # -------------------------------------------------------------------------
    # ALERT TYPES
    # -------------------------------------------------------------------------

    @classmethod
    def get_alert_types(cls) -> list[str]:
        """Get all alert type names."""
        return list(cls._yaml().get("alert_types", {}).keys())

    @classmethod
    def is_valid_alert_type(cls, alert_type: str) -> bool:
        """Check if an alert type is valid."""
        return alert_type in cls.get_alert_types()

    @classmethod
    def get_alert_type_config(cls, alert_type: str) -> dict[str, Any]:
        """Get configuration for an alert type."""
        return cls._yaml().get("alert_types", {}).get(alert_type, {})

    @classmethod
    def get_alert_template(cls, alert_type: str) -> str:
        """Get the message template for an alert type."""
        config = cls.get_alert_type_config(alert_type)
        return config.get("template", "")

    @classmethod
    def get_alert_throttle_minutes(cls, alert_type: str) -> int:
        """Get throttle duration for an alert type."""
        config = cls.get_alert_type_config(alert_type)
        return config.get("throttle_minutes", 60)

    # -------------------------------------------------------------------------
    # RULES
    # -------------------------------------------------------------------------

    @classmethod
    def get_rules(cls) -> dict[str, Any]:
        """Get all notification rules."""
        return cls._yaml().get("rules", {})

    @classmethod
    def is_escalation_enabled(cls) -> bool:
        """Check if escalation is enabled."""
        return cls.get_rules().get("escalation", {}).get("enabled", False)

    @classmethod
    def is_aggregation_enabled(cls) -> bool:
        """Check if aggregation is enabled."""
        return cls.get_rules().get("aggregation", {}).get("enabled", False)

    @classmethod
    def get_aggregation_window_minutes(cls) -> int:
        """Get aggregation window in minutes."""
        return cls.get_rules().get("aggregation", {}).get("window_minutes", 15)

    @classmethod
    def is_quiet_hours_enabled(cls) -> bool:
        """Check if quiet hours are enabled."""
        return cls.get_rules().get("quiet_hours", {}).get("enabled", False)

    @classmethod
    def get_quiet_hours(cls) -> tuple[int, int]:
        """Get quiet hours start and end (24h format)."""
        quiet = cls.get_rules().get("quiet_hours", {})
        return (quiet.get("start_hour", 22), quiet.get("end_hour", 7))

    # -------------------------------------------------------------------------
    # DIGESTS
    # -------------------------------------------------------------------------

    @classmethod
    def get_digests(cls) -> dict[str, Any]:
        """Get all digest configurations."""
        return cls._yaml().get("digests", {})

    @classmethod
    def get_digest_config(cls, digest_name: str) -> dict[str, Any]:
        """Get configuration for a specific digest."""
        return cls.get_digests().get(digest_name, {})

    @classmethod
    def is_digest_enabled(cls, digest_name: str) -> bool:
        """Check if a digest is enabled."""
        config = cls.get_digest_config(digest_name)
        return config.get("enabled", False)

    # -------------------------------------------------------------------------
    # RETENTION
    # -------------------------------------------------------------------------

    @classmethod
    def get_alert_history_days(cls) -> int:
        """Get alert history retention in days."""
        return cls._yaml().get("retention", {}).get("alert_history_days", 90)

    @classmethod
    def get_resolved_alerts_days(cls) -> int:
        """Get resolved alerts retention in days."""
        return cls._yaml().get("retention", {}).get("resolved_alerts_days", 30)

    # -------------------------------------------------------------------------
    # CACHE MANAGEMENT
    # -------------------------------------------------------------------------

    @classmethod
    def clear_cache(cls) -> None:
        """Clear the YAML cache (for testing)."""
        _load_alerts_yaml.cache_clear()


# =============================================================================
# ALERT MODEL
# =============================================================================


class Alert(LightwaveBaseSchema):
    """
    A single alert instance.

    Attributes:
        alert_type: Type of alert from alerts.yaml
        severity: Severity level
        title: Short alert title
        message: Full alert message (rendered from template)
        data: Data used to render the template
        category: Alert category
        channels: Channels to deliver to
        entity_type: Related entity type (optional)
        entity_id: Related entity ID (optional)
        created_at: When the alert was created
        expires_at: When the alert expires (optional)
        acknowledged: Whether the alert has been acknowledged
        acknowledged_by: Who acknowledged the alert
        acknowledged_at: When the alert was acknowledged
        resolved: Whether the alert is resolved
        resolved_at: When the alert was resolved
        metadata: Additional metadata
    """

    alert_type: str = Field(..., description="Alert type from alerts.yaml")
    severity: str = Field("medium", description="Severity level")
    title: str = Field(..., min_length=1, max_length=200, description="Alert title")
    message: str = Field("", description="Rendered alert message")
    data: dict[str, Any] = Field(default_factory=dict, description="Template data")
    category: str | None = Field(None, description="Alert category")
    channels: list[str] = Field(default_factory=list, description="Delivery channels")
    entity_type: str | None = Field(None, description="Related entity type")
    entity_id: str | None = Field(None, description="Related entity ID")
    created_at: datetime = Field(default_factory=datetime.utcnow, description="Created time")
    expires_at: datetime | None = Field(None, description="Expiration time")
    acknowledged: bool = Field(False, description="Whether acknowledged")
    acknowledged_by: str | None = Field(None, description="Who acknowledged")
    acknowledged_at: datetime | None = Field(None, description="When acknowledged")
    resolved: bool = Field(False, description="Whether resolved")
    resolved_at: datetime | None = Field(None, description="When resolved")
    metadata: dict[str, Any] = Field(default_factory=dict, description="Additional metadata")

    @field_validator("alert_type")
    @classmethod
    def validate_alert_type(cls, v: str) -> str:
        """Validate alert type against YAML."""
        if not AlertsConfig.is_valid_alert_type(v):
            valid = AlertsConfig.get_alert_types()
            raise ValueError(f"Invalid alert_type '{v}'. Valid types: {valid}")
        return v

    @field_validator("severity")
    @classmethod
    def validate_severity(cls, v: str) -> str:
        """Validate severity against YAML."""
        if not AlertsConfig.is_valid_severity(v):
            valid = AlertsConfig.get_severity_levels()
            raise ValueError(f"Invalid severity '{v}'. Valid levels: {valid}")
        return v

    @field_validator("channels")
    @classmethod
    def validate_channels(cls, v: list[str]) -> list[str]:
        """Validate all channels are valid."""
        invalid = [c for c in v if not AlertsConfig.is_valid_channel(c)]
        if invalid:
            valid = AlertsConfig.get_channels()
            raise ValueError(f"Invalid channels: {invalid}. Valid channels: {valid}")
        return v

    @field_validator("category")
    @classmethod
    def validate_category(cls, v: str | None) -> str | None:
        """Validate category against YAML."""
        if v is not None and not AlertsConfig.is_valid_category(v):
            valid = AlertsConfig.get_categories()
            raise ValueError(f"Invalid category '{v}'. Valid categories: {valid}")
        return v

    @model_validator(mode="after")
    def populate_defaults(self) -> "Alert":
        """Populate defaults from alert type config."""
        config = AlertsConfig.get_alert_type_config(self.alert_type)

        # Set category from config if not provided
        if self.category is None and "category" in config:
            object.__setattr__(self, "category", config["category"])

        # Set channels from config if empty
        if not self.channels:
            channels = config.get("channels", [])
            if not channels and self.category:
                channels = AlertsConfig.get_category_default_channels(self.category)
            object.__setattr__(self, "channels", channels)

        # Render message from template if not provided
        if not self.message and self.data:
            template = AlertsConfig.get_alert_template(self.alert_type)
            if template:
                try:
                    rendered = template.format(**self.data)
                    object.__setattr__(self, "message", rendered)
                except KeyError:
                    # Missing template variables, leave message empty
                    pass

        return self

    def render_message(self) -> str:
        """Render the alert message from template and data."""
        template = AlertsConfig.get_alert_template(self.alert_type)
        if not template:
            return self.title
        try:
            return template.format(**self.data)
        except KeyError as e:
            return f"{self.title} (missing data: {e})"

    def requires_immediate_action(self) -> bool:
        """Check if this alert requires immediate action."""
        return AlertsConfig.requires_immediate_action(self.severity)

    def get_throttle_minutes(self) -> int:
        """Get throttle duration for this alert type."""
        return AlertsConfig.get_alert_throttle_minutes(self.alert_type)


# =============================================================================
# ALERT DELIVERY
# =============================================================================


class AlertDelivery(LightwaveBaseSchema):
    """
    Record of an alert delivery attempt.

    Attributes:
        alert_id: ID of the alert
        channel: Channel used for delivery
        status: Delivery status
        delivered_at: When delivery was attempted
        error_message: Error message if failed
        retry_count: Number of retry attempts
        metadata: Additional delivery metadata
    """

    alert_id: str = Field(..., min_length=1, description="Alert identifier")
    channel: str = Field(..., description="Delivery channel")
    status: str = Field("pending", description="Delivery status")
    delivered_at: datetime | None = Field(None, description="Delivery time")
    error_message: str | None = Field(None, description="Error message")
    retry_count: int = Field(0, ge=0, description="Retry attempts")
    metadata: dict[str, Any] = Field(default_factory=dict, description="Delivery metadata")

    @field_validator("channel")
    @classmethod
    def validate_channel(cls, v: str) -> str:
        """Validate channel against YAML."""
        if not AlertsConfig.is_valid_channel(v):
            valid = AlertsConfig.get_channels()
            raise ValueError(f"Invalid channel '{v}'. Valid channels: {valid}")
        return v

    @field_validator("status")
    @classmethod
    def validate_status(cls, v: str) -> str:
        """Validate delivery status."""
        valid = ["pending", "sent", "delivered", "failed", "skipped"]
        if v not in valid:
            raise ValueError(f"Invalid status '{v}'. Valid statuses: {valid}")
        return v


# =============================================================================
# ALERT AGGREGATION
# =============================================================================


class AlertAggregation(LightwaveBaseSchema):
    """
    Aggregated alerts grouped by type/category.

    Attributes:
        alert_type: Type of alerts aggregated
        category: Category of alerts
        count: Number of alerts aggregated
        first_alert_at: When first alert occurred
        last_alert_at: When last alert occurred
        sample_alerts: Sample alert IDs
        summary: Aggregated summary message
    """

    alert_type: str = Field(..., description="Alert type")
    category: str | None = Field(None, description="Alert category")
    count: int = Field(..., ge=1, description="Alert count")
    first_alert_at: datetime = Field(..., description="First alert time")
    last_alert_at: datetime = Field(..., description="Last alert time")
    sample_alerts: list[str] = Field(default_factory=list, description="Sample alert IDs")
    summary: str = Field("", description="Aggregated summary")

    @field_validator("alert_type")
    @classmethod
    def validate_alert_type(cls, v: str) -> str:
        """Validate alert type against YAML."""
        if not AlertsConfig.is_valid_alert_type(v):
            valid = AlertsConfig.get_alert_types()
            raise ValueError(f"Invalid alert_type '{v}'. Valid types: {valid}")
        return v


# =============================================================================
# USER NOTIFICATION PREFERENCES
# =============================================================================


class NotificationPreferences(LightwaveBaseSchema):
    """
    User's notification preferences.

    Attributes:
        user_id: User identifier
        enabled_channels: Channels the user wants to receive
        disabled_categories: Categories the user opted out of
        severity_filter: Minimum severity to notify
        quiet_hours_enabled: Whether to respect quiet hours
        email_digest_only: Whether to batch emails into digests
    """

    user_id: str = Field(..., min_length=1, description="User identifier")
    enabled_channels: list[str] = Field(default_factory=list, description="Enabled channels")
    disabled_categories: list[str] = Field(default_factory=list, description="Disabled categories")
    severity_filter: str | None = Field(None, description="Minimum severity")
    quiet_hours_enabled: bool = Field(True, description="Respect quiet hours")
    email_digest_only: bool = Field(False, description="Batch emails")

    @field_validator("enabled_channels")
    @classmethod
    def validate_enabled_channels(cls, v: list[str]) -> list[str]:
        """Validate all channels are valid."""
        invalid = [c for c in v if not AlertsConfig.is_valid_channel(c)]
        if invalid:
            valid = AlertsConfig.get_channels()
            raise ValueError(f"Invalid channels: {invalid}. Valid channels: {valid}")
        return v

    @field_validator("disabled_categories")
    @classmethod
    def validate_disabled_categories(cls, v: list[str]) -> list[str]:
        """Validate all categories are valid and opt-out-able."""
        invalid = [c for c in v if not AlertsConfig.is_valid_category(c)]
        if invalid:
            valid = AlertsConfig.get_categories()
            raise ValueError(f"Invalid categories: {invalid}. Valid categories: {valid}")
        return v

    @field_validator("severity_filter")
    @classmethod
    def validate_severity_filter(cls, v: str | None) -> str | None:
        """Validate severity filter against YAML."""
        if v is not None and not AlertsConfig.is_valid_severity(v):
            valid = AlertsConfig.get_severity_levels()
            raise ValueError(f"Invalid severity_filter '{v}'. Valid levels: {valid}")
        return v

    def should_notify(self, alert: Alert) -> bool:
        """Check if user should be notified for an alert."""
        # Check category opt-out
        if alert.category in self.disabled_categories:
            return False

        # Check severity filter
        if self.severity_filter:
            severity_order = AlertsConfig.get_severity_levels()
            if severity_order:
                alert_idx = (
                    severity_order.index(alert.severity) if alert.severity in severity_order else len(severity_order)
                )
                filter_idx = severity_order.index(self.severity_filter) if self.severity_filter in severity_order else 0
                if alert_idx > filter_idx:
                    return False

        return True


# =============================================================================
# DIGEST SUMMARY
# =============================================================================


class DigestSummary(LightwaveBaseSchema):
    """
    A digest summary combining multiple alerts.

    Attributes:
        digest_type: Type of digest (daily_standup, weekly_sprint, etc.)
        period_start: Start of digest period
        period_end: End of digest period
        alert_counts: Count of alerts by category
        highlights: Key highlights
        data: Data for rendering digest template
        generated_at: When digest was generated
    """

    digest_type: str = Field(..., min_length=1, description="Digest type")
    period_start: datetime = Field(..., description="Period start")
    period_end: datetime = Field(..., description="Period end")
    alert_counts: dict[str, int] = Field(default_factory=dict, description="Alerts by category")
    highlights: list[str] = Field(default_factory=list, description="Key highlights")
    data: dict[str, Any] = Field(default_factory=dict, description="Digest data")
    generated_at: datetime = Field(default_factory=datetime.utcnow, description="Generation time")

    @model_validator(mode="after")
    def validate_period(self) -> "DigestSummary":
        """Ensure period_end is after period_start."""
        if self.period_end <= self.period_start:
            raise ValueError("period_end must be after period_start")
        return self


# =============================================================================
# MODULE EXPORTS
# =============================================================================

__all__ = [
    "AlertsConfig",
    "Alert",
    "AlertDelivery",
    "AlertAggregation",
    "NotificationPreferences",
    "DigestSummary",
]
