"""Tests for public API: basic schema validation and serialization.

Uses mock database sessions to test the public router logic
without requiring a real PostgreSQL connection.
"""

from __future__ import annotations

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

import pytest

sqlalchemy = pytest.importorskip("sqlalchemy", reason="sqlalchemy required for public API tests")

from app.schemas.document import DocumentFullOut, DocumentOut  # noqa: E402
from app.schemas.sanctuary import SanctuaryEntryOut  # noqa: E402


# ---------------------------------------------------------------------------
# Helpers: mock objects
# ---------------------------------------------------------------------------


def _make_document_mock(
    doc_id: uuid.UUID | None = None,
    slug: str = "licence-g1",
    title: str = "Licence G1",
    doc_type: str = "licence",
    version: str = "2.0.0",
    doc_status: str = "active",
    description: str | None = "La licence monetaire",
) -> MagicMock:
    """Create a mock Document for schema validation."""
    doc = MagicMock()
    doc.id = doc_id or uuid.uuid4()
    doc.slug = slug
    doc.title = title
    doc.doc_type = doc_type
    doc.version = version
    doc.status = doc_status
    doc.description = description
    doc.ipfs_cid = None
    doc.chain_anchor = None
    doc.genesis_json = None
    doc.created_at = datetime.now(timezone.utc)
    doc.updated_at = datetime.now(timezone.utc)
    doc.items = []
    return doc


def _make_item_mock(
    item_id: uuid.UUID | None = None,
    document_id: uuid.UUID | None = None,
    position: str = "1",
    item_type: str = "clause",
    title: str | None = "Article 1",
    current_text: str = "Texte de l'article",
    sort_order: int = 0,
) -> MagicMock:
    """Create a mock DocumentItem for schema validation."""
    item = MagicMock()
    item.id = item_id or uuid.uuid4()
    item.document_id = document_id or uuid.uuid4()
    item.position = position
    item.item_type = item_type
    item.title = title
    item.current_text = current_text
    item.voting_protocol_id = None
    item.sort_order = sort_order
    item.section_tag = None
    item.inertia_preset = "standard"
    item.is_permanent_vote = True
    item.created_at = datetime.now(timezone.utc)
    item.updated_at = datetime.now(timezone.utc)
    return item


def _make_sanctuary_entry_mock(
    entry_id: uuid.UUID | None = None,
    entry_type: str = "document",
    reference_id: uuid.UUID | None = None,
    title: str | None = "Licence G1 v2.0.0",
    content_hash: str = "abc123def456",
    ipfs_cid: str | None = "QmTestCid123",
    chain_tx_hash: str | None = "0xdeadbeef",
) -> MagicMock:
    """Create a mock SanctuaryEntry for schema validation."""
    entry = MagicMock()
    entry.id = entry_id or uuid.uuid4()
    entry.entry_type = entry_type
    entry.reference_id = reference_id or uuid.uuid4()
    entry.title = title
    entry.content_hash = content_hash
    entry.ipfs_cid = ipfs_cid
    entry.chain_tx_hash = chain_tx_hash
    entry.chain_block = 12345 if chain_tx_hash else None
    entry.metadata_json = None
    entry.created_at = datetime.now(timezone.utc)
    return entry


# ---------------------------------------------------------------------------
# Tests: DocumentOut schema serialization
# ---------------------------------------------------------------------------


class TestDocumentOutSchema:
    """Test DocumentOut schema validation from mock objects."""

    def test_document_out_basic(self):
        """DocumentOut validates from a mock document object."""
        doc = _make_document_mock()
        out = DocumentOut.model_validate(doc)

        assert out.slug == "licence-g1"
        assert out.title == "Licence G1"
        assert out.doc_type == "licence"
        assert out.version == "2.0.0"
        assert out.status == "active"
        # items_count is set explicitly after validation (not from model)
        out.items_count = 0
        assert out.items_count == 0

    def test_document_out_with_items_count(self):
        """DocumentOut can have items_count set after validation."""
        doc = _make_document_mock()
        out = DocumentOut.model_validate(doc)
        out.items_count = 42

        assert out.items_count == 42

    def test_document_out_all_fields_present(self):
        """All expected fields are present in the DocumentOut serialization."""
        doc = _make_document_mock()
        out = DocumentOut.model_validate(doc)
        data = out.model_dump()

        expected_fields = {
            "id", "slug", "title", "doc_type", "version", "status",
            "description", "ipfs_cid", "chain_anchor", "genesis_json",
            "created_at", "updated_at", "items_count",
        }
        assert expected_fields.issubset(set(data.keys()))


# ---------------------------------------------------------------------------
# Tests: DocumentFullOut schema serialization
# ---------------------------------------------------------------------------


class TestDocumentFullOutSchema:
    """Test DocumentFullOut schema validation (document with items)."""

    def test_document_full_out_empty_items(self):
        """DocumentFullOut works with an empty items list."""
        doc = _make_document_mock()
        doc.items = []
        out = DocumentFullOut.model_validate(doc)

        assert out.slug == "licence-g1"
        assert out.items == []

    def test_document_full_out_with_items(self):
        """DocumentFullOut includes items when present."""
        doc_id = uuid.uuid4()
        doc = _make_document_mock(doc_id=doc_id)
        item1 = _make_item_mock(document_id=doc_id, position="1", sort_order=0)
        item2 = _make_item_mock(document_id=doc_id, position="2", sort_order=1)
        doc.items = [item1, item2]

        out = DocumentFullOut.model_validate(doc)

        assert len(out.items) == 2
        assert out.items[0].position == "1"
        assert out.items[1].position == "2"


# ---------------------------------------------------------------------------
# Tests: SanctuaryEntryOut schema serialization
# ---------------------------------------------------------------------------


class TestSanctuaryEntryOutSchema:
    """Test SanctuaryEntryOut schema validation."""

    def test_sanctuary_entry_out_basic(self):
        """SanctuaryEntryOut validates from a mock entry."""
        entry = _make_sanctuary_entry_mock()
        out = SanctuaryEntryOut.model_validate(entry)

        assert out.entry_type == "document"
        assert out.content_hash == "abc123def456"
        assert out.ipfs_cid == "QmTestCid123"
        assert out.chain_tx_hash == "0xdeadbeef"
        assert out.chain_block == 12345

    def test_sanctuary_entry_out_without_ipfs(self):
        """SanctuaryEntryOut works when IPFS CID is None."""
        entry = _make_sanctuary_entry_mock(ipfs_cid=None, chain_tx_hash=None)
        out = SanctuaryEntryOut.model_validate(entry)

        assert out.ipfs_cid is None
        assert out.chain_tx_hash is None
        assert out.chain_block is None

    def test_sanctuary_entry_out_all_fields(self):
        """All expected fields are present in SanctuaryEntryOut."""
        entry = _make_sanctuary_entry_mock()
        out = SanctuaryEntryOut.model_validate(entry)
        data = out.model_dump()

        expected_fields = {
            "id", "entry_type", "reference_id", "title",
            "content_hash", "ipfs_cid", "chain_tx_hash",
            "chain_block", "metadata_json", "created_at",
        }
        assert expected_fields.issubset(set(data.keys()))

    def test_sanctuary_entry_types(self):
        """Different entry_type values are accepted."""
        for entry_type in ("document", "decision", "vote_result"):
            entry = _make_sanctuary_entry_mock(entry_type=entry_type)
            out = SanctuaryEntryOut.model_validate(entry)
            assert out.entry_type == entry_type
