"""Configuration models for metadata stage."""

from dataclasses import dataclass, field
from pathlib import Path
from typing import List, Dict, Any
import yaml

from .provider_normalizer import ProviderNormalizer


@dataclass
class KeyConfig:
    """Configuration for a single key with values and allocation mode flags."""
    key_name: str
    values: List[str]
    tags_only: bool = False
    k8s_labels_only: bool = False
    resource_id_only: bool = False

    def __post_init__(self):
        """Validate key configuration."""
        if not self.key_name:
            raise ValueError("Key name cannot be empty")
        if not self.values:
            raise ValueError(f"Values cannot be empty for key: {self.key_name}")
        if not all(isinstance(v, str) for v in self.values):
            raise ValueError(f"All values must be strings for key: {self.key_name}")

        # Validate mutual exclusion of allocation modes
        flags = [self.tags_only, self.k8s_labels_only, self.resource_id_only]
        if sum(flags) > 1:
            raise ValueError(
                f"Key '{self.key_name}': Only one allocation mode flag can be true "
                f"(tags_only, k8s_labels_only, resource_id_only)"
            )


@dataclass
class CorrelatedKeyGroup:
    """Configuration for correlated key group with allocation mode flags."""
    keys: Dict[str, List[str]]  # key_name -> correlated values
    tags_only: bool = False
    k8s_labels_only: bool = False
    resource_id_only: bool = False

    def __post_init__(self):
        """Validate correlated key group configuration."""
        if not self.keys:
            raise ValueError("Correlated keys cannot be empty")

        # Validate mutual exclusion of allocation modes
        flags = [self.tags_only, self.k8s_labels_only, self.resource_id_only]
        if sum(flags) > 1:
            raise ValueError(
                "Correlated group: Only one allocation mode flag can be true "
                "(tags_only, k8s_labels_only, resource_id_only)"
            )

        # Validate all lists have same length
        lengths = [len(values) for values in self.keys.values()]
        if len(set(lengths)) > 1:
            raise ValueError(f"Correlated group has mismatched lengths: {dict(zip(self.keys.keys(), lengths))}")

        # Check at least one non-null value per index
        num_sets = lengths[0] if lengths else 0
        for i in range(num_sets):
            values_at_index = [self.keys[key][i] for key in self.keys]
            if all(v is None or v == "" for v in values_at_index):
                raise ValueError(f"Correlated group index {i} has all null values")


@dataclass
class ProviderConfig:
    """Configuration for a single provider with all its keys."""
    provider: str  # Normalized provider name
    keys: List[KeyConfig]  # Independent keys
    correlated_groups: List[CorrelatedKeyGroup] = field(default_factory=list)  # Correlated keys

    def __post_init__(self):
        """Validate provider configuration."""
        if not self.provider:
            raise ValueError("Provider name cannot be empty")
        if not self.keys and not self.correlated_groups:
            raise ValueError(f"Keys and correlated_groups cannot both be empty for provider: {self.provider}")

        # Validate no duplicate key names (simple check first)
        key_names = [key.key_name for key in self.keys]
        duplicates = [name for name in key_names if key_names.count(name) > 1]
        if duplicates:
            raise ValueError(
                f"Provider '{self.provider}' has duplicate key names: {set(duplicates)}. "
                f"Each key name must be unique within a provider. "
                f"Remove duplicate definitions from your config."
            )


