# #region Test.PreviewSessionOps [C:3] [TYPE Module] [SEMANTICS test, translate, preview, session, query]
# @BRIEF Tests for preview_session_ops.py — get_preview_session.
# @RELATION BINDS_TO -> [preview_session_ops]
# @TEST_CONTRACT: get_preview_session -> dict | returns latest session with records
# @TEST_EDGE: no_session_found -> raises ValueError

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

import os
os.environ.setdefault("SECRET_KEY", "test-secret-key-for-testing")
os.environ.setdefault("DATABASE_URL", "sqlite:///:memory:")
os.environ.setdefault("AUTH_DATABASE_URL", "sqlite:///:memory:")

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

import pytest

from src.plugins.translate.preview_session_ops import get_preview_session


class TestGetPreviewSession:
    """get_preview_session — fetch latest preview session for a job."""

    def _make_session(self, overrides: dict | None = None) -> MagicMock:
        """Create a mock preview session."""
        session = MagicMock()
        session.id = "session-1"
        session.job_id = "job-1"
        session.status = "APPLIED"
        session.created_by = "user-1"
        session.created_at = datetime(2026, 1, 15, 12, 0, 0, tzinfo=timezone.utc)
        session.expires_at = datetime(2026, 2, 15, 12, 0, 0, tzinfo=timezone.utc)
        if overrides:
            for k, v in overrides.items():
                setattr(session, k, v)
        return session

    def _make_record(self) -> MagicMock:
        """Create a mock preview record."""
        r = MagicMock()
        r.id = "rec-1"
        r.source_sql = "SELECT * FROM foo"
        r.target_sql = "SELECT * FROM bar"
        r.source_object_type = "table"
        r.source_object_id = "obj-1"
        r.source_object_name = "foo"
        r.status = "done"
        r.feedback = None
        r.languages = []
        return r

    def _make_job(self, target_languages=None, target_dialect=None) -> MagicMock:
        """Create a mock translation job."""
        job = MagicMock()
        job.id = "job-1"
        job.target_languages = target_languages if target_languages is not None else ["ru", "de"]
        job.target_dialect = target_dialect if target_dialect else "clickhouse"
        return job

    def _setup_db_mock(self, session, records, job):
        """Configure db mock chain for the three queries in get_preview_session."""
        db = MagicMock()
        # Query 1: TranslationPreviewSession (filter + order_by + first)
        db.query.return_value.filter.return_value.order_by.return_value.first.return_value = session
        # Query 2: TranslationPreviewRecord (filter + all)
        db.query.return_value.filter.return_value.all.return_value = records
        # Query 3: TranslationJob (filter + first — no order_by)
        db.query.return_value.filter.return_value.first.return_value = job
        return db

    def test_returns_session_with_records(self):
        """Returns session dict with serialized records."""
        session = self._make_session()
        record = self._make_record()
        job = self._make_job()
        db = self._setup_db_mock(session, [record], job)

        with patch("src.plugins.translate.preview_session_ops._serialize_preview_record") as mock_ser:
            mock_ser.return_value = {"id": "rec-1", "source_sql": "SELECT * FROM foo"}
            result = get_preview_session(db, "job-1")

        assert result["id"] == "session-1"
        assert result["job_id"] == "job-1"
        assert result["status"] == "APPLIED"
        assert result["created_by"] == "user-1"
        assert "created_at" in result
        assert "expires_at" in result
        assert result["target_languages"] == ["ru", "de"]
        assert len(result["records"]) == 1
        assert result["records"][0]["id"] == "rec-1"

    def test_no_session_found_raises(self):
        """No session found raises ValueError."""
        db = MagicMock()
        db.query.return_value.filter.return_value.order_by.return_value.first.return_value = None

        with pytest.raises(ValueError, match="No preview session found for job 'job-1'"):
            get_preview_session(db, "job-1")

    def test_job_not_found_defaults_to_en(self):
        """Job not found falls back to ['en'] (last fallback)."""
        session = self._make_session()
        db = MagicMock()
        db.query.return_value.filter.return_value.order_by.return_value.first.return_value = session
        db.query.return_value.filter.return_value.all.return_value = []
        # No job found -> first (no order_by) returns None
        db.query.return_value.filter.return_value.first.return_value = None

        with patch("src.plugins.translate.preview_session_ops._serialize_preview_record") as mock_ser:
            result = get_preview_session(db, "job-1")

        # When job is None -> the fallback is ["en"]
        assert result["target_languages"] == ["en"]

    def test_job_no_target_languages_uses_dialect(self):
        """Job with None target_languages uses target_dialect."""
        session = self._make_session()
        job = self._make_job(target_dialect="postgresql")
        job.target_languages = None  # explicitly override to None
        db = self._setup_db_mock(session, [], job)

        with patch("src.plugins.translate.preview_session_ops._serialize_preview_record") as mock_ser:
            result = get_preview_session(db, "job-1")

        assert result["target_languages"] == ["postgresql"]

    def test_target_languages_not_list_normalized(self):
        """target_languages that is not a list gets wrapped in list."""
        session = self._make_session()
        job = self._make_job(target_languages="ru")  # string instead of list
        db = self._setup_db_mock(session, [], job)

        with patch("src.plugins.translate.preview_session_ops._serialize_preview_record") as mock_ser:
            result = get_preview_session(db, "job-1")

        assert result["target_languages"] == ["ru"]

    def test_session_expires_at_none(self):
        """Session without expires_at returns None for that field."""
        session = self._make_session({"expires_at": None})
        job = self._make_job()
        db = self._setup_db_mock(session, [], job)

        with patch("src.plugins.translate.preview_session_ops._serialize_preview_record") as mock_ser:
            result = get_preview_session(db, "job-1")

        assert result["expires_at"] is None

    def test_empty_records(self):
        """No records returns empty records list."""
        session = self._make_session()
        job = self._make_job()
        db = self._setup_db_mock(session, [], job)

        with patch("src.plugins.translate.preview_session_ops._serialize_preview_record"):
            result = get_preview_session(db, "job-1")

        assert result["records"] == []
# #endregion Test.PreviewSessionOps
