"""
Infrastructure configuration schemas from infrastructure.yaml.

These schemas define the structure for ECS, ALB, database, cache, and
domain-level infrastructure configuration.

SST File: lightwave/schema/definitions/infrastructure.yaml
SST Files: lightwave/schema/definitions/domains/*.yaml
Database Model: apps.core.infra.platform.models.DomainDeployment

Usage:
    from lightwave.schema.pydantic.sst import (
        InfrastructureSpec,
        DomainConfig,
        ECSConfig,
        SharedResources,
    )

    # Load full infrastructure spec
    spec = get_infrastructure_spec()
    print(spec.shared.database.instance_class_default)  # "db.t3.medium"

    # Load specific domain config
    domain = get_domain_config("cineos")
    print(domain.ecs.cpu)  # 512

    # Get all active domains
    domains = get_all_domain_configs()
    for d in domains:
        print(f"{d.domain.slug}: {d.domain.fqdn}")
"""

from __future__ import annotations

from enum import Enum
from functools import lru_cache
from pathlib import Path
from typing import Any, ClassVar, Literal

import yaml
from pydantic import Field, field_validator

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

# =============================================================================
# ENUMS
# =============================================================================


class DeploymentMode(str, Enum):
    """How a domain is deployed in infrastructure."""

    SHARED = "shared"  # Runs within lightwave-platform ECS
    SPLIT = "split"  # Has dedicated ECS service


class DomainStatus(str, Enum):
    """Domain deployment status."""

    ACTIVE = "active"
    PENDING = "pending"
    SUSPENDED = "suspended"
    DEPRECATED = "deprecated"


class HealthStatus(str, Enum):
    """Infrastructure health status."""

    HEALTHY = "healthy"
    DEGRADED = "degraded"
    UNHEALTHY = "unhealthy"
    DEPLOYING = "deploying"
    UNKNOWN = "unknown"


class EnvironmentType(str, Enum):
    """Environment type classification."""

    DEVELOPMENT = "development"
    STAGING = "staging"
    PRODUCTION = "production"


# =============================================================================
# SHARED RESOURCES SCHEMAS
# =============================================================================


class DatabaseConfig(SSTSchema):
    """PostgreSQL database configuration."""

    type: Literal["rds_postgresql"] = "rds_postgresql"
    engine_version: str = Field("17", description="PostgreSQL version")
    instance_class_default: str = Field("db.t3.medium", description="Default instance type")
    multi_az_prod: bool = Field(True, description="Multi-AZ in production")
    multi_az_staging: bool = Field(False, description="Multi-AZ in staging")
    allocated_storage_gb: int = Field(100, description="Initial storage in GB")
    max_allocated_storage_gb: int = Field(500, description="Max autoscale storage in GB")
    backup_retention_days: int = Field(7, description="Backup retention period")
    deletion_protection_prod: bool = Field(True, description="Deletion protection in prod")
    performance_insights: bool = Field(True, description="Enable Performance Insights")
    notes: str | None = None


class CacheConfig(SSTSchema):
    """Redis cache configuration."""

    type: Literal["elasticache_redis"] = "elasticache_redis"
    engine_version: str = Field("7.1", description="Redis version")
    node_type_default: str = Field("cache.t3.micro", description="Default node type")
    num_cache_nodes: int = Field(1, description="Number of cache nodes")
    automatic_failover: bool = Field(False, description="Enable automatic failover")
    at_rest_encryption: bool = Field(True, description="Encrypt data at rest")
    transit_encryption: bool = Field(True, description="Encrypt data in transit")
    notes: str | None = None


class RegistryConfig(SSTSchema):
    """ECR registry configuration."""

    type: Literal["ecr"] = "ecr"
    name: str = Field("lightwave-platform", description="Repository name")
    image_tag_mutability: Literal["MUTABLE", "IMMUTABLE"] = "MUTABLE"
    scan_on_push: bool = Field(True, description="Scan images on push")
    encryption_type: Literal["AES256", "KMS"] = "AES256"
    lifecycle_policy: dict[str, Any] | None = None
    notes: str | None = None


class VPCEndpointsConfig(SSTSchema):
    """VPC endpoints configuration."""

    description: str = "Required VPC endpoints for private networking"
    required: list[str] = Field(
        default_factory=lambda: [
            "com.amazonaws.{region}.ecr.api",
            "com.amazonaws.{region}.ecr.dkr",
            "com.amazonaws.{region}.logs",
            "com.amazonaws.{region}.s3",
            "com.amazonaws.{region}.secretsmanager",
        ]
    )


