#!/usr/bin/env python3
"""
Tests for Story 5.01: IntentSignal — Data Model
AIVA RLM Nexus PRD v2 — Track A, Module 5 (Intent + Routing)

Black box tests (BB1-BB5): verify public API behaviour from the outside.
White box tests (WB1-WB6): verify internal invariants and structural properties.
Package test: verify __init__.py exports work.
No-SQLite test: confirm no sqlite3 import in the module.

No mocking required — pure data model with no I/O.
"""
import dataclasses
import inspect
import sys

import pytest

sys.path.insert(0, "/mnt/e/genesis-system")

from datetime import datetime, timezone
from core.intent import IntentType, IntentSignal


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _make_signal(**overrides) -> IntentSignal:
    """Return a minimal valid IntentSignal with optional field overrides."""
    defaults = dict(
        session_id="sess-001",
        utterance="I need to book a plumber",
        intent_type=IntentType.BOOK_JOB,
        confidence=0.95,
        extracted_entities={"service": "plumber"},
        requires_swarm=True,
        created_at=datetime.now(timezone.utc),
    )
    defaults.update(overrides)
    return IntentSignal(**defaults)


# ---------------------------------------------------------------------------
# BB1: IntentType.BOOK_JOB.value == "book_job"
# ---------------------------------------------------------------------------

class TestBB1_BookJobValue:
    """BB1: BOOK_JOB enum member has correct string value."""

    def test_book_job_value(self):
        assert IntentType.BOOK_JOB.value == "book_job", (
            f"Expected 'book_job', got '{IntentType.BOOK_JOB.value}'"
        )

    def test_qualify_lead_value(self):
        assert IntentType.QUALIFY_LEAD.value == "qualify_lead"

    def test_answer_faq_value(self):
        assert IntentType.ANSWER_FAQ.value == "answer_faq"

    def test_escalate_human_value(self):
        assert IntentType.ESCALATE_HUMAN.value == "escalate_human"

    def test_capture_memory_value(self):
        assert IntentType.CAPTURE_MEMORY.value == "capture_memory"

    def test_task_dispatch_value(self):
        assert IntentType.TASK_DISPATCH.value == "task_dispatch"


# ---------------------------------------------------------------------------
# BB2: IntentType.UNKNOWN.value == "unknown"
# ---------------------------------------------------------------------------

class TestBB2_UnknownValue:
    """BB2: UNKNOWN enum member has correct string value."""

    def test_unknown_value(self):
        assert IntentType.UNKNOWN.value == "unknown", (
            f"Expected 'unknown', got '{IntentType.UNKNOWN.value}'"
        )

    def test_unknown_accessible_by_name(self):
        assert IntentType["UNKNOWN"] is IntentType.UNKNOWN

    def test_unknown_accessible_by_value(self):
        assert IntentType("unknown") is IntentType.UNKNOWN


# ---------------------------------------------------------------------------
# BB3: IntentSignal instantiates with all required fields
# ---------------------------------------------------------------------------

class TestBB3_Instantiation:
    """BB3: IntentSignal can be created with all required fields."""

    def test_instantiation_no_error(self):
        signal = _make_signal()
        assert signal is not None

    def test_session_id_preserved(self):
        signal = _make_signal(session_id="test-session-42")
        assert signal.session_id == "test-session-42"

    def test_utterance_preserved(self):
        signal = _make_signal(utterance="Hello, I need help")
        assert signal.utterance == "Hello, I need help"

    def test_intent_type_preserved(self):
        signal = _make_signal(intent_type=IntentType.QUALIFY_LEAD)
        assert signal.intent_type is IntentType.QUALIFY_LEAD

    def test_confidence_preserved(self):
        signal = _make_signal(confidence=0.77)
        assert signal.confidence == 0.77

    def test_created_at_preserved(self):
        ts = datetime(2026, 2, 25, 12, 0, 0, tzinfo=timezone.utc)
        signal = _make_signal(created_at=ts)
        assert signal.created_at == ts


# ---------------------------------------------------------------------------
# BB4: requires_swarm=True is stored correctly
# ---------------------------------------------------------------------------

