"""Tests for API schemas — camelCase serialization and validation."""

import pytest

from api.schemas import (
    AnalysisResponse,
    CreateAnalysisRequest,
    DocumentResponse,
    PipelineOptionsRequest,
    _to_camel,
)


class TestToCamel:
    def test_single_word(self):
        assert _to_camel("status") == "status"

    def test_two_words(self):
        assert _to_camel("document_id") == "documentId"

    def test_three_words(self):
        assert _to_camel("content_html_raw") == "contentHtmlRaw"

    def test_already_camel(self):
        assert _to_camel("documentId") == "documentId"


class TestDocumentResponse:
    def test_serializes_to_camel_case(self):
        doc = DocumentResponse(
            id="1",
            filename="test.pdf",
            content_type="application/pdf",
            file_size=1024,
            page_count=5,
            created_at="2024-01-01",
        )
        data = doc.model_dump(by_alias=True)
        assert "contentType" in data
        assert "fileSize" in data
        assert "pageCount" in data
        assert "createdAt" in data
        # Original snake_case should not appear
        assert "content_type" not in data
        assert "file_size" not in data

    def test_optional_fields_default_to_none(self):
        doc = DocumentResponse(id="1", filename="test.pdf", created_at="2024-01-01")
        assert doc.content_type is None
        assert doc.file_size is None
        assert doc.page_count is None


class TestAnalysisResponse:
    def test_serializes_to_camel_case(self):
        resp = AnalysisResponse(
            id="1",
            document_id="d1",
            status="COMPLETED",
            content_markdown="# Hello",
            pages_json="[]",
            created_at="2024-01-01",
        )
        data = resp.model_dump(by_alias=True)
        assert "documentId" in data
        assert "contentMarkdown" in data
        assert "pagesJson" in data
        assert "errorMessage" in data
        assert "startedAt" in data
        assert "completedAt" in data

    def test_populate_by_name(self):
        """Can create using snake_case field names."""
        resp = AnalysisResponse(
            id="1",
            document_id="d1",
            status="PENDING",
            created_at="2024-01-01",
        )
        assert resp.document_id == "d1"


class TestPipelineOptionsRequest:
    def test_defaults(self):
        opts = PipelineOptionsRequest()
        assert opts.do_ocr is True
        assert opts.do_table_structure is True
        assert opts.table_mode == "accurate"
        assert opts.do_code_enrichment is False
        assert opts.do_formula_enrichment is False
        assert opts.do_picture_classification is False
        assert opts.do_picture_description is False
        assert opts.generate_picture_images is False
        assert opts.generate_page_images is False
        assert opts.images_scale == 1.0

    def test_custom_values(self):
        opts = PipelineOptionsRequest(
            do_ocr=False,
            table_mode="fast",
            do_code_enrichment=True,
            images_scale=2.0,
        )
        assert opts.do_ocr is False
        assert opts.table_mode == "fast"
        assert opts.do_code_enrichment is True
        assert opts.images_scale == 2.0

    def test_model_dump(self):
        opts = PipelineOptionsRequest(do_ocr=False)
        data = opts.model_dump()
        assert data["do_ocr"] is False
        assert data["do_table_structure"] is True  # default preserved

    def test_invalid_table_mode_rejected(self):
        with pytest.raises(ValueError, match='table_mode must be "accurate" or "fast"'):
            PipelineOptionsRequest(table_mode="invalid")

    def test_negative_images_scale_rejected(self):
        with pytest.raises(ValueError, match="images_scale must be between"):
            PipelineOptionsRequest(images_scale=-1.0)

    def test_zero_images_scale_rejected(self):
        with pytest.raises(ValueError, match="images_scale must be between"):
            PipelineOptionsRequest(images_scale=0)

    def test_excessive_images_scale_rejected(self):
        with pytest.raises(ValueError, match="images_scale must be between"):
            PipelineOptionsRequest(images_scale=11.0)

    def test_boundary_images_scale_accepted(self):
        opts = PipelineOptionsRequest(images_scale=0.1)
        assert opts.images_scale == 0.1
        opts2 = PipelineOptionsRequest(images_scale=10.0)
        assert opts2.images_scale == 10.0


class TestCreateAnalysisRequest:
    def test_parses_document_id(self):
        req = CreateAnalysisRequest(documentId="doc-42")
        assert req.documentId == "doc-42"
        assert req.pipelineOptions is None

    def test_parses_with_pipeline_options(self):
        req = CreateAnalysisRequest(
            documentId="doc-42",
            pipelineOptions=PipelineOptionsRequest(do_ocr=False, table_mode="fast"),
        )
        assert req.documentId == "doc-42"
        assert req.pipelineOptions.do_ocr is False
        assert req.pipelineOptions.table_mode == "fast"