class SharedResources(SSTSchema):
    """Shared infrastructure resources used by all domains."""

    description: str = "Shared infrastructure used by all domains"
    database: DatabaseConfig
    cache: CacheConfig
    registry: RegistryConfig
    vpc_endpoints: VPCEndpointsConfig | None = None


# =============================================================================
# ECS CONFIGURATION SCHEMAS
# =============================================================================


class HealthCheckConfig(SSTSchema):
    """ECS health check configuration."""

    path: str = Field("/health/", description="Health check endpoint path")
    interval: int = Field(30, description="Check interval in seconds")
    timeout: int = Field(5, description="Check timeout in seconds")
    healthy_threshold: int = Field(2, description="Consecutive healthy checks required")
    unhealthy_threshold: int = Field(3, description="Consecutive unhealthy checks required")


class ContainerConfig(SSTSchema):
    """ECS container configuration."""

    name: str = Field(..., description="Container name")
    port: int = Field(8000, description="Container port")
    environment: dict[str, str] = Field(default_factory=dict)


class ECSConfig(SSTSchema):
    """ECS service configuration."""

    cpu: int = Field(256, description="CPU units (256 = 0.25 vCPU)")
    memory: int = Field(512, description="Memory in MB")
    desired_count: int = Field(1, description="Desired task count")
    min_capacity: int = Field(1, description="Minimum autoscale capacity")
    max_capacity: int = Field(4, description="Maximum autoscale capacity")
    health_check_path: str = Field("/health/", description="Health check path")
    health_check_grace_period_seconds: int = Field(60, description="Grace period for health checks")
    deployment_maximum_percent: int = Field(200, description="Max percent during deployment")
    deployment_minimum_healthy_percent: int = Field(100, description="Min healthy percent during deployment")
    enable_execute_command: bool = Field(True, description="Enable ECS Exec")
    architecture: Literal["ARM64", "X86_64"] = "ARM64"
    service_name: str | None = None
    container: ContainerConfig | None = None
    health_check: HealthCheckConfig | None = None

    @field_validator("cpu")
    @classmethod
    def validate_cpu(cls, v: int) -> int:
        """Validate CPU is a valid Fargate value."""
        valid_values = [256, 512, 1024, 2048, 4096, 8192, 16384]
        if v not in valid_values:
            msg = f"CPU must be one of {valid_values}, got {v}"
            raise ValueError(msg)
        return v


# =============================================================================
# ALB CONFIGURATION SCHEMAS
# =============================================================================


class TargetGroupConfig(SSTSchema):
    """ALB target group configuration."""

    name: str | None = None
    health_check_path: str = Field("/health/", description="Health check path")
    stickiness_enabled: bool = Field(False, description="Enable session stickiness")
    stickiness_duration: int = Field(86400, description="Stickiness duration in seconds")


class ALBConfig(SSTSchema):
    """Application Load Balancer configuration."""

    name: str | None = None
    idle_timeout_seconds: int = Field(60, description="Connection idle timeout")
    ssl_policy: str = Field(
        "ELBSecurityPolicy-TLS13-1-2-2021-06",
        description="SSL/TLS policy",
    )
    deregistration_delay: int = Field(30, description="Target deregistration delay")
    health_check_interval: int = Field(30, description="Health check interval")
    health_check_timeout: int = Field(5, description="Health check timeout")
    healthy_threshold: int = Field(2, description="Healthy threshold count")
    unhealthy_threshold: int = Field(3, description="Unhealthy threshold count")
    target_group: TargetGroupConfig | None = None


# =============================================================================
# AUTOSCALING CONFIGURATION
# =============================================================================


class AutoscalingConfig(SSTSchema):
    """ECS autoscaling configuration."""

    min_capacity: int = Field(1, description="Minimum task count")
    max_capacity: int = Field(4, description="Maximum task count")
    target_cpu_utilization: int = Field(70, description="Target CPU utilization %")
    target_memory_utilization: int = Field(80, description="Target memory utilization %")
    scale_in_cooldown: int = Field(300, description="Scale-in cooldown in seconds")
    scale_out_cooldown: int = Field(60, description="Scale-out cooldown in seconds")


# =============================================================================
# LOGGING CONFIGURATION
# =============================================================================


class LoggingConfig(SSTSchema):
    """CloudWatch logging configuration."""

    driver: Literal["awslogs"] = "awslogs"
    retention_days: int = Field(30, description="Log retention in days")
    log_group_pattern: str = Field("/ecs/{cluster}/{service}", description="Log group pattern")


