"""Governance classification engine — CORE vs CUSTOM vs CANDIDATE."""
from __future__ import annotations

from collections import defaultdict
from datetime import datetime
from typing import Any, Dict, List, Optional, Set

from .constants import (
    CONFIDENCE_HIGH,
    CONFIDENCE_MEDIUM,
    MeasureAggregation,
    ToolClassification,
)
from .contracts import GovernanceDecision, LogicArtifact


# ---------------------------------------------------------------------------
# Known core patterns (seeded rules)
# ---------------------------------------------------------------------------

# Aggregation types that are universally reusable
_CORE_AGGREGATIONS: Set[MeasureAggregation] = {
    MeasureAggregation.SUM,
    MeasureAggregation.COUNT,
    MeasureAggregation.COUNT_DISTINCT,
    MeasureAggregation.AVG,
    MeasureAggregation.MIN,
    MeasureAggregation.MAX,
}

# Common measure name patterns that indicate reusable logic
_CORE_MEASURE_PATTERNS: List[str] = [
    "total_",
    "count_",
    "avg_",
    "sum_",
    "net_",
    "gross_",
    "num_",
    "pct_",
    "percent_",
]

# Client-specific indicators
_CUSTOM_INDICATORS: List[str] = [
    "custom_",
    "override_",
    "client_",
    "manual_",
    "adj_",       # manual adjustments
    "exception_",
]

# Minimum client count to auto-classify as CORE
CORE_CLIENT_THRESHOLD = 3

# Minimum client count for CANDIDATE
CANDIDATE_CLIENT_THRESHOLD = 2


