# #region Test.DatasetReviewSemanticModels [C:3] [TYPE Module] [SEMANTICS test,sqlalchemy,dataset,review,semantic,model]
# @BRIEF Verify SemanticSource, SemanticFieldEntry, and SemanticCandidate SQLAlchemy models — construction, defaults, FK constraints, relationships.
# @RELATION BINDS_TO -> [DatasetReviewSemanticModels]
# @TEST_EDGE: missing_session_id -> FK violation on semantic source
# @TEST_EDGE: cascade_delete -> Session delete cascades to semantic entries
# @TEST_EDGE: field_lock -> is_locked defaults to False
# @TEST_EDGE: candidate_status_transition -> Status defaults to PROPOSED
import uuid
from datetime import UTC, datetime

from sqlalchemy import create_engine, text
from sqlalchemy.orm import Session as SASession
import pytest

from src.models.dataset_review_pkg._session_models import DatasetReviewSession
from src.models.dataset_review_pkg._semantic_models import (
    Base,
    SemanticCandidate,
    SemanticFieldEntry,
    SemanticSource,
)
from src.models.dataset_review_pkg._enums import (
    CandidateMatchType,
    CandidateStatus,
    FieldKind,
    FieldProvenance,
    SemanticSourceStatus,
    SemanticSourceType,
    TrustLevel,
)
from src.models.mapping import Environment


@pytest.fixture(scope="function")
def db_session():
    """Fresh in-memory SQLite with FK enforcement using single connection."""
    engine = create_engine("sqlite:///:memory:", connect_args={"check_same_thread": False})
    conn = engine.connect()
    conn.execute(text("PRAGMA foreign_keys=ON"))

    # Create the raw users table FIRST — multiple model tables FK-reference it
    conn.execute(text("CREATE TABLE IF NOT EXISTS users (id VARCHAR PRIMARY KEY)"))

    from src.models.mapping import Base as MappingBase
    MappingBase.metadata.create_all(conn)
    Base.metadata.create_all(conn)
    conn.commit()

    session = SASession(bind=conn)
    env = Environment(id="env-1", name="Test", url="https://example.com", credentials_id="cred-1")
    session.add(env)
    session.execute(text("INSERT OR IGNORE INTO users (id) VALUES ('user-1')"))
    session.commit()
    yield session
    session.close()
    conn.close()


def _create_session(db_session) -> DatasetReviewSession:
    """Helper to create a valid session."""
    rev = DatasetReviewSession(
        user_id="user-1", environment_id="env-1",
        source_kind="superset", source_input="test", dataset_ref="ds_1",
    )
    db_session.add(rev)
    db_session.flush()
    return rev


