"""Tests for human feedback and confidence calibration."""

from __future__ import annotations

from unittest.mock import AsyncMock

from koan.graph.client import GraphRecord
from koan.graph.feedback import (
    CalibrationResult,
    ConfidenceCalibrator,
    FeedbackRecord,
    FeedbackType,
    record_feedback,
)


def _mock_client() -> AsyncMock:
    client = AsyncMock()
    client.execute = AsyncMock(return_value=[])
    client.execute_write = AsyncMock(return_value=[])
    return client


class TestFeedbackType:
    def test_explicit_value(self) -> None:
        assert FeedbackType.EXPLICIT.value == "explicit"

    def test_fork_value(self) -> None:
        assert FeedbackType.FORK.value == "fork"

    def test_rewind_value(self) -> None:
        assert FeedbackType.REWIND.value == "rewind"

    def test_thumbs_up_value(self) -> None:
        assert FeedbackType.THUMBS_UP.value == "thumbs_up"

    def test_thumbs_down_value(self) -> None:
        assert FeedbackType.THUMBS_DOWN.value == "thumbs_down"


class TestFeedbackRecord:
    def test_defaults(self) -> None:
        rec = FeedbackRecord(
            feedback_id="f1",
            run_id="r1",
            feedback_type=FeedbackType.EXPLICIT,
            score=0.8,
        )
        assert rec.feedback_id == "f1"
        assert rec.score == 0.8
        assert rec.timestamp  # auto-populated

    def test_with_comment(self) -> None:
        rec = FeedbackRecord(
            feedback_id="f2",
            run_id="r2",
            feedback_type=FeedbackType.THUMBS_DOWN,
            score=-1.0,
            comment="Not helpful",
        )
        assert rec.comment == "Not helpful"


class TestRecordFeedback:
    async def test_record_explicit_feedback(self) -> None:
        client = _mock_client()
        fid = await record_feedback(
            client,
            run_id="run-1",
            feedback_type=FeedbackType.EXPLICIT,
            score=0.8,
            comment="Very helpful!",
        )
        assert fid  # non-empty UUID string
        assert client.execute_write.call_count == 1
        call_args = client.execute_write.call_args
        assert "Feedback" in call_args[0][0]
        assert call_args[0][1]["score"] == 0.8
        assert call_args[0][1]["comment"] == "Very helpful!"

    async def test_record_fork_feedback(self) -> None:
        client = _mock_client()
        fid = await record_feedback(
            client,
            run_id="run-2",
            feedback_type=FeedbackType.FORK,
            score=-0.5,
        )
        assert fid
        call_params = client.execute_write.call_args[0][1]
        assert call_params["feedback_type"] == "fork"
        assert call_params["score"] == -0.5

    async def test_record_thumbs_up(self) -> None:
        client = _mock_client()
        fid = await record_feedback(
            client,
            run_id="run-3",
            feedback_type=FeedbackType.THUMBS_UP,
            score=1.0,
        )
        assert fid
        call_params = client.execute_write.call_args[0][1]
        assert call_params["feedback_type"] == "thumbs_up"


