"""Human feedback collection and confidence calibration for the decision graph."""

from __future__ import annotations

from dataclasses import dataclass
from datetime import UTC, datetime
from enum import Enum
from typing import TYPE_CHECKING, Any
from uuid import uuid4

if TYPE_CHECKING:
    from koan.graph.client import GraphClient


class FeedbackType(Enum):
    """Types of feedback signals."""

    EXPLICIT = "explicit"  # User answered "were you satisfied?"
    FORK = "fork"  # User forked the conversation (implicit dissatisfaction)
    REWIND = "rewind"  # User rewound the conversation (implicit dissatisfaction)
    THUMBS_UP = "thumbs_up"  # Quick positive signal
    THUMBS_DOWN = "thumbs_down"  # Quick negative signal


@dataclass
class FeedbackRecord:
    """A single feedback signal linked to a run or outcome."""

    feedback_id: str
    run_id: str
    feedback_type: FeedbackType
    score: float  # -1.0 to 1.0 (negative = dissatisfied, positive = satisfied)
    comment: str = ""
    timestamp: str = ""

    def __post_init__(self) -> None:
        if not self.timestamp:
            self.timestamp = datetime.now(UTC).isoformat()


async def record_feedback(
    client: GraphClient,
    *,
    run_id: str,
    feedback_type: FeedbackType,
    score: float,
    comment: str = "",
) -> str:
    """Record a feedback node in the graph and link it to a run's outcome.

    Args:
        client: Graph client instance.
        run_id: The run to attach feedback to.
        feedback_type: Type of feedback signal.
        score: Satisfaction score from -1.0 (very dissatisfied) to 1.0 (very satisfied).
        comment: Optional user comment.

    Returns:
        The generated feedback_id.
    """
    feedback_id = str(uuid4())
    timestamp = datetime.now(UTC).isoformat()

    await client.execute_write(
        """
        MATCH (r:Run {run_id: $run_id})
        OPTIONAL MATCH (r)-[:PRODUCED]->(o:Outcome)
        CREATE (f:Feedback {
            feedback_id: $feedback_id,
            feedback_type: $feedback_type,
            score: $score,
            comment: $comment,
            timestamp: $timestamp
        })
        CREATE (r)-[:HAS_FEEDBACK]->(f)
        WITH f, o
        WHERE o IS NOT NULL
        CREATE (o)-[:HAS_FEEDBACK]->(f)
        """,
        {
            "run_id": run_id,
            "feedback_id": feedback_id,
            "feedback_type": feedback_type.value,
            "score": score,
            "comment": comment,
            "timestamp": timestamp,
        },
    )

    return feedback_id