class TestSemanticSource:
    """Verify SemanticSource model."""

    def test_create_minimal(self, db_session):
        """Minimal required fields produce valid semantic source."""
        session = _create_session(db_session)
        src = SemanticSource(
            session_id=session.session_id,
            source_type=SemanticSourceType.AI_GENERATED,
            source_ref="ref-1",
            source_version="v1",
            display_name="AI Suggestions",
            trust_level=TrustLevel.GENERATED,
        )
        db_session.add(src)
        db_session.flush()

        assert src.source_id is not None
        assert isinstance(uuid.UUID(src.source_id), uuid.UUID)
        assert src.source_type == SemanticSourceType.AI_GENERATED
        assert src.trust_level == TrustLevel.GENERATED
        assert src.status == SemanticSourceStatus.AVAILABLE
        assert src.schema_overlap_score is None
        assert src.created_at is not None

    def test_all_fields(self, db_session):
        """All fields populated."""
        session = _create_session(db_session)
        src = SemanticSource(
            session_id=session.session_id,
            source_type=SemanticSourceType.UPLOADED_FILE,
            source_ref="file.csv",
            source_version="v2",
            display_name="Uploaded Dictionary",
            trust_level=TrustLevel.TRUSTED,
            schema_overlap_score=0.85,
            status=SemanticSourceStatus.APPLIED,
        )
        db_session.add(src)
        db_session.flush()

        assert src.schema_overlap_score == 0.85
        assert src.status == SemanticSourceStatus.APPLIED

    def test_all_source_types(self, db_session):
        """All SemanticSourceType values accepted."""
        session = _create_session(db_session)
        for st in SemanticSourceType:
            src = SemanticSource(
                session_id=session.session_id, source_type=st,
                source_ref="r", source_version="v", display_name="D",
                trust_level=TrustLevel.CANDIDATE,
            )
            db_session.add(src)
        db_session.flush()
        assert db_session.query(SemanticSource).count() == len(SemanticSourceType)

    def test_all_trust_levels(self, db_session):
        """All TrustLevel values accepted."""
        session = _create_session(db_session)
        for tl in TrustLevel:
            src = SemanticSource(
                session_id=session.session_id,
                source_type=SemanticSourceType.REFERENCE_DATASET,
                source_ref="r", source_version="v", display_name="D",
                trust_level=tl,
            )
            db_session.add(src)
        db_session.flush()
        assert db_session.query(SemanticSource).count() == len(TrustLevel)

    def test_relationship(self, db_session):
        """Navigate source -> session."""
        session = _create_session(db_session)
        src = SemanticSource(
            session_id=session.session_id,
            source_type=SemanticSourceType.NEIGHBOR_DATASET,
            source_ref="r", source_version="v", display_name="D",
            trust_level=TrustLevel.RECOMMENDED,
        )
        db_session.add(src)
        db_session.flush()
        assert src.session.session_id == session.session_id
        assert src.session in db_session


class TestSemanticFieldEntry:
    """Verify SemanticFieldEntry model."""

    def test_create_minimal(self, db_session):
        """Minimal required fields."""
        session = _create_session(db_session)
        entry = SemanticFieldEntry(
            session_id=session.session_id,
            field_name="revenue",
            field_kind=FieldKind.METRIC,
            last_changed_by="user-1",
        )
        db_session.add(entry)
        db_session.flush()

        assert entry.field_id is not None
        assert entry.field_name == "revenue"
        assert entry.field_kind == FieldKind.METRIC
        assert entry.provenance == FieldProvenance.UNRESOLVED
        assert entry.is_locked is False
        assert entry.has_conflict is False
        assert entry.needs_review is True
        assert entry.last_changed_by == "user-1"
        assert entry.created_at is not None
        assert entry.updated_at is not None
        # Nullable fields
        assert entry.verbose_name is None
        assert entry.description is None
        assert entry.display_format is None
        assert entry.source_id is None
        assert entry.source_version is None
        assert entry.confidence_rank is None
        assert entry.user_feedback is None

    def test_all_fields(self, db_session):
        """All fields populated."""
        session = _create_session(db_session)
        entry = SemanticFieldEntry(
            session_id=session.session_id,
            field_name="orders_count",
            field_kind=FieldKind.METRIC,
            verbose_name="Orders Count",
            description="Total number of orders",
            display_format=".0f",
            provenance=FieldProvenance.DICTIONARY_EXACT,
            source_id="src-1",
            source_version="v3",
            confidence_rank=1,
            is_locked=True,
            has_conflict=False,
            needs_review=False,
            last_changed_by="user-2",
            user_feedback="Looks correct",
        )
        db_session.add(entry)
        db_session.flush()

        assert entry.verbose_name == "Orders Count"
        assert entry.description == "Total number of orders"
        assert entry.provenance == FieldProvenance.DICTIONARY_EXACT
        assert entry.is_locked is True
        assert entry.user_feedback == "Looks correct"

    def test_cascade_delete(self, db_session):
        """Session delete cascades to field entries."""
        session = _create_session(db_session)
        entry = SemanticFieldEntry(
            session_id=session.session_id,
            field_name="col_a", field_kind=FieldKind.COLUMN,
            last_changed_by="user-1",
        )
        db_session.add(entry)
        db_session.flush()
        db_session.delete(session)
        db_session.flush()
        assert db_session.query(SemanticFieldEntry).count() == 0

    def test_relationship(self, db_session):
        """Navigate entry -> session."""
        session = _create_session(db_session)
        entry = SemanticFieldEntry(
            session_id=session.session_id,
            field_name="col_a", field_kind=FieldKind.COLUMN,
            last_changed_by="user-1",
        )
        db_session.add(entry)
        db_session.flush()
        assert entry.session is session