class TestBB4_RequiresSwarm:
    """BB4: requires_swarm boolean flag is stored exactly as given."""

    def test_requires_swarm_true(self):
        signal = _make_signal(requires_swarm=True)
        assert signal.requires_swarm is True

    def test_requires_swarm_false(self):
        signal = _make_signal(requires_swarm=False)
        assert signal.requires_swarm is False

    def test_requires_swarm_is_bool(self):
        signal = _make_signal(requires_swarm=True)
        assert isinstance(signal.requires_swarm, bool)


# ---------------------------------------------------------------------------
# BB5: raw_gemini_response defaults to None when not provided
# ---------------------------------------------------------------------------

class TestBB5_RawGeminiResponseDefault:
    """BB5: raw_gemini_response is None when not explicitly set."""

    def test_raw_gemini_response_defaults_none(self):
        signal = _make_signal()
        assert signal.raw_gemini_response is None

    def test_raw_gemini_response_accepts_string(self):
        signal = _make_signal(raw_gemini_response='{"intent": "book_job"}')
        assert signal.raw_gemini_response == '{"intent": "book_job"}'

    def test_raw_gemini_response_accepts_empty_string(self):
        signal = _make_signal(raw_gemini_response="")
        assert signal.raw_gemini_response == ""


# ---------------------------------------------------------------------------
# WB1: Enum has exactly 7 members
# ---------------------------------------------------------------------------

class TestWB1_EnumMemberCount:
    """WB1: IntentType must have exactly 7 members (including UNKNOWN)."""

    def test_exactly_seven_members(self):
        members = list(IntentType)
        assert len(members) == 7, (
            f"Expected 7 IntentType members, found {len(members)}: {members}"
        )

    def test_all_expected_names_present(self):
        names = {m.name for m in IntentType}
        expected = {
            "BOOK_JOB", "QUALIFY_LEAD", "ANSWER_FAQ",
            "ESCALATE_HUMAN", "CAPTURE_MEMORY", "TASK_DISPATCH", "UNKNOWN",
        }
        assert names == expected, (
            f"Name mismatch. Extra: {names - expected}. Missing: {expected - names}"
        )


# ---------------------------------------------------------------------------
# WB2: Two identical IntentSignal instances are equal (__eq__ works)
# ---------------------------------------------------------------------------

class TestWB2_DataclassEquality:
    """WB2: Dataclass __eq__ compares by field values, not identity."""

    def test_equal_instances(self):
        ts = datetime(2026, 2, 25, 10, 0, tzinfo=timezone.utc)
        a = IntentSignal(
            session_id="s1",
            utterance="book a job",
            intent_type=IntentType.BOOK_JOB,
            confidence=0.9,
            extracted_entities={},
            requires_swarm=True,
            created_at=ts,
        )
        b = IntentSignal(
            session_id="s1",
            utterance="book a job",
            intent_type=IntentType.BOOK_JOB,
            confidence=0.9,
            extracted_entities={},
            requires_swarm=True,
            created_at=ts,
        )
        assert a == b, "Identical IntentSignal instances should be equal"

    def test_different_confidence_not_equal(self):
        ts = datetime(2026, 2, 25, 10, 0, tzinfo=timezone.utc)
        a = IntentSignal(
            session_id="s1", utterance="x", intent_type=IntentType.UNKNOWN,
            confidence=0.5, extracted_entities={}, requires_swarm=False, created_at=ts,
        )
        b = IntentSignal(
            session_id="s1", utterance="x", intent_type=IntentType.UNKNOWN,
            confidence=0.9, extracted_entities={}, requires_swarm=False, created_at=ts,
        )
        assert a != b


# ---------------------------------------------------------------------------
# WB3: confidence field accepts 0.0 and 1.0 without error
# ---------------------------------------------------------------------------

class TestWB3_ConfidenceBoundaries:
    """WB3: confidence=0.0 and confidence=1.0 are both valid."""

    def test_confidence_zero(self):
        signal = _make_signal(confidence=0.0)
        assert signal.confidence == 0.0

    def test_confidence_one(self):
        signal = _make_signal(confidence=1.0)
        assert signal.confidence == 1.0

    def test_confidence_midpoint(self):
        signal = _make_signal(confidence=0.5)
        assert signal.confidence == 0.5

    def test_confidence_is_float(self):
        signal = _make_signal(confidence=0.8)
        assert isinstance(signal.confidence, float), (
            f"Expected float, got {type(signal.confidence)}"
        )