@dataclass
class MetadataConfig:
    """Complete metadata configuration."""
    dataset_name: str
    providers: List[ProviderConfig]
    key_variants: Dict[str, List[str]] = field(default_factory=dict)

    def __post_init__(self):
        """Validate metadata configuration."""
        if not self.dataset_name:
            raise ValueError("Dataset name cannot be empty")
        if not self.providers:
            raise ValueError("Providers cannot be empty")

        # Validate key variants if provided
        if self.key_variants:
            for canonical_key, variants in self.key_variants.items():
                if not canonical_key:
                    raise ValueError("Canonical key name cannot be empty")
                if not variants or not isinstance(variants, list):
                    raise ValueError(f"Key variants for '{canonical_key}' must be a non-empty list")
                if not all(isinstance(v, str) and v for v in variants):
                    raise ValueError(f"All variants for '{canonical_key}' must be non-empty strings")

                # Check for duplicate variants within the same key
                duplicates = [v for v in variants if variants.count(v) > 1]
                if duplicates:
                    raise ValueError(
                        f"Key '{canonical_key}' has duplicate variants: {set(duplicates)}. "
                        f"Each variant must appear only once. "
                        f"Check your key_variants configuration."
                    )

    @classmethod
    def from_yaml(cls, config_path: str) -> 'MetadataConfig':
        """Load configuration from YAML file with validation and normalization."""
        config_path = Path(config_path)
        if not config_path.exists():
            raise FileNotFoundError(f"Configuration file not found: {config_path}")

        with open(config_path, 'r') as f:
            config_data = yaml.safe_load(f)

        return cls.from_dict(config_data)

    @classmethod
    def from_dict(cls, config_data: Dict[str, Any]) -> 'MetadataConfig':
        """Load configuration from dictionary with validation and normalization."""
        if not isinstance(config_data, dict):
            raise ValueError("Configuration must be a dictionary")

        if 'dataset_name' not in config_data:
            raise ValueError("Configuration must contain 'dataset_name'")

        dataset_name = config_data['dataset_name']
        key_variants = config_data.get('key_variants', {})
        providers = []

        # Process providers as direct keys in config_data (standardized format)
        for provider_name, provider_data in config_data.items():
            if provider_name in ['dataset_name', 'key_variants']:
                continue

            # Normalize provider name
            normalized_provider = cls._normalize_provider_name(provider_name)

            # Parse provider keys
            keys = []
            correlated_groups = []
            if not isinstance(provider_data, list):
                raise ValueError(f"Provider '{provider_name}' must contain a list of key configurations")

            for key_data in provider_data:
                if not isinstance(key_data, dict):
                    raise ValueError(f"Each key configuration must be a dictionary in provider '{provider_name}'")

                # Check if this is a correlated group
                if 'correlated' in key_data:
                    # Parse correlated group
                    correlated_data = key_data['correlated']
                    if not isinstance(correlated_data, dict):
                        raise ValueError(f"Correlated configuration must be a dictionary in provider '{provider_name}'")

                    tags_only = key_data.get('tags_only', False)
                    k8s_labels_only = key_data.get('k8s_labels_only', False)
                    resource_id_only = key_data.get('resource_id_only', False)

                    # Extract key-value pairs (exclude allocation mode flags)
                    correlated_keys = {k: v for k, v in correlated_data.items()
                                     if k not in ['tags_only', 'k8s_labels_only', 'resource_id_only']}
                    if not correlated_keys:
                        raise ValueError(f"Correlated group must have at least one key in provider '{provider_name}'")

                    # Allocation mode flags from correlated dict take precedence
                    tags_only = correlated_data.get('tags_only', tags_only)
                    k8s_labels_only = correlated_data.get('k8s_labels_only', k8s_labels_only)
                    resource_id_only = correlated_data.get('resource_id_only', resource_id_only)

                    # Create CorrelatedKeyGroup
                    correlated_group = CorrelatedKeyGroup(
                        keys=correlated_keys,
                        tags_only=tags_only,
                        k8s_labels_only=k8s_labels_only,
                        resource_id_only=resource_id_only
                    )
                    correlated_groups.append(correlated_group)
                else:
                    # Parse independent key (existing logic)
                    # Extract the actual metadata key name (exclude allocation mode flags)
                    actual_keys = [k for k in key_data.keys() if k not in ['tags_only', 'k8s_labels_only', 'resource_id_only']]

                    if len(actual_keys) != 1:
                        raise ValueError(f"Each key configuration must have exactly one metadata key in provider '{provider_name}', found: {actual_keys}")

                    key_name = actual_keys[0]
                    key_value = key_data[key_name]
                    tags_only = key_data.get('tags_only', False)
                    k8s_labels_only = key_data.get('k8s_labels_only', False)
                    resource_id_only = key_data.get('resource_id_only', False)

                    # Handle key value format
                    if isinstance(key_value, list):
                        values = key_value
                    elif isinstance(key_value, dict):
                        if 'values' not in key_value:
                            raise ValueError(f"Key '{key_name}' configuration must have 'values' field")
                        values = key_value['values']
                        # Allocation mode flags from the dict take precedence
                        tags_only = key_value.get('tags_only', tags_only)
                        k8s_labels_only = key_value.get('k8s_labels_only', k8s_labels_only)
                        resource_id_only = key_value.get('resource_id_only', resource_id_only)
                    else:
                        raise ValueError(f"Key '{key_name}' must have list of values or dict with 'values' field")

                    # Create KeyConfig
                    key_config = KeyConfig(
                        key_name=key_name,
                        values=values,
                        tags_only=tags_only,
                        k8s_labels_only=k8s_labels_only,
                        resource_id_only=resource_id_only
                    )
                    keys.append(key_config)

            # Create ProviderConfig
            provider_config = ProviderConfig(
                provider=normalized_provider,
                keys=keys,
                correlated_groups=correlated_groups
            )
            providers.append(provider_config)

        # Validate no duplicate keys across all providers
        cls._validate_no_duplicate_keys(providers)

        return cls(dataset_name=dataset_name, providers=providers, key_variants=key_variants)

    @staticmethod
    def _validate_no_duplicate_keys(providers: List[ProviderConfig]) -> None:
        """
        Validate that no keys are duplicated within each provider across
        independent keys and correlated groups.

        Raises:
            ValueError: If duplicate keys are found with detailed error message.
        """
        for provider_config in providers:
            provider_name = provider_config.provider
            key_occurrences = {}  # key_name -> list of sources where it appears

            # Collect independent keys
            for key_config in provider_config.keys:
                key_name = key_config.key_name
                if key_name not in key_occurrences:
                    key_occurrences[key_name] = []
                key_occurrences[key_name].append("standalone allocation")

            # Collect keys from correlated groups
            for i, correlated_group in enumerate(provider_config.correlated_groups):
                for key_name in correlated_group.keys.keys():
                    if key_name not in key_occurrences:
                        key_occurrences[key_name] = []
                    key_occurrences[key_name].append(f"correlated group {i + 1}")

            # Find duplicates
            duplicates = {key: sources for key, sources in key_occurrences.items() if len(sources) > 1}

            if duplicates:
                error_details = []
                for key, sources in duplicates.items():
                    source_list = ", ".join(sources)
                    error_details.append(f"  • '{key}' appears in: {source_list}")

                error_msg = (
                    f"Duplicate keys found in provider '{provider_name}':\n" +
                    "\n".join(error_details) +
                    "\n\nEach key can only be defined once per provider. " +
                    "Choose either standalone allocation OR correlated group, not both."
                )
                raise ValueError(error_msg)

    @staticmethod
    def _normalize_provider_name(provider: str) -> str:
        """Normalize provider name for consistent column naming."""
        return ProviderNormalizer.normalize(provider)

    def get_provider_config(self, provider: str) -> ProviderConfig:
        """Get configuration for a specific provider."""
        normalized = self._normalize_provider_name(provider)
        for provider_config in self.providers:
            if provider_config.provider == normalized:
                return provider_config
        raise ValueError(f"No configuration found for provider: {provider}")

    def has_provider(self, provider: str) -> bool:
        """Check if provider is configured."""
        normalized = self._normalize_provider_name(provider)
        return any(p.provider == normalized for p in self.providers)

    def show_configuration_summary(self, available_providers: List[str] = None) -> str:
        """Generate user-friendly configuration summary."""
        lines = []
        lines.append("🔍 Configuration Analysis:")
        lines.append(f"📊 Dataset: {self.dataset_name}")
        lines.append(f"🏢 Providers configured: {[p.provider for p in self.providers]}")

        for provider_config in self.providers:
            lines.append(f"  {provider_config.provider}: {len(provider_config.keys)} keys configured")
            for key_config in provider_config.keys:
                scenario = "Tags-Only" if key_config.tags_only else "Four-Group"
                special = " (Region Special Case)" if key_config.key_name.lower() == 'region' else ""
                lines.append(f"    • {key_config.key_name}: {len(key_config.values)} values ({scenario}{special})")

        # Show provider validation if available_providers provided
        if available_providers is not None:
            configured_providers = set(p.provider for p in self.providers)
            available_providers_normalized = set(self._normalize_provider_name(p) for p in available_providers)

            missing_providers = configured_providers - available_providers_normalized
            if missing_providers:
                lines.append("")
                lines.append(f"⚠️  WARNING: Configured providers not found in data: {list(missing_providers)}")
                lines.append(f"📋 Available providers in {self.dataset_name}: {available_providers}")

        return "\n".join(lines)