"""Evaluation framework — auto-evaluate agent outputs and flag low-confidence decisions."""

from __future__ import annotations

from dataclasses import dataclass, field
from enum import Enum
from typing import TYPE_CHECKING, Any

import structlog

if TYPE_CHECKING:
    from koan.agents.types import AgentResult
    from koan.graph.client import GraphClient
    from koan.graph.feedback import ConfidenceCalibrator

log = structlog.get_logger()


class ReviewStatus(Enum):
    """Whether a decision needs human review."""

    APPROVED = "approved"
    FLAGGED = "flagged"
    REJECTED = "rejected"


@dataclass
class EvaluationResult:
    """Result of evaluating an agent's output."""

    review_status: ReviewStatus
    raw_confidence: float
    calibrated_confidence: float
    reasons: list[str] = field(default_factory=list)
    metadata: dict[str, Any] = field(default_factory=dict)


@dataclass
class EvaluationPolicy:
    """Thresholds for auto-approval, flagging, and rejection.

    - ``auto_approve_threshold``: Calibrated confidence above this → auto-approved.
    - ``flag_threshold``: Below auto_approve but above this → flagged for review.
    - Below flag_threshold → rejected.
    - ``max_negative_rate``: If historical negative feedback rate exceeds this, flag.
    - ``max_fork_rate``: If fork/rewind rate exceeds this, flag.
    """

    auto_approve_threshold: float = 0.8
    flag_threshold: float = 0.5
    max_negative_rate: float = 0.3
    max_fork_rate: float = 0.25


class DecisionEvaluator:
    """Evaluates agent decisions using calibrated confidence and feedback history.

    Combines raw decision confidence with historical calibration data to
    determine whether a decision should be auto-approved, flagged for
    human review, or rejected.

    Usage::

        evaluator = DecisionEvaluator(calibrator, policy=EvaluationPolicy())
        result = await evaluator.evaluate(
            agent_result=result,
            agent_id="billing-agent",
            decision_type="classification",
            raw_confidence=0.92,
        )
        if result.review_status == ReviewStatus.FLAGGED:
            # Route to human reviewer
            ...
    """

    def __init__(
        self,
        calibrator: ConfidenceCalibrator,
        *,
        policy: EvaluationPolicy | None = None,
    ) -> None:
        self._calibrator = calibrator
        self._policy = policy or EvaluationPolicy()

    async def evaluate(
        self,
        *,
        agent_id: str | None = None,
        decision_type: str | None = None,
        entity_id: str | None = None,
        raw_confidence: float = 1.0,
    ) -> EvaluationResult:
        """Evaluate a decision and determine its review status.

        Args:
            agent_id: The agent that made the decision.
            decision_type: Type of decision for filtered calibration.
            entity_id: Entity involved, for entity-specific calibration.
            raw_confidence: The agent's self-reported confidence.

        Returns:
            EvaluationResult with review status, calibrated confidence, and reasons.
        """
        cal = await self._calibrator.get_calibration(
            agent_id=agent_id,
            decision_type=decision_type,
            entity_id=entity_id,
        )

        calibrated = raw_confidence * cal.factor
        reasons: list[str] = []
        status = ReviewStatus.APPROVED

        # Check calibrated confidence thresholds
        if calibrated < self._policy.flag_threshold:
            status = ReviewStatus.REJECTED
            reasons.append(
                f"Calibrated confidence {calibrated:.2f} below rejection threshold "
                f"{self._policy.flag_threshold}"
            )
        elif calibrated < self._policy.auto_approve_threshold:
            status = ReviewStatus.FLAGGED
            reasons.append(
                f"Calibrated confidence {calibrated:.2f} below auto-approve threshold "
                f"{self._policy.auto_approve_threshold}"
            )

        # Check negative feedback rate
        if cal.sample_count > 0 and cal.negative_rate > self._policy.max_negative_rate:
            if status == ReviewStatus.APPROVED:
                status = ReviewStatus.FLAGGED
            reasons.append(
                f"Negative feedback rate {cal.negative_rate:.0%} exceeds "
                f"max {self._policy.max_negative_rate:.0%}"
            )

        # Check fork/rewind rate
        if cal.sample_count > 0 and cal.fork_rate > self._policy.max_fork_rate:
            if status == ReviewStatus.APPROVED:
                status = ReviewStatus.FLAGGED
            reasons.append(
                f"Fork/rewind rate {cal.fork_rate:.0%} exceeds "
                f"max {self._policy.max_fork_rate:.0%}"
            )

        # No feedback history — flag if confidence isn't high
        if cal.sample_count == 0 and raw_confidence < self._policy.auto_approve_threshold:
            status = ReviewStatus.FLAGGED
            reasons.append(
                f"No feedback history and raw confidence {raw_confidence:.2f} "
                f"below auto-approve threshold"
            )

        log.debug(
            "evaluation.complete",
            status=status.value,
            raw_confidence=raw_confidence,
            calibrated_confidence=round(calibrated, 3),
            calibration_factor=cal.factor,
            reasons=reasons,
        )

        return EvaluationResult(
            review_status=status,
            raw_confidence=raw_confidence,
            calibrated_confidence=round(calibrated, 3),
            reasons=reasons,
            metadata={
                "calibration_factor": cal.factor,
                "sample_count": cal.sample_count,
                "positive_rate": cal.positive_rate,
                "negative_rate": cal.negative_rate,
                "fork_rate": cal.fork_rate,
            },
        )

    async def evaluate_result(
        self,
        result: AgentResult,
        *,
        agent_id: str | None = None,
        decision_type: str | None = None,
    ) -> EvaluationResult:
        """Convenience: evaluate an AgentResult, extracting confidence from metadata."""
        raw_conf = result.metadata.get("confidence", 1.0)
        return await self.evaluate(
            agent_id=agent_id or result.agent_name,
            decision_type=decision_type,
            raw_confidence=raw_conf,
        )


async def record_review_decision(
    client: GraphClient,
    *,
    run_id: str,
    reviewer: str,
    approved: bool,
    comment: str = "",
) -> None:
    """Record a human reviewer's decision on a flagged run.

    Creates a Review node linked to the Run, indicating whether
    the human approved or rejected the agent's decision.
    """
    from datetime import UTC, datetime  # noqa: PLC0415
    from uuid import uuid4  # noqa: PLC0415

    review_id = str(uuid4())
    await client.execute_write(
        """
        MATCH (r:Run {run_id: $run_id})
        CREATE (rev:Review {
            review_id: $review_id,
            reviewer: $reviewer,
            approved: $approved,
            comment: $comment,
            timestamp: $timestamp
        })
        CREATE (r)-[:REVIEWED_BY]->(rev)
        """,
        {
            "run_id": run_id,
            "review_id": review_id,
            "reviewer": reviewer,
            "approved": approved,
            "comment": comment,
            "timestamp": datetime.now(UTC).isoformat(),
        },
    )