# ---------------------------------------------------------------------------
# WB4: extracted_entities accepts {} empty dict
# ---------------------------------------------------------------------------

class TestWB4_ExtractedEntities:
    """WB4: extracted_entities accepts empty and non-empty dicts."""

    def test_empty_dict(self):
        signal = _make_signal(extracted_entities={})
        assert signal.extracted_entities == {}

    def test_populated_dict(self):
        entities = {"name": "George", "location": "Cairns", "service": "plumbing"}
        signal = _make_signal(extracted_entities=entities)
        assert signal.extracted_entities == entities

    def test_extracted_entities_is_dict(self):
        signal = _make_signal(extracted_entities={"k": "v"})
        assert isinstance(signal.extracted_entities, dict)


# ---------------------------------------------------------------------------
# WB5: IntentType is an Enum (isinstance check)
# ---------------------------------------------------------------------------

class TestWB5_IntentTypeIsEnum:
    """WB5: IntentType is a subclass of Enum."""

    def test_intent_type_is_enum_subclass(self):
        from enum import Enum
        assert issubclass(IntentType, Enum), (
            "IntentType must be a subclass of Enum"
        )

    def test_members_are_enum_instances(self):
        from enum import Enum
        for member in IntentType:
            assert isinstance(member, Enum), (
                f"{member} is not an Enum instance"
            )


# ---------------------------------------------------------------------------
# WB6: IntentSignal is a dataclass (dataclasses.is_dataclass check)
# ---------------------------------------------------------------------------

class TestWB6_IntentSignalIsDataclass:
    """WB6: IntentSignal is a proper dataclass."""

    def test_is_dataclass(self):
        assert dataclasses.is_dataclass(IntentSignal), (
            "IntentSignal must be decorated with @dataclass"
        )

    def test_is_dataclass_instance(self):
        signal = _make_signal()
        assert dataclasses.is_dataclass(signal), (
            "IntentSignal instance must also satisfy is_dataclass"
        )

    def test_has_eight_fields(self):
        fields = dataclasses.fields(IntentSignal)
        assert len(fields) == 8, (
            f"Expected 8 fields, found {len(fields)}: {[f.name for f in fields]}"
        )

    def test_field_names_correct(self):
        field_names = {f.name for f in dataclasses.fields(IntentSignal)}
        expected = {
            "session_id", "utterance", "intent_type", "confidence",
            "extracted_entities", "requires_swarm", "created_at",
            "raw_gemini_response",
        }
        assert field_names == expected, (
            f"Field mismatch. Extra: {field_names - expected}. Missing: {expected - field_names}"
        )


# ---------------------------------------------------------------------------
# Package export tests
# ---------------------------------------------------------------------------

class TestPackageExports:
    """Verify that core.intent exports both classes via __init__.py."""

    def test_intent_type_importable_from_package(self):
        from core.intent import IntentType as IT
        assert IT is IntentType

    def test_intent_signal_importable_from_package(self):
        from core.intent import IntentSignal as IS
        assert IS is IntentSignal

    def test_all_exports_defined(self):
        import core.intent as pkg
        assert hasattr(pkg, "__all__")
        assert "IntentType" in pkg.__all__
        assert "IntentSignal" in pkg.__all__


# ---------------------------------------------------------------------------
# No-SQLite test
# ---------------------------------------------------------------------------

class TestNoSQLite:
    """Confirm that no sqlite3 import appears anywhere in the intent module."""

    def test_no_sqlite3_in_intent_signal(self):
        import core.intent.intent_signal as module
        source = inspect.getsource(module)
        assert "import sqlite3" not in source, (
            "sqlite3 is FORBIDDEN in intent_signal.py (Rule 7)"
        )

    def test_no_sqlite3_in_init(self):
        import core.intent as pkg
        source = inspect.getsource(pkg)
        assert "import sqlite3" not in source, (
            "sqlite3 is FORBIDDEN in core/intent/__init__.py (Rule 7)"
        )


# ---------------------------------------------------------------------------
# Run summary
# ---------------------------------------------------------------------------

if __name__ == "__main__":
    result = pytest.main([__file__, "-v", "--tb=short"])
    sys.exit(result)