# =============================================================================
# DEFAULT CONFIGURATION
# =============================================================================


class DefaultConfig(SSTSchema):
    """Default configuration for all domains."""

    description: str = "Default ECS service configuration"
    ecs: ECSConfig
    autoscaling: AutoscalingConfig
    alb: ALBConfig
    logging: LoggingConfig


# =============================================================================
# ENVIRONMENT CONFIGURATION
# =============================================================================


class EnvironmentDefinition(SSTSchema):
    """Single environment definition."""

    name: str
    type: EnvironmentType
    aws_account_id: str | None = None
    region: str | None = None
    cluster_name: str | None = None
    vpc_id: str | None = None


class EnvironmentsConfig(SSTSchema):
    """All environments configuration."""

    description: str = "Deployment environments in promotion order"
    promotion_order: list[str] = Field(default_factory=lambda: ["local", "non-prod", "prod"])
    definitions: dict[str, EnvironmentDefinition]


# =============================================================================
# DOMAIN CONFIGURATION SCHEMAS
# =============================================================================


class DomainIdentity(SSTSchema):
    """Domain identity and routing configuration."""

    name: str = Field(..., description="Human-readable domain name")
    slug: str = Field(..., description="URL-safe domain identifier")
    description: str = Field("", description="Domain description")
    tenant_schema: str = Field(..., description="PostgreSQL tenant schema name")
    tld: str = Field(..., description="Top-level domain (io, com, etc.)")
    domains: dict[str, str] = Field(default_factory=dict, description="Domain routing map")
    certificates: dict[str, str] = Field(default_factory=dict, description="ACM certificate ARNs")


class DomainResources(SSTSchema):
    """Domain-specific resource requirements."""

    database: dict[str, Any] = Field(default_factory=dict)
    cache: dict[str, Any] = Field(default_factory=dict)
    storage: dict[str, Any] = Field(default_factory=dict)


class CloudWatchAlarm(SSTSchema):
    """CloudWatch alarm definition."""

    name: str
    metric: str
    threshold: int
    evaluation_periods: int = 3
    comparison: str | None = None


class MonitoringConfig(SSTSchema):
    """Domain monitoring configuration."""

    cloudwatch: dict[str, Any] = Field(default_factory=dict)
    log_groups: list[str] = Field(default_factory=list)


class DomainConfig(SSTSchema):
    """Complete domain infrastructure configuration."""

    domain: DomainIdentity
    ecs: ECSConfig = Field(default_factory=ECSConfig)
    alb: ALBConfig = Field(default_factory=ALBConfig)
    autoscaling: AutoscalingConfig = Field(default_factory=AutoscalingConfig)
    features: dict[str, bool] = Field(default_factory=dict)
    resources: DomainResources = Field(default_factory=DomainResources)
    monitoring: MonitoringConfig = Field(default_factory=MonitoringConfig)


# =============================================================================
# DOMAIN REGISTRY SCHEMAS
# =============================================================================


class DomainRegistryEntry(SSTSchema):
    """Single domain in the registry."""

    deployment_mode: DeploymentMode = DeploymentMode.SHARED
    tenant_schema: str
    fqdn: str
    config_file: str | None = None


class DomainIndexEntry(SSTSchema):
    """Domain entry from __index.yaml."""

    status: DomainStatus = DomainStatus.ACTIVE
    priority: int = Field(10, description="Deployment priority (lower = higher priority)")
    description: str = ""
    tenant_schema: str
    fqdn: str
    app_subdomain: str | None = None
    deployment_mode: DeploymentMode = DeploymentMode.SHARED
    config_file: str | None = None


class DomainRegistry(SSTSchema):
    """Registry of all domains."""

    domains: dict[str, DomainIndexEntry]

    def get_active_domains(self) -> list[DomainIndexEntry]:
        """Get all active domains sorted by priority."""
        return sorted(
            [d for d in self.domains.values() if d.status == DomainStatus.ACTIVE],
            key=lambda d: d.priority,
        )

    def get_domain(self, domain_id: str) -> DomainIndexEntry | None:
        """Get a specific domain by ID."""
        return self.domains.get(domain_id)


# =============================================================================
# DEPLOYMENT MODE SCHEMAS
# =============================================================================


class DeploymentModeInfra(SSTSchema):
    """Infrastructure resources for a deployment mode."""

    ecs_service: str | None = None
    alb: str | None = None
    ecr: str | None = None