class GovernanceEngine:
    """Classify logic artifacts as CORE, CUSTOM, or CANDIDATE.

    Classification rules:
    - **CORE**: Pattern appears in 3+ clients, OR uses standard aggregation
      on common columns with high confidence.
    - **CUSTOM**: Only in 1 client, uses client-specific naming patterns,
      or has low confidence.
    - **CANDIDATE**: 2 clients, or partially matches core patterns — under review.
    """

    def __init__(
        self,
        core_threshold: int = CORE_CLIENT_THRESHOLD,
        candidate_threshold: int = CANDIDATE_CLIENT_THRESHOLD,
    ):
        self.core_threshold = core_threshold
        self.candidate_threshold = candidate_threshold

    # ------------------------------------------------------------------
    # Classification
    # ------------------------------------------------------------------

    def classify_artifact(
        self,
        artifact: LogicArtifact,
        *,
        client_id: str = "",
        client_history: Optional[Dict[str, List[str]]] = None,
    ) -> GovernanceDecision:
        """Classify a single artifact as CORE / CUSTOM / CANDIDATE.

        ``client_history`` maps canonical measure names → list of client_ids
        that use them.  When provided, client count drives the classification.
        """
        reasons: List[str] = []
        score = 0.0  # positive = CORE, negative = CUSTOM

        # --- Rule 1: Aggregation type ---
        for m in artifact.objects.measures:
            if m.aggregation in _CORE_AGGREGATIONS:
                score += 0.2
                reasons.append(f"Standard aggregation: {m.aggregation.value}")

        # --- Rule 2: Measure naming patterns ---
        for m in artifact.objects.measures:
            name = (m.alias or m.name or "").lower()
            if any(name.startswith(p) for p in _CORE_MEASURE_PATTERNS):
                score += 0.15
                reasons.append(f"Core naming pattern: {name}")
            if any(name.startswith(p) for p in _CUSTOM_INDICATORS):
                score -= 0.3
                reasons.append(f"Custom indicator: {name}")

        # --- Rule 3: Client history (most important) ---
        client_count = 1
        if client_history:
            counts = []
            for m in artifact.objects.measures:
                canon = (m.alias or m.name or "").lower().replace(" ", "_")
                clients = client_history.get(canon, [])
                counts.append(len(clients))
            if counts:
                client_count = max(counts)

            if client_count >= self.core_threshold:
                score += 0.5
                reasons.append(f"Used by {client_count} clients (>= {self.core_threshold})")
            elif client_count >= self.candidate_threshold:
                score += 0.1
                reasons.append(f"Used by {client_count} clients (candidate)")
            else:
                score -= 0.1
                reasons.append(f"Only {client_count} client(s)")

        # --- Rule 4: Overall confidence ---
        if artifact.confidence >= CONFIDENCE_HIGH:
            score += 0.1
            reasons.append(f"High extraction confidence: {artifact.confidence}")
        elif artifact.confidence < CONFIDENCE_MEDIUM:
            score -= 0.15
            reasons.append(f"Low extraction confidence: {artifact.confidence}")

        # --- Rule 5: Dependency complexity ---
        dep_count = len(artifact.objects.dependencies)
        if dep_count > 5:
            score -= 0.1
            reasons.append(f"Complex: {dep_count} dependencies")

        # --- Make the decision ---
        if score >= 0.4:
            classification = ToolClassification.CORE
        elif score <= -0.1:
            classification = ToolClassification.CUSTOM
        else:
            classification = ToolClassification.CANDIDATE

        return GovernanceDecision(
            artifact_id=artifact.artifact_id,
            classification=classification,
            rationale="; ".join(reasons),
            client_count=client_count,
        )

    def classify_batch(
        self,
        artifacts: List[LogicArtifact],
        *,
        client_history: Optional[Dict[str, List[str]]] = None,
    ) -> List[GovernanceDecision]:
        """Classify multiple artifacts and return decisions."""
        return [
            self.classify_artifact(art, client_history=client_history)
            for art in artifacts
        ]

    # ------------------------------------------------------------------
    # Reporting
    # ------------------------------------------------------------------

    def generate_report(
        self, decisions: List[GovernanceDecision]
    ) -> Dict[str, Any]:
        """Generate a governance summary report from classification decisions."""
        if not decisions:
            return {
                "total": 0,
                "core": 0,
                "custom": 0,
                "candidate": 0,
                "decisions": [],
                "recommendations": [],
            }

        by_class: Dict[str, List[GovernanceDecision]] = defaultdict(list)
        for d in decisions:
            by_class[d.classification.value].append(d)

        core_count = len(by_class.get("core", []))
        custom_count = len(by_class.get("custom", []))
        candidate_count = len(by_class.get("candidate", []))
        total = len(decisions)

        # Build recommendations
        recommendations: List[str] = []
        if candidate_count > 0:
            recommendations.append(
                f"{candidate_count} artifact(s) classified as CANDIDATE — "
                "review with additional client data to promote or demote."
            )
        if custom_count > total * 0.5:
            recommendations.append(
                "Over 50% of artifacts are CUSTOM — consider whether "
                "client-specific patterns can be generalized."
            )
        if core_count > 0:
            recommendations.append(
                f"{core_count} CORE artifact(s) identified — these are "
                "candidates for the reusable platform library."
            )

        return {
            "total": total,
            "core": core_count,
            "custom": custom_count,
            "candidate": candidate_count,
            "core_pct": round(core_count / total * 100, 1) if total else 0,
            "decisions": [d.model_dump() for d in decisions],
            "recommendations": recommendations,
        }

    # ------------------------------------------------------------------
    # Helpers
    # ------------------------------------------------------------------

    def update_client_history(
        self,
        history: Dict[str, List[str]],
        artifacts: List[LogicArtifact],
        client_id: str,
    ) -> Dict[str, List[str]]:
        """Update the client history with measures from new artifacts.

        Returns the updated history dict (mutated in place).
        """
        for art in artifacts:
            for m in art.objects.measures:
                canon = (m.alias or m.name or "").lower().replace(" ", "_")
                if not canon:
                    continue
                if canon not in history:
                    history[canon] = []
                if client_id not in history[canon]:
                    history[canon].append(client_id)
        return history

    def find_promotion_candidates(
        self,
        decisions: List[GovernanceDecision],
    ) -> List[GovernanceDecision]:
        """Find CANDIDATE decisions that are close to CORE promotion."""
        return [
            d for d in decisions
            if d.classification == ToolClassification.CANDIDATE
            and d.client_count >= self.candidate_threshold
        ]
