"""Trust Elasticity metric for Janus Labs governance measurement."""

from dataclasses import dataclass
from typing import Optional

from deepeval.metrics import BaseMetric
from deepeval.test_case import LLMTestCase

from harness.types import RunArtifactBundle


@dataclass
class GovernanceSignals:
    """Extracted governance signals from a run."""
    foundation_checks_triggered: int = 0
    iterations_count: int = 1
    halted: bool = False
    tool_success_rate: float = 1.0
    error_count: int = 0
    error_rate: float = 0.0


def extract_governance_signals(bundle: RunArtifactBundle) -> GovernanceSignals:
    """
    Extract governance-relevant signals from a RunArtifactBundle.

    Args:
        bundle: The captured execution artifacts

    Returns:
        GovernanceSignals with extracted metrics
    """
    tool_traces = bundle.get("tool_traces", [])
    total_tools = len(tool_traces)

    successful = sum(
        1 for trace in tool_traces
        if "error" not in str(trace.get("result", "")).lower()
    )

    success_rate = successful / total_tools if total_tools > 0 else 1.0
    error_count = total_tools - successful
    error_rate = error_count / total_tools if total_tools > 0 else 0.0

    foundation_checks = sum(
        1 for trace in tool_traces
        if "foundation" in trace.get("tool_name", "").lower()
    )

    transcript_len = len(bundle.get("transcript", []))
    iterations_count = max(1, transcript_len // 2)

    return GovernanceSignals(
        foundation_checks_triggered=foundation_checks,
        iterations_count=iterations_count,
        halted=bundle.get("exit_code") == "halt",
        tool_success_rate=success_rate,
        error_count=error_count,
        error_rate=error_rate,
    )


class TrustElasticityMetric(BaseMetric):
    """
    Custom DeepEval metric measuring Trust Elasticity.

    Trust Elasticity quantifies how well governance intensity
    scales with demonstrated competence. Higher = better.

    Scale: 0-100 (mapped to letter grades S/A/B/C/D/F)
    """

    def __init__(
        self,
        base_score: float = 7.0,
        threshold: float = 0.6,
        bundle: Optional[RunArtifactBundle] = None,
    ):
        """
        Initialize Trust Elasticity metric.

        Args:
            base_score: Base LLM judge score (1-10), default 7.0
            threshold: Minimum acceptable (0-1 scale), default 0.6
            bundle: Optional RunArtifactBundle for governance signals
        """
        self.base_score = base_score
        self.threshold = threshold
        self.bundle = bundle
        self._score: Optional[float] = None
        self._reason: Optional[str] = None

    @property
    def name(self) -> str:
        return "TrustElasticity"

    def measure(self, test_case: LLMTestCase) -> float:
        """
        Calculate Trust Elasticity score.

        Args:
            test_case: The LLMTestCase to evaluate

        Returns:
            Score between 0-1 (multiply by 100 for display scale)
        """
        _ = test_case
        if self.bundle:
            signals = extract_governance_signals(self.bundle)
        else:
            signals = GovernanceSignals()

        competence_factor = 1.0 + (0.1 * signals.tool_success_rate) - (0.1 * signals.error_rate)
        competence_factor = max(0.5, min(1.5, competence_factor))

        governance_factor = 1.0 - (0.05 * signals.foundation_checks_triggered)
        governance_factor = max(0.7, governance_factor)

        if signals.halted:
            governance_factor *= 0.5

        raw_score = self.base_score * competence_factor * governance_factor
        raw_score = max(1.0, min(10.0, raw_score))

        self._score = raw_score / 10.0
        self._reason = (
            f"Base: {self.base_score:.1f}, "
            f"Competence: {competence_factor:.2f}, "
            f"Governance: {governance_factor:.2f}, "
            f"Final: {raw_score:.1f}/10 ({self._score * 100:.0f}/100)"
        )

        return self._score

    def is_successful(self) -> bool:
        """Check if score meets threshold."""
        if self._score is None:
            return False
        return self._score >= self.threshold

    @property
    def score(self) -> float:
        """Return calculated score."""
        return self._score or 0.0

    @property
    def reason(self) -> str:
        """Return explanation of score."""
        return self._reason or "Not yet measured"

    @staticmethod
    def score_to_grade(score_0_100: float) -> str:
        """
        Convert 0-100 score to letter grade.

        Args:
            score_0_100: Score on 0-100 scale

        Returns:
            Letter grade (S/A/B/C/D/F)
        """
        if score_0_100 >= 90:
            return "S"
        if score_0_100 >= 80:
            return "A"
        if score_0_100 >= 70:
            return "B"
        if score_0_100 >= 60:
            return "C"
        if score_0_100 >= 50:
            return "D"
        return "F"
