"""Conformed dimension mastering with surrogate key generation."""
from __future__ import annotations

import hashlib
import logging
from typing import Any, Dict, List, Optional, Set, Tuple

from .contracts import (
    ConformedDimension,
    EnterpriseKeyMap,
    MasteringStrategy,
    _new_id,
)

logger = logging.getLogger(__name__)


class ConformedDimMaster:
    """Enterprise dimension mastering with surrogate key generation.

    Wraps and extends MultiSystemModelMerger.detect_conformed_dimensions()
    from src/plugins/ce/blce/multi_system_merger.py with:
      - Enterprise key generation (deterministic hash or auto-increment)
      - Member mastering strategies (first_match, most_complete, priority_system)
      - SCD Type 2 support for tracking member changes
    """

    def __init__(self, overlap_threshold: float = 0.60):
        self._overlap_threshold = overlap_threshold
        self._system_dims: Dict[str, List[Dict[str, Any]]] = {}
        self._conformed: List[ConformedDimension] = []

    def register_system_dimensions(
        self,
        system_name: str,
        dimensions: List[Dict[str, Any]],
    ) -> int:
        """Register dimension tables from a source system.

        Each dimension dict should have:
          - table_name: str
          - key_column: str (natural key)
          - attributes: List[str] (attribute columns)
          - members: List[Dict[str, Any]] (optional — actual member data)
        """
        self._system_dims[system_name] = dimensions
        logger.info("Registered %d dimensions for system '%s'", len(dimensions), system_name)
        return len(dimensions)

    def detect_conformed(self) -> List[ConformedDimension]:
        """Detect conformed dimensions across all registered systems."""
        systems = list(self._system_dims.keys())
        if len(systems) < 2:
            return []

        conformed: List[ConformedDimension] = []
        seen: Set[str] = set()

        for i, sys_a in enumerate(systems):
            for j in range(i + 1, len(systems)):
                sys_b = systems[j]
                for dim_a in self._system_dims[sys_a]:
                    for dim_b in self._system_dims[sys_b]:
                        pair_key = f"{dim_a['table_name']}|{dim_b['table_name']}"
                        if pair_key in seen:
                            continue

                        match_type, overlap = self._check_match(dim_a, dim_b)
                        if match_type:
                            seen.add(pair_key)
                            cd = self._build_conformed(
                                sys_a, dim_a, sys_b, dim_b, match_type, overlap,
                            )
                            conformed.append(cd)

        self._conformed = conformed
        return conformed

    def master_dimension(
        self,
        conformed_dim: ConformedDimension,
        system_data: Dict[str, List[Dict[str, Any]]],
        strategy: MasteringStrategy = MasteringStrategy.FIRST_MATCH,
        priority_system: str = "",
    ) -> EnterpriseKeyMap:
        """Generate enterprise surrogate keys and master member data.

        Args:
            conformed_dim: A ConformedDimension from detect_conformed()
            system_data: {system_name: [member_dicts]} — actual member rows
            strategy: How to resolve conflicts when same member exists in multiple systems
            priority_system: For PRIORITY_SYSTEM strategy, which system takes precedence
        """
        all_members: Dict[str, Dict[str, Any]] = {}
        source_tracking: Dict[str, List[str]] = {}

        for src in conformed_dim.source_dims:
            system = src["system"]
            key_col = src.get("key_column", conformed_dim.natural_key_mapping.get(system, ""))
            members = system_data.get(system, [])

            for member in members:
                nk = str(member.get(key_col, ""))
                if not nk:
                    continue

                if nk not in source_tracking:
                    source_tracking[nk] = []
                source_tracking[nk].append(system)

                if nk not in all_members:
                    all_members[nk] = member
                else:
                    all_members[nk] = self._resolve_conflict(
                        all_members[nk], member, system, strategy, priority_system,
                    )

        # Generate enterprise keys
        mappings: List[Dict[str, Any]] = []
        ent_key_counter = 1
        for nk in sorted(all_members.keys()):
            ent_key = self._generate_enterprise_key(
                conformed_dim.dimension_name, nk,
            )
            mappings.append({
                "enterprise_key": ent_key,
                "natural_key": nk,
                "systems": source_tracking.get(nk, []),
                "attributes": all_members[nk],
                "sequence": ent_key_counter,
            })
            ent_key_counter += 1

        overlap = sum(1 for s in source_tracking.values() if len(s) > 1)
        unique_per_system: Dict[str, int] = {}
        for nk, sys_list in source_tracking.items():
            for s in sys_list:
                unique_per_system[s] = unique_per_system.get(s, 0) + 1

        return EnterpriseKeyMap(
            dimension_name=conformed_dim.dimension_name,
            mappings=mappings,
            total_members=len(mappings),
            overlap_count=overlap,
            unique_per_system=unique_per_system,
        )

    def generate_dim_ddl(
        self,
        conformed_dim: ConformedDimension,
        schema_prefix: str = "",
    ) -> str:
        """Generate DDL for a conformed enterprise dimension table."""
        table_name = f"{schema_prefix}{conformed_dim.dimension_name}"
        ek_col = conformed_dim.enterprise_key_column or f"ENT_{conformed_dim.dimension_name}_KEY"

        lines = [
            f"CREATE OR REPLACE TABLE {table_name} (",
            f"  {ek_col} VARCHAR(64) NOT NULL PRIMARY KEY,",
        ]

        # Natural key columns per system
        for system, nk_col in conformed_dim.natural_key_mapping.items():
            lines.append(f"  {system.upper()}_{nk_col} VARCHAR(256),")

        # Unified attributes
        for attr in conformed_dim.attributes:
            lines.append(f"  {attr} VARCHAR(1024),")

        lines.append("  SOURCE_SYSTEMS VARIANT,")

        if conformed_dim.scd_type == 2:
            lines.append("  EFFECTIVE_FROM TIMESTAMP_NTZ DEFAULT CURRENT_TIMESTAMP(),")
            lines.append("  EFFECTIVE_TO TIMESTAMP_NTZ DEFAULT '9999-12-31'::TIMESTAMP_NTZ,")
            lines.append("  IS_CURRENT BOOLEAN DEFAULT TRUE,")

        lines.append("  CREATED_AT TIMESTAMP_NTZ DEFAULT CURRENT_TIMESTAMP(),")
        lines.append("  UPDATED_AT TIMESTAMP_NTZ DEFAULT CURRENT_TIMESTAMP()")
        lines.append(");")

        return "\n".join(lines)

    # ------------------------------------------------------------------
    # Internal helpers
    # ------------------------------------------------------------------

    def _check_match(
        self,
        dim_a: Dict[str, Any],
        dim_b: Dict[str, Any],
    ) -> Tuple[str, float]:
        """Check if two dimensions are conformed.

        Returns (match_type, overlap_score) or ("", 0.0) if no match.
        """
        name_a = dim_a["table_name"].upper()
        name_b = dim_b["table_name"].upper()
        attrs_a = set(c.upper() for c in dim_a.get("attributes", []))
        attrs_b = set(c.upper() for c in dim_b.get("attributes", []))

        # Exact name match
        if name_a == name_b:
            overlap = self._calc_overlap(attrs_a, attrs_b)
            return "exact_name", overlap

        # Prefix-stripped match
        stripped_a = self._strip_dim_prefix(name_a)
        stripped_b = self._strip_dim_prefix(name_b)
        if stripped_a == stripped_b and stripped_a:
            overlap = self._calc_overlap(attrs_a, attrs_b)
            return "prefix_stripped", overlap

        # Column overlap match
        if attrs_a and attrs_b:
            overlap = self._calc_overlap(attrs_a, attrs_b)
            if overlap >= self._overlap_threshold:
                return "column_overlap", overlap

        return "", 0.0

    def _build_conformed(
        self,
        sys_a: str,
        dim_a: Dict[str, Any],
        sys_b: str,
        dim_b: Dict[str, Any],
        match_type: str,
        overlap: float,
    ) -> ConformedDimension:
        """Build a ConformedDimension from a matched pair."""
        canonical = self._canonical_name(dim_a["table_name"], dim_b["table_name"])
        attrs_a = set(dim_a.get("attributes", []))
        attrs_b = set(dim_b.get("attributes", []))
        unified_attrs = sorted(attrs_a | attrs_b, key=str.upper)

        attr_source_map: Dict[str, Dict[str, str]] = {}
        for attr in unified_attrs:
            sources: Dict[str, str] = {}
            if attr in attrs_a:
                sources[sys_a] = attr
            if attr in attrs_b:
                sources[sys_b] = attr
            attr_source_map[attr] = sources

        return ConformedDimension(
            dimension_name=f"ENT_{canonical}",
            source_dims=[
                {"system": sys_a, "table": dim_a["table_name"], "key_column": dim_a.get("key_column", "")},
                {"system": sys_b, "table": dim_b["table_name"], "key_column": dim_b.get("key_column", "")},
            ],
            enterprise_key_column=f"ENT_{canonical}_KEY",
            natural_key_mapping={
                sys_a: dim_a.get("key_column", ""),
                sys_b: dim_b.get("key_column", ""),
            },
            attributes=unified_attrs,
            attribute_source_map=attr_source_map,
            member_count_estimate={},
        )

    @staticmethod
    def _resolve_conflict(
        existing: Dict[str, Any],
        new: Dict[str, Any],
        new_system: str,
        strategy: MasteringStrategy,
        priority_system: str,
    ) -> Dict[str, Any]:
        """Resolve attribute conflicts when same member in multiple systems."""
        if strategy == MasteringStrategy.FIRST_MATCH:
            return existing
        elif strategy == MasteringStrategy.PRIORITY_SYSTEM:
            if new_system == priority_system:
                return new
            return existing
        elif strategy == MasteringStrategy.MOST_COMPLETE:
            existing_nulls = sum(1 for v in existing.values() if v is None or v == "")
            new_nulls = sum(1 for v in new.values() if v is None or v == "")
            return new if new_nulls < existing_nulls else existing
        return existing

    @staticmethod
    def _generate_enterprise_key(dim_name: str, natural_key: str) -> str:
        """Generate deterministic enterprise surrogate key."""
        raw = f"{dim_name}:{natural_key}"
        return hashlib.sha256(raw.encode()).hexdigest()[:16].upper()

    @staticmethod
    def _calc_overlap(a: Set[str], b: Set[str]) -> float:
        if not a or not b:
            return 0.0
        intersection = a & b
        return len(intersection) / min(len(a), len(b))

    @staticmethod
    def _strip_dim_prefix(name: str) -> str:
        import re
        return re.sub(r"^(DIM_|DIMENSION_|D_|ENT_)", "", name)

    @staticmethod
    def _canonical_name(a: str, b: str) -> str:
        """Produce a canonical dimension name from two source names."""
        import re
        stripped_a = re.sub(r"^(DIM_|DIMENSION_|D_|ENT_)", "", a.upper())
        stripped_b = re.sub(r"^(DIM_|DIMENSION_|D_|ENT_)", "", b.upper())
        if stripped_a == stripped_b:
            return stripped_a
        return f"{stripped_a}_{stripped_b}"
