# #region Test.DatasetReview.ClarificationEngine [C:3] [TYPE Module] [SEMANTICS test,dataset,clarification,engine,state,answer]
# @BRIEF Tests for clarification_engine.py — build_question_payload, record_answer, summarize_progress,
#        _get_latest_clarification_session, _find_question.
# @RELATION BINDS_TO -> [ClarificationEngine]
# @TEST_EDGE: build_question_payload -> Active question, completed state, no clarification session
# @TEST_EDGE: record_answer -> Selected/custom/skipped/expert, duplicate rejection, wrong question rejection
# @TEST_EDGE: summarize_progress -> String format with counts
# @TEST_EDGE: _get_latest_clarification_session -> Sorted by started_at
# @TEST_EDGE: _find_question -> Found and not found

from __future__ import annotations
from pathlib import Path
import sys

sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src"))

from datetime import datetime, timezone
from unittest.mock import MagicMock, patch, PropertyMock
import pytest


# ── Fixtures ────────────────────────────────────────────────

@pytest.fixture
def mock_repository():
    """Mock DatasetReviewSessionRepository with MagicMock DB."""
    repo = MagicMock()
    repo.db = MagicMock()
    repo.event_logger = MagicMock()
    return repo


@pytest.fixture
def mock_question():
    from src.models.dataset_review import ClarificationQuestion, QuestionState
    q = MagicMock(spec=ClarificationQuestion)
    q.question_id = "q-1"
    q.topic_ref = "region_mapping"
    q.question_text = "What region?"
    q.why_it_matters = "Affects access"
    q.current_guess = "US"
    q.priority = 5
    q.state = QuestionState.OPEN
    q.created_at = datetime(2024, 6, 1, tzinfo=timezone.utc)
    q.answer = None
    q.clarification_session_id = "cs-1"
    q.updated_at = datetime(2024, 6, 1, tzinfo=timezone.utc)
    return q


@pytest.fixture
def mock_option():
    from src.models.dataset_review import ClarificationOption
    opt = MagicMock(spec=ClarificationOption)
    opt.option_id = "opt-1"
    opt.question_id = "q-1"
    opt.label = "US"
    opt.value = "US"
    opt.is_recommended = True
    opt.display_order = 1
    return opt


@pytest.fixture
def mock_clarification_session():
    from src.models.dataset_review import ClarificationSession, QuestionState
    cs = MagicMock(spec=ClarificationSession)
    cs.clarification_session_id = "cs-1"
    cs.current_question_id = "q-1"
    cs.status = MagicMock(value="active")
    cs.resolved_count = 0
    cs.remaining_count = 1
    cs.summary_delta = ""
    cs.started_at = datetime(2024, 6, 1, tzinfo=timezone.utc)
    cs.updated_at = datetime(2024, 6, 1, tzinfo=timezone.utc)
    return cs


@pytest.fixture
def mock_session():
    from src.models.dataset_review import (
        DatasetReviewSession, SessionPhase, ReadinessState, RecommendedAction,
    )
    s = MagicMock(spec=DatasetReviewSession)
    s.session_id = "sess-1"
    s.current_phase = SessionPhase.CLARIFICATION
    s.readiness_state = ReadinessState.CLARIFICATION_ACTIVE
    s.recommended_action = RecommendedAction.ANSWER_NEXT_QUESTION
    s.user_id = "user-1"
    s.version = 1
    s.clarification_sessions = []
    s.findings = []
    return s


# ── Tests for ClarificationEngine.build_question_payload ───

