# #region Test.DatasetReview.ClarificationHelpers [C:3] [TYPE Module] [SEMANTICS test,dataset,clarification,helpers,question,selection]
# @BRIEF Tests for clarification_pkg/_helpers.py — pure helper functions: select_next_open_question,
#        count_resolved_questions, count_remaining_questions, normalize_answer_value,
#        build_impact_summary, upsert_clarification_finding, derive_readiness_state, derive_recommended_action.
# @RELATION BINDS_TO -> [ClarificationHelpers]
# @TEST_EDGE: select_next_open_question -> Priority ordering, no open questions
# @TEST_EDGE: count_resolved_questions -> All answered, mixed, none
# @TEST_EDGE: count_remaining_questions -> Open, skipped, expert review counts
# @TEST_EDGE: normalize_answer_value -> Selected valid/invalid, skipped, expert, missing required
# @TEST_EDGE: build_impact_summary -> SKIPPED, EXPERT_REVIEW, selected
# @TEST_EDGE: upsert_clarification_finding -> New finding creation, existing update
# @TEST_EDGE: derive_readiness_state -> Clarification active, needed, review ready
# @TEST_EDGE: derive_recommended_action -> Answer next, start, review

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
import pytest


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

@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 does this dataset cover?"
    q.why_it_matters = "Region affects row-level 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"
    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 = 3
    cs.summary_delta = ""
    return cs


# ── Tests for select_next_open_question ────────────────────

class TestSelectNextOpenQuestion:
    """select_next_open_question — priority-driven selection."""

    def test_returns_highest_priority(self):
        from src.services.dataset_review.clarification_pkg._helpers import select_next_open_question
        from src.models.dataset_review import QuestionState

        q1 = MagicMock()
        q1.question_id = "q-1"
        q1.priority = 5
        q1.state = QuestionState.OPEN
        q1.created_at = datetime(2024, 6, 1, tzinfo=timezone.utc)

        q2 = MagicMock()
        q2.question_id = "q-2"
        q2.priority = 10
        q2.state = QuestionState.OPEN
        q2.created_at = datetime(2024, 6, 2, tzinfo=timezone.utc)

        cs = MagicMock()
        cs.questions = [q1, q2]

        result = select_next_open_question(cs)
        assert result is not None
        assert result.question_id == "q-2"  # Higher priority first

    def test_returns_none_when_no_open(self):
        from src.services.dataset_review.clarification_pkg._helpers import select_next_open_question
        from src.models.dataset_review import QuestionState

        q1 = MagicMock()
        q1.state = QuestionState.ANSWERED

        cs = MagicMock()
        cs.questions = [q1]

        result = select_next_open_question(cs)
        assert result is None

    def test_empty_questions_returns_none(self):
        from src.services.dataset_review.clarification_pkg._helpers import select_next_open_question

        cs = MagicMock()
        cs.questions = []

        result = select_next_open_question(cs)
        assert result is None


# ── Tests for count_resolved_questions ─────────────────────

class TestCountResolvedQuestions:
    """count_resolved_questions — count of ANSWERED questions."""

    def test_all_answered(self):
        from src.services.dataset_review.clarification_pkg._helpers import count_resolved_questions
        from src.models.dataset_review import QuestionState

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

        assert count_resolved_questions(cs) == 2

    def test_mixed_states(self):
        from src.services.dataset_review.clarification_pkg._helpers import count_resolved_questions
        from src.models.dataset_review import QuestionState

        cs = MagicMock()
        states = [QuestionState.ANSWERED, QuestionState.OPEN, QuestionState.SKIPPED]
        cs.questions = [
            MagicMock(state=s) for s in states
        ]
        assert count_resolved_questions(cs) == 1

    def test_no_questions(self):
        from src.services.dataset_review.clarification_pkg._helpers import count_resolved_questions

        cs = MagicMock()
        cs.questions = []
        assert count_resolved_questions(cs) == 0


# ── Tests for count_remaining_questions ────────────────────