class ConfidenceCalibrator:
    """Queries historical feedback to calibrate confidence scores.

    Uses past feedback signals (explicit satisfaction ratings, fork/rewind events)
    to compute an adjustment factor for agent confidence.

    Usage::

        calibrator = ConfidenceCalibrator(graph_client)
        adjustment = await calibrator.get_calibration(
            agent_id="billing-agent",
            decision_type="classification",
        )
        # adjustment.factor = 0.85 means past decisions of this type
        # got 85% positive feedback → slightly lower confidence
        calibrated = raw_confidence * adjustment.factor
    """

    def __init__(self, client: GraphClient) -> None:
        self._client = client

    async def get_calibration(
        self,
        *,
        agent_id: str | None = None,
        decision_type: str | None = None,
        entity_id: str | None = None,
    ) -> CalibrationResult:
        """Compute a calibration factor based on historical feedback.

        Args:
            agent_id: Filter feedback to this agent.
            decision_type: Filter to this decision type.
            entity_id: Filter to decisions involving this entity.

        Returns:
            CalibrationResult with factor, sample count, and breakdown.
        """
        feedback_data = await self._query_feedback(
            agent_id=agent_id,
            decision_type=decision_type,
            entity_id=entity_id,
        )

        if not feedback_data:
            return CalibrationResult(
                factor=1.0,
                sample_count=0,
                positive_rate=0.0,
                negative_rate=0.0,
                fork_rate=0.0,
            )

        total = len(feedback_data)
        positive = sum(1 for f in feedback_data if f.get("score", 0) > 0)
        negative = sum(1 for f in feedback_data if f.get("score", 0) < 0)
        forks = sum(
            1 for f in feedback_data
            if f.get("feedback_type") in ("fork", "rewind")
        )

        positive_rate = positive / total if total > 0 else 0.0
        negative_rate = negative / total if total > 0 else 0.0
        fork_rate = forks / total if total > 0 else 0.0

        # Calibration factor: base of 1.0, penalised by negative feedback
        # Fork/rewind signals are weighted as 0.7x of explicit negative
        explicit_neg = negative - forks
        weighted_neg = explicit_neg + (forks * 0.7)
        factor = max(0.3, 1.0 - (weighted_neg / total * 0.5))

        return CalibrationResult(
            factor=round(factor, 3),
            sample_count=total,
            positive_rate=round(positive_rate, 3),
            negative_rate=round(negative_rate, 3),
            fork_rate=round(fork_rate, 3),
        )

    async def get_feedback_summary(
        self,
        *,
        agent_id: str | None = None,
        decision_type: str | None = None,
        limit: int = 20,
    ) -> dict[str, Any]:
        """Get a human-readable feedback summary for prompt injection.

        Returns a dict suitable for inclusion in GraphContext.
        """
        feedback_data = await self._query_feedback(
            agent_id=agent_id,
            decision_type=decision_type,
            limit=limit,
        )

        if not feedback_data:
            return {}

        total = len(feedback_data)
        positive = sum(1 for f in feedback_data if f.get("score", 0) > 0)
        negative = sum(1 for f in feedback_data if f.get("score", 0) < 0)

        avg_score = sum(f.get("score", 0) for f in feedback_data) / total

        recent_comments = [
            f.get("comment", "")
            for f in feedback_data[:5]
            if f.get("comment")
        ]

        return {
            "total_feedback": total,
            "positive": positive,
            "negative": negative,
            "satisfaction_rate": round(positive / total, 2) if total else 0.0,
            "avg_score": round(avg_score, 2),
            "recent_comments": recent_comments,
        }

    async def _query_feedback(
        self,
        *,
        agent_id: str | None = None,
        decision_type: str | None = None,
        entity_id: str | None = None,
        limit: int = 50,
    ) -> list[dict[str, Any]]:
        """Query feedback records from the graph."""
        if agent_id and decision_type and entity_id:
            query = """
                MATCH (a:Agent {agent_id: $agent_id})-[:EXECUTED]->(r:Run)-[:HAS_FEEDBACK]->(f:Feedback)
                MATCH (r)-[:CONTAINS]->(d:Decision {decision_type: $decision_type})
                MATCH (d)-[:REFERENCED]->(e:Entity {entity_id: $entity_id})
                RETURN f.feedback_id AS feedback_id,
                       f.feedback_type AS feedback_type,
                       f.score AS score,
                       f.comment AS comment,
                       f.timestamp AS timestamp
                LIMIT $limit
            """
            params: dict[str, Any] = {
                "agent_id": agent_id,
                "decision_type": decision_type,
                "entity_id": entity_id,
                "limit": limit,
            }
        elif agent_id and decision_type:
            query = """
                MATCH (a:Agent {agent_id: $agent_id})-[:EXECUTED]->(r:Run)-[:HAS_FEEDBACK]->(f:Feedback)
                MATCH (r)-[:CONTAINS]->(d:Decision {decision_type: $decision_type})
                RETURN f.feedback_id AS feedback_id,
                       f.feedback_type AS feedback_type,
                       f.score AS score,
                       f.comment AS comment,
                       f.timestamp AS timestamp
                LIMIT $limit
            """
            params = {
                "agent_id": agent_id,
                "decision_type": decision_type,
                "limit": limit,
            }
        elif agent_id:
            query = """
                MATCH (a:Agent {agent_id: $agent_id})-[:EXECUTED]->(r:Run)-[:HAS_FEEDBACK]->(f:Feedback)
                RETURN f.feedback_id AS feedback_id,
                       f.feedback_type AS feedback_type,
                       f.score AS score,
                       f.comment AS comment,
                       f.timestamp AS timestamp
                LIMIT $limit
            """
            params = {"agent_id": agent_id, "limit": limit}
        else:
            query = """
                MATCH (r:Run)-[:HAS_FEEDBACK]->(f:Feedback)
                RETURN f.feedback_id AS feedback_id,
                       f.feedback_type AS feedback_type,
                       f.score AS score,
                       f.comment AS comment,
                       f.timestamp AS timestamp
                LIMIT $limit
            """
            params = {"limit": limit}

        records = await self._client.execute(query, params)
        return [r.data for r in records]


@dataclass
class CalibrationResult:
    """Result of confidence calibration based on historical feedback."""

    factor: float  # Multiplier for raw confidence (0.3–1.0)
    sample_count: int  # Number of feedback samples used
    positive_rate: float  # Fraction of positive feedback
    negative_rate: float  # Fraction of negative feedback
    fork_rate: float  # Fraction of fork/rewind signals
