"""
Kubernetes allocation stage for adding cluster, namespace, and workload metadata.

Advanced K8s allocation based on cz-demo-tools approach with:
- Deterministic topology generation
- Complex resource-to-workload mappings
- Realistic cost distribution across workloads
- Full cluster -> namespace -> workload hierarchies

Ported from cz-demo-tools to use Polars for performance.
"""

import hashlib
from dataclasses import dataclass
from typing import (
    Any,
    Dict,
    List,
    Optional,
    Set,
)

import polars as pl

from ..constants import (
    COMPONENT_TYPES,
    ENVIRONMENTS,
    K8S_SERVICES,
    PURPOSES,
    REGIONS,
    TEAM_PREFIXES,
    WORKLOAD_TYPES,
)


@dataclass
class K8sTopology:
    """Complete K8s topology with clusters, namespaces, workloads, and resource mappings."""

    clusters: List[str]
    namespaces: Dict[str, List[str]]  # cluster_id -> namespaces
    workloads: Dict[str, List[str]]  # namespace -> workloads
    resource_mappings: Dict[str, List[str]]  # resource_id -> list of workloads
    cluster_environments: Dict[str, str]  # cluster_id -> environment (prod/prd/dev/etc)




class DeterministicIdGenerator:
    """Generates deterministic K8s identifiers based on resource IDs."""

    def __init__(self, default_namespaces: Set[str]):
        self.default_namespaces = default_namespaces

        # Use shared constants for realistic generation
        self.environments = ENVIRONMENTS
        self.regions = REGIONS
        self.purposes = PURPOSES
        self.team_prefixes = TEAM_PREFIXES
        self.workload_types = WORKLOAD_TYPES
        self.component_types = COMPONENT_TYPES

        self._hash_cache = {}

    def _hash_based_selection(self, items: list, seed: str) -> str:
        """Select an item from a list based on hash value for determinism."""
        if seed not in self._hash_cache:
            self._hash_cache[seed] = hashlib.md5(seed.encode()).hexdigest()
        index = int(self._hash_cache[seed], 16) % len(items)
        return items[index]

    def generate_id(self, resource_id: str, prefix: str, seed: int) -> str:
        """Generate deterministic ID based on resource_id and a seed."""
        # Include provider in seed for provider-specific cluster names (Option 3: Provider-Based Seed Enhancement)
        provider = getattr(self, '_current_provider', '')
        input_str = f"{resource_id}:{prefix}:{seed}:{provider}"
        hash_obj = hashlib.md5(input_str.encode())
        hash_value = hash_obj.hexdigest()[:8]

        if prefix == "cluster":
            env = self._hash_based_selection(self.environments, f"{input_str}:env")
            region = self._hash_based_selection(self.regions, f"{input_str}:region")
            purpose = self._hash_based_selection(self.purposes, f"{input_str}:purpose")
            return f"{env}-{region}-{purpose}-{hash_value[:4]}"

        elif prefix == "namespace":
            if hash_value[:2] in ["00", "01", "02", "03"]:
                # System or platform namespace
                return self._hash_based_selection(
                    list(self.default_namespaces), input_str
                )
            else:
                # Team or project namespace
                team_prefix = self._hash_based_selection(
                    self.team_prefixes, f"{input_str}:team"
                )
                return f"{team_prefix}-{hash_value[:6]}"

        elif prefix == "workload":
            workload_type = self._hash_based_selection(
                self.workload_types, f"{input_str}:type"
            )
            component = self._hash_based_selection(
                self.component_types, f"{input_str}:component"
            )
            return f"{workload_type}-{component}-{hash_value[:4]}"

        return f"{prefix}-{hash_value}"

    def validate_k8s_resource_availability(self, resource_count: int) -> dict:
        """
        Validate K8s resource availability against fixed topology capacity.
        
        Args:
            resource_count: Number of eligible compute resources
            
        Returns:
            Validation report with feasibility assessment and allocation strategy
        """
        from ..config.models import MIN_TOPOLOGY_RESOURCE_CAPACITY, MAX_TOPOLOGY_RESOURCE_CAPACITY
        
        target_resources = int(resource_count * K8S_ALLOCATION_PERCENTAGE)
        
        if target_resources < MIN_TOPOLOGY_RESOURCE_CAPACITY:
            return {
                "feasible": False,
                "message": f"Insufficient resources: need minimum {MIN_TOPOLOGY_RESOURCE_CAPACITY}, have {target_resources}",
                "min_required": MIN_TOPOLOGY_RESOURCE_CAPACITY,
                "eligible_resources": resource_count,
                "target_resources": target_resources
            }
        
        # Determine allocation strategy
        actual_resources = min(target_resources, MAX_TOPOLOGY_RESOURCE_CAPACITY)
        workloads_per_namespace = 2 if actual_resources <= 350 else 3
        total_workloads = 35 * workloads_per_namespace  # 5 clusters × 7 namespaces
        
        return {
            "feasible": True,
            "eligible_resources": resource_count,
            "target_resources": target_resources,
            "actual_resources": actual_resources,
            "workloads_per_namespace": workloads_per_namespace,
            "total_workloads": total_workloads,
            "avg_resources_per_workload": actual_resources / total_workloads,
            "capped_allocation": actual_resources < target_resources,
            "remaining_resources": target_resources - actual_resources if actual_resources < target_resources else 0
        }

    def generate_topology(
        self,
        base_seed: str,
        clusters_count: int,
        namespaces_per_cluster: int,
        workloads_per_namespace: int,
    ) -> K8sTopology:
        """Generate a complete K8s topology with deterministic naming."""
        clusters = []
        namespaces = {}
        workloads = {}
        cluster_environments = {}

        # Generate cluster IDs and track their environments
        for i in range(clusters_count):
            cluster_id = self.generate_id(base_seed, "cluster", i)
            clusters.append(cluster_id)
            namespaces[cluster_id] = []
            
            # Extract environment from cluster name (format: "env-region-purpose-hash")
            env = cluster_id.split('-')[0]
            cluster_environments[cluster_id] = env

            # Add default namespaces
            for ns in self.default_namespaces:
                namespaces[cluster_id].append(ns)
                workloads[ns] = []

                # Add workloads to namespace
                base_workload_seed = i * len(self.default_namespaces) + hash(ns) % 1000
                for j in range(workloads_per_namespace):
                    workload_id = self.generate_id(
                        base_seed, "workload", base_workload_seed + j
                    )
                    workloads[ns].append(workload_id)

            # Add custom namespaces
            base_namespace_seed = i * namespaces_per_cluster
            for j in range(namespaces_per_cluster):
                ns = self.generate_id(base_seed, "namespace", base_namespace_seed + j)
                namespaces[cluster_id].append(ns)
                workloads[ns] = []

                # Add workloads to namespace
                base_workload_seed = (
                    base_namespace_seed * workloads_per_namespace + j * 100
                )
                for k in range(workloads_per_namespace):
                    workload_id = self.generate_id(
                        base_seed, "workload", base_workload_seed + k
                    )
                    workloads[ns].append(workload_id)

            # Add Idle namespace and workload
            idle_namespace = "__UNALLOCATED__"
            idle_workload = "__UNALLOCATED__"
            namespaces[cluster_id].append(idle_namespace)
            workloads[idle_namespace] = [idle_workload]

        # Don't initialize resource_mappings with workload keys - it will be filled by distribute_resources
        return K8sTopology(clusters, namespaces, workloads, {}, cluster_environments)




def create_compute_mask(
    df: pl.DataFrame, services: Set[str], resource_types: Set[str]
) -> pl.Expr:
    """Create a mask for filtering compute resources based on service and resource type."""
    service_mask = pl.col("service").is_in(list(services))
    if resource_types:
        resource_mask = pl.col("resource_type").is_in(list(resource_types))
        return service_mask & resource_mask
    return service_mask


def should_allocate_k8s(provider: str, service: str) -> bool:
    """Determine if a service should get K8s allocation."""
    return service in K8S_SERVICES.get(provider, [])