class TestCountRemainingQuestions:
    """count_remaining_questions — unresolved (open/skipped/expert) counts."""

    def test_counts_open_skipped_expert(self):
        from src.services.dataset_review.clarification_pkg._helpers import count_remaining_questions
        from src.models.dataset_review import QuestionState

        cs = MagicMock()
        states = [QuestionState.OPEN, QuestionState.SKIPPED, QuestionState.EXPERT_REVIEW, QuestionState.ANSWERED]
        cs.questions = [
            MagicMock(state=s) for s in states
        ]
        assert count_remaining_questions(cs) == 3

    def test_all_resolved(self):
        from src.services.dataset_review.clarification_pkg._helpers import count_remaining_questions
        from src.models.dataset_review import QuestionState

        cs = MagicMock()
        cs.questions = [
            MagicMock(state=QuestionState.ANSWERED),
        ]
        assert count_remaining_questions(cs) == 0


# ── Tests for normalize_answer_value ───────────────────────

class TestNormalizeAnswerValue:
    """normalize_answer_value — validation and normalization."""

    def test_selected_valid(self):
        from src.services.dataset_review.clarification_pkg._helpers import normalize_answer_value
        from src.models.dataset_review import AnswerKind, ClarificationQuestion

        q = MagicMock(spec=ClarificationQuestion)
        opt = MagicMock()
        opt.value = "US"
        q.options = [opt]

        result = normalize_answer_value(AnswerKind.SELECTED, "US", q)
        assert result == "US"

    def test_selected_invalid_raises(self):
        from src.services.dataset_review.clarification_pkg._helpers import normalize_answer_value
        from src.models.dataset_review import AnswerKind, ClarificationQuestion

        q = MagicMock(spec=ClarificationQuestion)
        opt = MagicMock()
        opt.value = "US"
        q.options = [opt]

        with pytest.raises(ValueError, match="must match"):
            normalize_answer_value(AnswerKind.SELECTED, "EU", q)

    def test_selected_empty_value_raises(self):
        from src.services.dataset_review.clarification_pkg._helpers import normalize_answer_value
        from src.models.dataset_review import AnswerKind, ClarificationQuestion

        q = MagicMock(spec=ClarificationQuestion)
        q.options = []

        with pytest.raises(ValueError, match="required"):
            normalize_answer_value(AnswerKind.SELECTED, "", q)

    def test_custom_requires_non_empty(self):
        from src.services.dataset_review.clarification_pkg._helpers import normalize_answer_value
        from src.models.dataset_review import AnswerKind, ClarificationQuestion

        q = MagicMock(spec=ClarificationQuestion)
        q.options = []

        result = normalize_answer_value(AnswerKind.CUSTOM, "my_value", q)
        assert result == "my_value"

    def test_custom_empty_raises(self):
        from src.services.dataset_review.clarification_pkg._helpers import normalize_answer_value
        from src.models.dataset_review import AnswerKind, ClarificationQuestion

        q = MagicMock(spec=ClarificationQuestion)
        q.options = []

        with pytest.raises(ValueError, match="required"):
            normalize_answer_value(AnswerKind.CUSTOM, "", q)

    def test_skipped_returns_skipped(self):
        from src.services.dataset_review.clarification_pkg._helpers import normalize_answer_value
        from src.models.dataset_review import AnswerKind, ClarificationQuestion

        q = MagicMock(spec=ClarificationQuestion)
        q.options = []

        result = normalize_answer_value(AnswerKind.SKIPPED, None, q)
        assert result == "skipped"

    def test_expert_review_default(self):
        from src.services.dataset_review.clarification_pkg._helpers import normalize_answer_value
        from src.models.dataset_review import AnswerKind, ClarificationQuestion

        q = MagicMock(spec=ClarificationQuestion)
        q.options = []

        result = normalize_answer_value(AnswerKind.EXPERT_REVIEW, None, q)
        assert result == "expert_review"


# ── Tests for build_impact_summary ─────────────────────────

