"""Grain contract builder — propose conformed grain from artifacts."""
from __future__ import annotations

from collections import defaultdict
from typing import Dict, List, Set, Tuple

from ..contracts import GrainColumn, GrainContract, LogicArtifact


def _normalize_grain_col(col: str) -> str:
    """Normalize a grain column name for comparison."""
    s = col.lower().strip()
    # Strip common suffixes/prefixes for matching
    for suffix in ("_id", "_key", "_no", "_num", "_code"):
        if s.endswith(suffix):
            s = s[: -len(suffix)]
            break
    return s


class GrainContractBuilder:
    """Build GrainContract proposals from extracted grain columns.

    Groups grain columns by entity domain, proposes conformed grain
    names, and maps source-specific columns to the conformed set.
    """

    def build_contracts(
        self, artifacts: List[LogicArtifact]
    ) -> List[GrainContract]:
        """Propose grain contracts from artifacts.

        Groups artifacts by their grain pattern and proposes
        a conformed grain for each distinct pattern.
        """
        if not artifacts:
            return []

        # Collect grain info per artifact: (artifact_id, source_name, grain_cols)
        grain_entries: List[Tuple[str, str, List[GrainColumn]]] = []
        for art in artifacts:
            if art.objects.grain_columns:
                grain_entries.append((
                    art.artifact_id,
                    art.source_name or art.source_path or art.artifact_id,
                    art.objects.grain_columns,
                ))

        if not grain_entries:
            return []

        # Group by normalized grain pattern
        pattern_groups: Dict[str, List[Tuple[str, str, List[GrainColumn]]]] = defaultdict(list)
        for aid, sname, gcols in grain_entries:
            normalized = tuple(sorted(_normalize_grain_col(g.column) for g in gcols))
            pattern_key = ",".join(normalized)
            pattern_groups[pattern_key].append((aid, sname, gcols))

        contracts: List[GrainContract] = []
        for pattern_key, group in pattern_groups.items():
            conformed = pattern_key.split(",")

            # Build source_grains mapping
            source_grains: Dict[str, List[str]] = {}
            for aid, sname, gcols in group:
                source_grains[sname] = [g.column for g in gcols]

            # Compute confidence based on agreement
            confidence = min(0.5 + 0.1 * len(group), 1.0)

            # Derive entity name from the most common table reference
            entity = self._infer_entity(group)

            contracts.append(GrainContract(
                entity=entity,
                conformed_grain=conformed,
                source_grains=source_grains,
                confidence=round(confidence, 2),
            ))

        contracts.sort(key=lambda gc: -gc.confidence)
        return contracts

    def detect_grain_conflicts(
        self, artifacts: List[LogicArtifact]
    ) -> List[Dict]:
        """Detect artifacts that use different grains for the same entity.

        Returns a list of conflict descriptions.
        """
        # Group by entity (inferred from table names in grain columns)
        entity_grains: Dict[str, List[Tuple[str, Set[str]]]] = defaultdict(list)
        for art in artifacts:
            if not art.objects.grain_columns:
                continue
            tables = set()
            for g in art.objects.grain_columns:
                if g.table:
                    tables.add(g.table.lower())
            # Also check dependencies for entity inference
            for d in art.objects.dependencies:
                if d.dep_type == "table":
                    tables.add(d.name.lower())

            grain_set = {g.column.lower() for g in art.objects.grain_columns}
            for t in tables:
                entity_grains[t].append((art.artifact_id, grain_set))

        conflicts = []
        for entity, entries in entity_grains.items():
            if len(entries) < 2:
                continue
            grains = [g for _, g in entries]
            if len(set(frozenset(g) for g in grains)) > 1:
                conflicts.append({
                    "entity": entity,
                    "artifact_count": len(entries),
                    "grain_variants": [
                        {"artifact_id": aid, "grain": sorted(g)}
                        for aid, g in entries
                    ],
                })

        return conflicts

    def _infer_entity(
        self, group: List[Tuple[str, str, List[GrainColumn]]]
    ) -> str:
        """Infer entity name from grain column table references."""
        table_counts: Dict[str, int] = defaultdict(int)
        for _, _, gcols in group:
            for g in gcols:
                if g.table:
                    table_counts[g.table.lower()] += 1

        if table_counts:
            return max(table_counts, key=table_counts.get)

        # Fallback: use the grain column names themselves
        cols = []
        for _, _, gcols in group:
            cols.extend(g.column for g in gcols)
        return "_".join(sorted(set(cols)))[:50] if cols else "unknown"