class TestBuildQuestionPayload:
    """build_question_payload — active question selection."""

    def test_returns_question_payload(self, mock_repository, mock_session, mock_clarification_session, mock_question, mock_option):
        from src.services.dataset_review.clarification_engine import ClarificationEngine

        mock_clarification_session.questions = [mock_question]
        mock_question.options = [mock_option]
        mock_session.clarification_sessions = [mock_clarification_session]

        engine = ClarificationEngine(mock_repository)
        result = engine.build_question_payload(mock_session)

        assert result is not None
        assert result.question_id == "q-1"
        assert result.topic_ref == "region_mapping"
        assert len(result.options) == 1
        assert result.options[0]["value"] == "US"
        assert mock_clarification_session.current_question_id == "q-1"

    def test_returns_none_when_no_clarification_session(self, mock_repository, mock_session):
        from src.services.dataset_review.clarification_engine import ClarificationEngine

        mock_session.clarification_sessions = []
        engine = ClarificationEngine(mock_repository)
        result = engine.build_question_payload(mock_session)
        assert result is None

    def test_returns_none_when_all_answered(self, mock_repository, mock_session, mock_clarification_session, mock_question):
        from src.services.dataset_review.clarification_engine import ClarificationEngine
        from src.models.dataset_review import QuestionState

        mock_question.state = QuestionState.ANSWERED
        mock_clarification_session.questions = [mock_question]
        mock_session.clarification_sessions = [mock_clarification_session]

        engine = ClarificationEngine(mock_repository)
        result = engine.build_question_payload(mock_session)

        assert result is None
        assert mock_clarification_session.current_question_id is None
        assert mock_clarification_session.status.value == "completed"

    def test_advances_phase_from_clarification_to_review(self, mock_repository, mock_session, mock_clarification_session, mock_question):
        from src.services.dataset_review.clarification_engine import ClarificationEngine
        from src.models.dataset_review import QuestionState, SessionPhase

        mock_question.state = QuestionState.ANSWERED
        mock_clarification_session.questions = [mock_question]
        mock_session.clarification_sessions = [mock_clarification_session]
        mock_session.current_phase = SessionPhase.CLARIFICATION

        engine = ClarificationEngine(mock_repository)
        engine.build_question_payload(mock_session)
        assert mock_session.current_phase == SessionPhase.REVIEW


# ── Tests for ClarificationEngine.record_answer ────────────

class TestRecordAnswer:
    """record_answer — answer persistence and state advancement."""

    @pytest.fixture
    def engine(self, mock_repository):
        from src.services.dataset_review.clarification_engine import ClarificationEngine
        return ClarificationEngine(mock_repository)

    def _make_command(self, answer_kind, answer_value):
        from src.models.auth import User
        from src.services.dataset_review.clarification_engine import ClarificationAnswerCommand
        from src.models.dataset_review import AnswerKind

        user = MagicMock(spec=User)
        user.id = "user-1"
        return ClarificationAnswerCommand(
            session=MagicMock(),
            question_id="q-1",
            answer_kind=answer_kind,
            answer_value=answer_value,
            user=user,
        )

    def test_record_selected_answer(self, engine, mock_repository, mock_session, mock_clarification_session, mock_question, mock_option):
        from src.models.dataset_review import AnswerKind, QuestionState

        mock_question.options = [mock_option]
        mock_clarification_session.questions = [mock_question]
        mock_session.clarification_sessions = [mock_clarification_session]

        command = self._make_command(AnswerKind.SELECTED, "US")
        command.session = mock_session

        with patch.object(engine, 'summarize_progress', return_value="1 resolved, 0 unresolved"):
            result = engine.record_answer(command)

        assert result.clarification_session is not None
        assert mock_question.state == QuestionState.ANSWERED
        mock_repository.bump_session_version.assert_called_once()
        mock_repository.db.add.assert_called()
        mock_repository.db.commit.assert_called()
        mock_repository.db.flush.assert_called()
        mock_repository.db.refresh.assert_called()

    def test_rejects_duplicate_answer(self, engine, mock_repository, mock_session, mock_clarification_session, mock_question):
        from src.models.dataset_review import AnswerKind, ClarificationAnswer

        mock_question.answer = MagicMock(spec=ClarificationAnswer)
        mock_clarification_session.questions = [mock_question]
        mock_session.clarification_sessions = [mock_clarification_session]

        command = self._make_command(AnswerKind.SELECTED, "US")
        command.session = mock_session

        with pytest.raises(ValueError, match="already answered"):
            engine.record_answer(command)

    def test_rejects_non_active_question(self, engine, mock_repository, mock_session, mock_clarification_session, mock_question):
        from src.models.dataset_review import AnswerKind

        mock_clarification_session.questions = [mock_question]
        mock_clarification_session.current_question_id = "q-2"  # different question is active
        mock_session.clarification_sessions = [mock_clarification_session]
        mock_question.answer = None

        command = self._make_command(AnswerKind.SELECTED, "US")
        command.session = mock_session

        with pytest.raises(ValueError, match="Only the active"):
            engine.record_answer(command)

    def test_rejects_no_clarification_session(self, engine, mock_repository, mock_session):
        from src.models.dataset_review import AnswerKind

        mock_session.clarification_sessions = []
        command = self._make_command(AnswerKind.SELECTED, "US")
        command.session = mock_session

        with pytest.raises(ValueError, match="Clarification session not found"):
            engine.record_answer(command)

    def test_rejects_foreign_question(self, engine, mock_repository, mock_session, mock_clarification_session):
        from src.models.dataset_review import AnswerKind

        mock_clarification_session.questions = []  # no questions
        mock_session.clarification_sessions = [mock_clarification_session]

        command = self._make_command(AnswerKind.SELECTED, "US")
        command.session = mock_session

        with pytest.raises(ValueError, match="Clarification question not found"):
            engine.record_answer(command)

    def test_skipped_answer_sets_state(self, engine, mock_repository, mock_session, mock_clarification_session, mock_question, mock_option):
        from src.models.dataset_review import AnswerKind, QuestionState

        mock_question.options = [mock_option]
        mock_clarification_session.questions = [mock_question]
        mock_session.clarification_sessions = [mock_clarification_session]

        command = self._make_command(AnswerKind.SKIPPED, None)
        command.session = mock_session

        with patch.object(engine, 'summarize_progress', return_value="0 resolved, 1 unresolved"):
            result = engine.record_answer(command)

        assert mock_question.state == QuestionState.SKIPPED

    def test_expert_review_answer_sets_state(self, engine, mock_repository, mock_session, mock_clarification_session, mock_question, mock_option):
        from src.models.dataset_review import AnswerKind, QuestionState

        mock_question.options = [mock_option]
        mock_clarification_session.questions = [mock_question]
        mock_session.clarification_sessions = [mock_clarification_session]

        command = self._make_command(AnswerKind.EXPERT_REVIEW, None)
        command.session = mock_session

        with patch.object(engine, 'summarize_progress', return_value="0 resolved, 1 unresolved"):
            result = engine.record_answer(command)

        assert mock_question.state == QuestionState.EXPERT_REVIEW