class TestBuildImpactSummary:
    """build_impact_summary — readable audit note."""

    def test_skipped(self):
        from src.services.dataset_review.clarification_pkg._helpers import build_impact_summary
        from src.models.dataset_review import AnswerKind

        q = MagicMock()
        q.topic_ref = "region_mapping"
        summary = build_impact_summary(q, AnswerKind.SKIPPED, None)
        assert "skipped" in summary
        assert "region_mapping" in summary

    def test_expert_review(self):
        from src.services.dataset_review.clarification_pkg._helpers import build_impact_summary
        from src.models.dataset_review import AnswerKind

        q = MagicMock()
        q.topic_ref = "region_mapping"
        summary = build_impact_summary(q, AnswerKind.EXPERT_REVIEW, None)
        assert "expert review" in summary

    def test_selected(self):
        from src.services.dataset_review.clarification_pkg._helpers import build_impact_summary
        from src.models.dataset_review import AnswerKind

        q = MagicMock()
        q.topic_ref = "region_mapping"
        summary = build_impact_summary(q, AnswerKind.SELECTED, "US")
        assert "US" in summary
        assert "recorded" in summary


# ── Tests for upsert_clarification_finding ─────────────────

class TestUpsertClarificationFinding:
    """upsert_clarification_finding — create or update findings per question."""

    def test_creates_new_finding_for_selected(self):
        from src.services.dataset_review.clarification_pkg._helpers import upsert_clarification_finding
        from src.models.dataset_review import AnswerKind, DatasetReviewSession

        session = MagicMock(spec=DatasetReviewSession)
        session.session_id = "sess-1"
        session.findings = []
        question = MagicMock()
        question.question_id = "q-1"
        question.topic_ref = "region_mapping"
        db = MagicMock()

        finding = upsert_clarification_finding(session, question, AnswerKind.SELECTED, "US", db)
        assert finding is not None
        assert finding.area.value == "clarification"
        assert finding.code == "CLARIFICATION_RESOLVED"
        assert finding.resolution_state.value == "resolved"
        db.add.assert_called_once()

    def test_creates_new_finding_for_skipped(self):
        from src.services.dataset_review.clarification_pkg._helpers import upsert_clarification_finding
        from src.models.dataset_review import AnswerKind, DatasetReviewSession

        session = MagicMock(spec=DatasetReviewSession)
        session.session_id = "sess-1"
        session.findings = []
        question = MagicMock()
        question.question_id = "q-1"
        question.topic_ref = "region_mapping"
        db = MagicMock()

        finding = upsert_clarification_finding(session, question, AnswerKind.SKIPPED, None, db)
        assert finding.code == "CLARIFICATION_SKIPPED"
        assert finding.resolution_state.value == "skipped"

    def test_creates_new_finding_for_expert_review(self):
        from src.services.dataset_review.clarification_pkg._helpers import upsert_clarification_finding
        from src.models.dataset_review import AnswerKind, DatasetReviewSession

        session = MagicMock(spec=DatasetReviewSession)
        session.session_id = "sess-1"
        session.findings = []
        question = MagicMock()
        question.question_id = "q-1"
        question.topic_ref = "region_mapping"
        db = MagicMock()

        finding = upsert_clarification_finding(session, question, AnswerKind.EXPERT_REVIEW, None, db)
        assert finding.code == "CLARIFICATION_EXPERT_REVIEW"
        assert finding.resolution_state.value == "expert_review"

    def test_updates_existing_finding(self):
        from src.services.dataset_review.clarification_pkg._helpers import upsert_clarification_finding
        from src.models.dataset_review import (
            AnswerKind, DatasetReviewSession, ValidationFinding, FindingArea, ResolutionState,
        )

        session = MagicMock(spec=DatasetReviewSession)
        session.session_id = "sess-1"
        existing = MagicMock(spec=ValidationFinding)
        existing.area = FindingArea.CLARIFICATION
        existing.caused_by_ref = "clarification:q-1"
        session.findings = [existing]
        question = MagicMock()
        question.question_id = "q-1"
        question.topic_ref = "region_mapping"
        db = MagicMock()

        finding = upsert_clarification_finding(session, question, AnswerKind.SELECTED, "US", db)
        # Should NOT call db.add for existing
        db.add.assert_not_called()
        assert finding.code == "CLARIFICATION_RESOLVED"

    def test_skipped_keeps_open_resolution_state(self):
        from src.services.dataset_review.clarification_pkg._helpers import upsert_clarification_finding
        from src.models.dataset_review import AnswerKind, DatasetReviewSession

        session = MagicMock(spec=DatasetReviewSession)
        session.session_id = "sess-1"
        session.findings = []
        question = MagicMock()
        question.question_id = "q-1"
        question.topic_ref = "region_mapping"
        db = MagicMock()

        finding = upsert_clarification_finding(session, question, AnswerKind.SKIPPED, None, db)
        assert finding.resolved_at is None