class TestConfidenceCalibrator:
    async def test_no_feedback_returns_neutral(self) -> None:
        client = _mock_client()
        cal = ConfidenceCalibrator(client)
        result = await cal.get_calibration(agent_id="bot")
        assert result.factor == 1.0
        assert result.sample_count == 0

    async def test_all_positive_feedback(self) -> None:
        client = _mock_client()
        client.execute = AsyncMock(return_value=[
            GraphRecord(data={"feedback_id": "f1", "feedback_type": "explicit", "score": 1.0, "comment": "", "timestamp": "t1"}),
            GraphRecord(data={"feedback_id": "f2", "feedback_type": "thumbs_up", "score": 1.0, "comment": "", "timestamp": "t2"}),
            GraphRecord(data={"feedback_id": "f3", "feedback_type": "explicit", "score": 0.5, "comment": "ok", "timestamp": "t3"}),
        ])
        cal = ConfidenceCalibrator(client)
        result = await cal.get_calibration(agent_id="bot")
        assert result.factor == 1.0
        assert result.sample_count == 3
        assert result.positive_rate == 1.0
        assert result.negative_rate == 0.0

    async def test_mixed_feedback_lowers_factor(self) -> None:
        client = _mock_client()
        client.execute = AsyncMock(return_value=[
            GraphRecord(data={"feedback_id": "f1", "feedback_type": "explicit", "score": 1.0, "comment": "", "timestamp": "t1"}),
            GraphRecord(data={"feedback_id": "f2", "feedback_type": "explicit", "score": -1.0, "comment": "bad", "timestamp": "t2"}),
            GraphRecord(data={"feedback_id": "f3", "feedback_type": "thumbs_down", "score": -1.0, "comment": "", "timestamp": "t3"}),
            GraphRecord(data={"feedback_id": "f4", "feedback_type": "explicit", "score": 1.0, "comment": "", "timestamp": "t4"}),
        ])
        cal = ConfidenceCalibrator(client)
        result = await cal.get_calibration(agent_id="bot")
        assert result.sample_count == 4
        assert result.negative_rate == 0.5
        assert result.factor < 1.0

    async def test_fork_signals_penalised_less_than_explicit(self) -> None:
        client = _mock_client()
        # 2 fork signals, 2 positive
        client.execute = AsyncMock(return_value=[
            GraphRecord(data={"feedback_id": "f1", "feedback_type": "fork", "score": -0.5, "comment": "", "timestamp": "t1"}),
            GraphRecord(data={"feedback_id": "f2", "feedback_type": "rewind", "score": -0.5, "comment": "", "timestamp": "t2"}),
            GraphRecord(data={"feedback_id": "f3", "feedback_type": "explicit", "score": 1.0, "comment": "", "timestamp": "t3"}),
            GraphRecord(data={"feedback_id": "f4", "feedback_type": "explicit", "score": 1.0, "comment": "", "timestamp": "t4"}),
        ])
        cal = ConfidenceCalibrator(client)
        result = await cal.get_calibration(agent_id="bot")
        assert result.fork_rate == 0.5
        # Fork penalty is 0.7x of explicit, so factor should be higher than all-explicit-negative
        assert result.factor > 0.5

    async def test_all_negative_has_floor(self) -> None:
        client = _mock_client()
        client.execute = AsyncMock(return_value=[
            GraphRecord(data={"feedback_id": f"f{i}", "feedback_type": "explicit", "score": -1.0, "comment": "", "timestamp": f"t{i}"})
            for i in range(10)
        ])
        cal = ConfidenceCalibrator(client)
        result = await cal.get_calibration(agent_id="bot")
        assert result.factor >= 0.3  # Floor

    async def test_get_feedback_summary(self) -> None:
        client = _mock_client()
        client.execute = AsyncMock(return_value=[
            GraphRecord(data={"feedback_id": "f1", "feedback_type": "explicit", "score": 1.0, "comment": "Great!", "timestamp": "t1"}),
            GraphRecord(data={"feedback_id": "f2", "feedback_type": "explicit", "score": -1.0, "comment": "Bad", "timestamp": "t2"}),
            GraphRecord(data={"feedback_id": "f3", "feedback_type": "thumbs_up", "score": 1.0, "comment": "", "timestamp": "t3"}),
        ])
        cal = ConfidenceCalibrator(client)
        summary = await cal.get_feedback_summary(agent_id="bot")
        assert summary["total_feedback"] == 3
        assert summary["positive"] == 2
        assert summary["negative"] == 1
        assert summary["satisfaction_rate"] == 0.67
        assert "Great!" in summary["recent_comments"]

    async def test_empty_summary(self) -> None:
        client = _mock_client()
        cal = ConfidenceCalibrator(client)
        summary = await cal.get_feedback_summary(agent_id="bot")
        assert summary == {}

    async def test_calibration_with_decision_type(self) -> None:
        client = _mock_client()
        client.execute = AsyncMock(return_value=[
            GraphRecord(data={"feedback_id": "f1", "feedback_type": "explicit", "score": 1.0, "comment": "", "timestamp": "t1"}),
        ])
        cal = ConfidenceCalibrator(client)
        result = await cal.get_calibration(agent_id="bot", decision_type="routing")
        assert result.sample_count == 1
        # Verify the query included decision_type
        query = client.execute.call_args[0][0]
        assert "decision_type" in query


class TestCalibrationResult:
    def test_fields(self) -> None:
        r = CalibrationResult(
            factor=0.85,
            sample_count=10,
            positive_rate=0.7,
            negative_rate=0.3,
            fork_rate=0.1,
        )
        assert r.factor == 0.85
        assert r.sample_count == 10