# ── Tests for summarize_progress ───────────────────────────

class TestSummarizeProgress:
    """summarize_progress — readable progress string."""

    def test_format(self):
        from src.services.dataset_review.clarification_engine import ClarificationEngine
        from src.models.dataset_review import ClarificationSession, QuestionState

        mock_repo = MagicMock()
        engine = ClarificationEngine(mock_repo)

        cs = MagicMock(spec=ClarificationSession)
        q1 = MagicMock()
        q1.state = QuestionState.ANSWERED
        q2 = MagicMock()
        q2.state = QuestionState.OPEN
        cs.questions = [q1, q2]

        result = engine.summarize_progress(cs)
        assert result == "1 resolved, 1 unresolved"


# ── Tests for _get_latest_clarification_session ────────────

class TestGetLatestClarificationSession:
    """_get_latest_clarification_session — sorted by started_at."""

    def test_returns_latest(self):
        from src.services.dataset_review.clarification_engine import ClarificationEngine

        mock_repo = MagicMock()
        engine = ClarificationEngine(mock_repo)

        session = MagicMock()
        session.clarification_sessions = [
            MagicMock(started_at=datetime(2024, 1, 1, tzinfo=timezone.utc), clarification_session_id="cs-1"),
            MagicMock(started_at=datetime(2024, 6, 1, tzinfo=timezone.utc), clarification_session_id="cs-2"),
        ]
        result = engine._get_latest_clarification_session(session)
        assert result.clarification_session_id == "cs-2"

    def test_none_when_empty(self):
        from src.services.dataset_review.clarification_engine import ClarificationEngine

        mock_repo = MagicMock()
        engine = ClarificationEngine(mock_repo)

        session = MagicMock()
        session.clarification_sessions = []
        assert engine._get_latest_clarification_session(session) is None


# ── Tests for _find_question ───────────────────────────────

class TestFindQuestion:
    """_find_question — resolve question by ID."""

    def test_found(self):
        from src.services.dataset_review.clarification_engine import ClarificationEngine

        mock_repo = MagicMock()
        engine = ClarificationEngine(mock_repo)

        cs = MagicMock()
        q1 = MagicMock()
        q1.question_id = "q-1"
        q2 = MagicMock()
        q2.question_id = "q-2"
        cs.questions = [q1, q2]

        result = engine._find_question(cs, "q-2")
        assert result.question_id == "q-2"

    def test_not_found(self):
        from src.services.dataset_review.clarification_engine import ClarificationEngine

        mock_repo = MagicMock()
        engine = ClarificationEngine(mock_repo)

        cs = MagicMock()
        cs.questions = [MagicMock(question_id="q-1")]

        result = engine._find_question(cs, "q-nonexistent")
        assert result is None


# #endregion Test.DatasetReview.ClarificationEngine