# ── Tests for derive_readiness_state ───────────────────────

class TestDeriveReadinessState:
    """derive_readiness_state — post-clarification readiness."""

    def test_none_clarification_preserves_current(self):
        from src.services.dataset_review.clarification_pkg._helpers import derive_readiness_state
        from src.models.dataset_review import DatasetReviewSession

        session = MagicMock(spec=DatasetReviewSession)
        session.readiness_state = MagicMock(value="review_ready")
        result = derive_readiness_state(session, None)
        assert result.value == "review_ready"

    def test_current_question_active(self):
        from src.services.dataset_review.clarification_pkg._helpers import derive_readiness_state
        from src.models.dataset_review import ClarificationSession, DatasetReviewSession

        session = MagicMock(spec=DatasetReviewSession)
        cs = MagicMock(spec=ClarificationSession)
        cs.current_question_id = "q-1"
        cs.remaining_count = 5
        result = derive_readiness_state(session, cs)
        assert result.value == "clarification_active"

    def test_remaining_count_over_zero(self):
        from src.services.dataset_review.clarification_pkg._helpers import derive_readiness_state
        from src.models.dataset_review import ClarificationSession, DatasetReviewSession

        session = MagicMock(spec=DatasetReviewSession)
        cs = MagicMock(spec=ClarificationSession)
        cs.current_question_id = None
        cs.remaining_count = 2
        result = derive_readiness_state(session, cs)
        assert result.value == "clarification_needed"

    def test_all_resolved(self):
        from src.services.dataset_review.clarification_pkg._helpers import derive_readiness_state
        from src.models.dataset_review import ClarificationSession, DatasetReviewSession

        session = MagicMock(spec=DatasetReviewSession)
        cs = MagicMock(spec=ClarificationSession)
        cs.current_question_id = None
        cs.remaining_count = 0
        result = derive_readiness_state(session, cs)
        assert result.value == "review_ready"


# ── Tests for derive_recommended_action ────────────────────

class TestDeriveRecommendedAction:
    """derive_recommended_action — post-clarification action guidance."""

    def test_none_clarification_preserves_current(self):
        from src.services.dataset_review.clarification_pkg._helpers import derive_recommended_action
        from src.models.dataset_review import DatasetReviewSession

        session = MagicMock(spec=DatasetReviewSession)
        session.recommended_action = MagicMock(value="launch_dataset")
        result = derive_recommended_action(session, None)
        assert result.value == "launch_dataset"

    def test_current_question_prompts_answer(self):
        from src.services.dataset_review.clarification_pkg._helpers import derive_recommended_action
        from src.models.dataset_review import ClarificationSession, DatasetReviewSession

        session = MagicMock(spec=DatasetReviewSession)
        cs = MagicMock(spec=ClarificationSession)
        cs.current_question_id = "q-1"
        cs.remaining_count = 3
        result = derive_recommended_action(session, cs)
        assert result.value == "answer_next_question"

    def test_remaining_unresolved_prompts_start(self):
        from src.services.dataset_review.clarification_pkg._helpers import derive_recommended_action
        from src.models.dataset_review import ClarificationSession, DatasetReviewSession

        session = MagicMock(spec=DatasetReviewSession)
        cs = MagicMock(spec=ClarificationSession)
        cs.current_question_id = None
        cs.remaining_count = 2
        result = derive_recommended_action(session, cs)
        assert result.value == "start_clarification"

    def test_all_resolved_returns_review(self):
        from src.services.dataset_review.clarification_pkg._helpers import derive_recommended_action
        from src.models.dataset_review import ClarificationSession, DatasetReviewSession

        session = MagicMock(spec=DatasetReviewSession)
        cs = MagicMock(spec=ClarificationSession)
        cs.current_question_id = None
        cs.remaining_count = 0
        result = derive_recommended_action(session, cs)
        assert result.value == "review_documentation"


# #endregion Test.DatasetReview.ClarificationHelpers