class TestSemanticCandidate:
    """Verify SemanticCandidate model."""

    def test_create_minimal(self, db_session):
        """Minimal required fields."""
        session = _create_session(db_session)
        entry = self._create_field_entry(db_session, session)
        cand = SemanticCandidate(
            field_id=entry.field_id,
            candidate_rank=1,
            match_type=CandidateMatchType.EXACT,
            confidence_score=0.95,
        )
        db_session.add(cand)
        db_session.flush()

        assert cand.candidate_id is not None
        assert cand.candidate_rank == 1
        assert cand.match_type == CandidateMatchType.EXACT
        assert cand.confidence_score == 0.95
        assert cand.status == CandidateStatus.PROPOSED
        assert cand.created_at is not None
        assert cand.proposed_verbose_name is None
        assert cand.proposed_description is None

    def test_all_fields(self, db_session):
        """All candidate fields populated."""
        session = _create_session(db_session)
        entry = self._create_field_entry(db_session, session)
        cand = SemanticCandidate(
            field_id=entry.field_id,
            source_id="src-2",
            candidate_rank=1,
            match_type=CandidateMatchType.FUZZY,
            confidence_score=0.72,
            proposed_verbose_name="Customer Name",
            proposed_description="Full customer name",
            proposed_display_format="text",
            status=CandidateStatus.ACCEPTED,
        )
        db_session.add(cand)
        db_session.flush()

        assert cand.proposed_verbose_name == "Customer Name"
        assert cand.status == CandidateStatus.ACCEPTED
        assert cand.source_id == "src-2"

    def test_relationship_field(self, db_session):
        """Navigate candidate -> field -> session."""
        session = _create_session(db_session)
        entry = self._create_field_entry(db_session, session)
        cand = SemanticCandidate(
            field_id=entry.field_id,
            candidate_rank=1, match_type=CandidateMatchType.GENERATED,
            confidence_score=0.5,
        )
        db_session.add(cand)
        db_session.flush()

        assert cand.field is entry
        assert cand.field.session is session

    def test_cascade_field_delete(self, db_session):
        """Field entry delete cascades to candidates."""
        session = _create_session(db_session)
        entry = self._create_field_entry(db_session, session)
        cand = SemanticCandidate(
            field_id=entry.field_id,
            candidate_rank=1, match_type=CandidateMatchType.EXACT,
            confidence_score=0.9,
        )
        db_session.add(cand)
        db_session.flush()
        db_session.delete(entry)
        db_session.flush()
        assert db_session.query(SemanticCandidate).count() == 0

    def test_multiple_candidates(self, db_session):
        """Field entry can have multiple candidates."""
        session = _create_session(db_session)
        entry = self._create_field_entry(db_session, session)
        for i, mt in enumerate(CandidateMatchType):
            cand = SemanticCandidate(
                field_id=entry.field_id,
                candidate_rank=i + 1,
                match_type=mt,
                confidence_score=1.0 - (i * 0.1),
            )
            db_session.add(cand)
        db_session.flush()
        assert len(entry.candidates) == len(CandidateMatchType)

    def _create_field_entry(self, db_session, session) -> SemanticFieldEntry:
        entry = SemanticFieldEntry(
            session_id=session.session_id,
            field_name="test_col", field_kind=FieldKind.COLUMN,
            last_changed_by="user-1",
        )
        db_session.add(entry)
        db_session.flush()
        return entry
# #endregion Test.DatasetReviewSemanticModels