class DeploymentModeDefinition(SSTSchema):
    """Deployment mode definition."""

    description: str
    resources: DeploymentModeInfra
    isolation: str
    notes: str | None = None


class DeploymentModesConfig(SSTSchema):
    """Deployment modes configuration wrapper."""

    description: str = "Infrastructure deployment strategies"
    shared: DeploymentModeDefinition | None = None
    split: DeploymentModeDefinition | None = None


# =============================================================================
# INFRASTRUCTURE SPEC (TOP-LEVEL)
# =============================================================================


class InfrastructureSpec(SSTSchema):
    """Complete infrastructure specification from infrastructure.yaml."""

    environments: EnvironmentsConfig
    shared: SharedResources
    defaults: DefaultConfig
    domains: dict[str, Any]  # Registry section
    deployment_modes: DeploymentModesConfig | None = None
    health_status: dict[str, Any] | None = None
    events: dict[str, Any] | None = None
    validation: dict[str, Any] | None = None

    # Class-level config
    SST_KEY: ClassVar[str] = "infrastructure"  # Registry key in __index.yaml


# =============================================================================
# LOADER FUNCTIONS
# =============================================================================


def _get_definitions_dir() -> Path:
    """Get the definitions directory path."""
    from lightwave.schema.core.paths import DEFINITIONS_DIR

    return DEFINITIONS_DIR


@lru_cache(maxsize=1)
def get_infrastructure_spec() -> InfrastructureSpec:
    """
    Load the complete infrastructure specification from YAML.

    Returns:
        InfrastructureSpec: Complete infrastructure configuration

    Raises:
        FileNotFoundError: If infrastructure.yaml doesn't exist
        ValidationError: If YAML doesn't match schema
    """
    from lightwave.schema.core.paths import get_sst_path

    yaml_path = get_sst_path("infrastructure")

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

    with yaml_path.open() as f:
        raw_data = yaml.safe_load(f)

    # Remove _meta from validation (it's metadata, not config)
    data = {k: v for k, v in raw_data.items() if k != "_meta"}

    return InfrastructureSpec.model_validate(data)


@lru_cache(maxsize=1)
def get_domain_registry() -> DomainRegistry:
    """
    Load the domain registry from products/__index.yaml via SST registry.

    Returns:
        DomainRegistry: Registry of all domains
    """
    from lightwave.schema.core.paths import get_sst_path

    index_path = get_sst_path("products_index")

    if not index_path.exists():
        msg = f"Domain index file not found: {index_path}"
        raise FileNotFoundError(msg)

    with index_path.open() as f:
        raw_data = yaml.safe_load(f)

    domains_data = raw_data.get("domains", {})
    return DomainRegistry(domains=domains_data)


def get_domain_config(domain_id: str) -> DomainConfig:
    """
    Load configuration for a specific domain via SST registry.

    Args:
        domain_id: Domain identifier (e.g., "cineos", "createos")

    Returns:
        DomainConfig: Complete domain configuration

    Raises:
        FileNotFoundError: If domain config file doesn't exist
        ValidationError: If YAML doesn't match schema
    """
    from lightwave.schema.core.paths import get_sst_path

    try:
        domain_path = get_sst_path(domain_id)
    except KeyError:
        msg = f"Domain '{domain_id}' not found in SST registry"
        raise FileNotFoundError(msg)

    if not domain_path.exists():
        msg = f"Domain config file not found: {domain_path}"
        raise FileNotFoundError(msg)

    with domain_path.open() as f:
        raw_data = yaml.safe_load(f)

    # Remove _meta from validation
    data = {k: v for k, v in raw_data.items() if k != "_meta"}

    return DomainConfig.model_validate(data)


def get_all_domain_configs() -> list[DomainConfig]:
    """
    Load all domain configurations.

    Returns:
        List of DomainConfig for all active domains
    """
    registry = get_domain_registry()
    configs = []

    for domain_id in registry.domains:
        try:
            config = get_domain_config(domain_id)
            configs.append(config)
        except FileNotFoundError:
            # Domain in registry but no config file yet
            continue

    return configs


def get_environment(env_name: str) -> EnvironmentDefinition | None:
    """
    Get a specific environment definition.

    Args:
        env_name: Environment name (local, non-prod, prod)

    Returns:
        EnvironmentDefinition or None if not found
    """
    spec = get_infrastructure_spec()
    return spec.environments.definitions.get(env_name)


def clear_infrastructure_cache() -> None:
    """Clear cached infrastructure data (useful for testing)."""
    get_infrastructure_spec.cache_clear()
    get_domain_registry.cache_clear()
