"""BLCE Conflict Resolver — detect and resolve measure definition conflicts.

When multiple sources define the same measure with different formulas,
this module detects the conflict and proposes a resolution strategy.
"""
from __future__ import annotations

import logging
from collections import defaultdict
from typing import Any, Dict, List, Optional

from .contracts import ExtractedKPI, MeasureConflict

logger = logging.getLogger(__name__)


class ConflictResolver:
    """Detect and resolve measure definition conflicts."""

    def detect_conflicts(
        self,
        kpis: List[ExtractedKPI],
        departments: Optional[List[str]] = None,
    ) -> List[MeasureConflict]:
        """Group KPIs by canonical name and flag different formulas.

        Args:
            kpis: All extracted KPIs from various sources.
            departments: Optional department list for context.

        Returns:
            List of MeasureConflict objects.
        """
        # Group by canonical name
        groups: Dict[str, List[ExtractedKPI]] = defaultdict(list)
        for kpi in kpis:
            key = kpi.name.upper().strip()
            if key:
                groups[key].append(kpi)

        conflicts: List[MeasureConflict] = []
        for measure_name, group in groups.items():
            if len(group) < 2:
                continue

            # Check if formulas differ
            formulas = set()
            for kpi in group:
                if kpi.formula:
                    formulas.add(kpi.formula.strip().lower())

            if len(formulas) <= 1:
                continue  # All definitions agree

            definitions = [
                {
                    "source": kpi.source,
                    "source_type": kpi.source_type,
                    "formula": kpi.formula,
                    "departments": kpi.departments,
                    "confidence": kpi.confidence,
                }
                for kpi in group
            ]

            conflict = MeasureConflict(
                measure_name=measure_name,
                definitions=definitions,
                status="open",
            )

            # Auto-propose resolution
            self.propose_resolution(conflict)
            conflicts.append(conflict)

        return conflicts

    def propose_resolution(self, conflict: MeasureConflict) -> MeasureConflict:
        """Propose a resolution for a measure conflict.

        Resolution types:
        - ``unified``: definitions are close enough to merge.
        - ``separate``: definitions serve different purposes, keep both.
        - ``escalated``: too complex for auto-resolution, needs human review.

        Returns:
            The same conflict object with resolution fields populated.
        """
        defs = conflict.definitions
        if not defs:
            conflict.resolution_type = "escalated"
            conflict.resolution = "No definitions to resolve."
            return conflict

        # Heuristic 1: If definitions come from different source types
        # (report vs notes), pick the one with higher confidence
        source_types = {d.get("source_type", "") for d in defs}
        if len(source_types) > 1:
            best = max(defs, key=lambda d: d.get("confidence", 0))
            conflict.resolution_type = "unified"
            conflict.resolution = (
                f"Use definition from {best.get('source_type', 'unknown')} "
                f"(confidence: {best.get('confidence', 0):.2f}): "
                f"{best.get('formula', 'N/A')}"
            )
            conflict.status = "resolved"
            return conflict

        # Heuristic 2: If definitions serve different departments, keep separate
        dept_sets = [set(d.get("departments", [])) for d in defs]
        if any(dept_sets) and all(dept_sets):
            overlap = dept_sets[0]
            for ds in dept_sets[1:]:
                overlap = overlap & ds
            if not overlap:
                conflict.resolution_type = "separate"
                conflict.resolution = (
                    "Definitions serve different departments; "
                    "maintain separate measures with department prefixes."
                )
                conflict.status = "resolved"
                return conflict

        # Heuristic 3: Check formula similarity
        formulas = [d.get("formula", "").lower().strip() for d in defs if d.get("formula")]
        if len(formulas) >= 2:
            # Simple substring check
            if any(f1 in f2 or f2 in f1 for f1 in formulas for f2 in formulas if f1 != f2):
                best = max(defs, key=lambda d: len(d.get("formula", "")))
                conflict.resolution_type = "unified"
                conflict.resolution = (
                    f"Merge to most complete definition: {best.get('formula', 'N/A')}"
                )
                conflict.status = "resolved"
                return conflict

        # Default: escalate
        conflict.resolution_type = "escalated"
        conflict.resolution = (
            f"Multiple conflicting definitions ({len(defs)}); "
            "requires business stakeholder review."
        )
        return conflict

    def resolve_all(
        self,
        conflicts: List[MeasureConflict],
    ) -> Dict[str, Any]:
        """Resolve all open conflicts and return a summary.

        Returns:
            Dict with unified, separate, escalated counts.
        """
        for c in conflicts:
            if c.status == "open":
                self.propose_resolution(c)

        unified = sum(1 for c in conflicts if c.resolution_type == "unified")
        separate = sum(1 for c in conflicts if c.resolution_type == "separate")
        escalated = sum(1 for c in conflicts if c.resolution_type == "escalated")

        return {
            "total_conflicts": len(conflicts),
            "unified": unified,
            "separate": separate,
            "escalated": escalated,
        }
