"""Tests for lapis_spec.emitter."""

from lapis_spec.emitter import emit
from lapis_spec.models.lapis import (
    LapisDocument,
    LapisEnumType,
    LapisError,
    LapisField,
    LapisFlow,
    LapisFlowCondition,
    LapisLimits,
    LapisMeta,
    LapisObjectType,
    LapisOperation,
    LapisPlan,
    LapisQuota,
    LapisRateLimit,
    LapisWebhook,
)


class TestEmitMeta:
    def test_basic_meta(self):
        doc = LapisDocument(
            meta=LapisMeta(
                api="Test API",
                base="https://api.test.com",
                version="1.0.0",
                desc="A test API",
                auth="bearer header:Authorization",
            )
        )
        text = emit(doc)
        assert "[meta]" in text
        assert "api: Test API" in text
        assert "base: https://api.test.com" in text
        assert "version: 1.0.0" in text
        assert "desc: A test API" in text
        assert "auth: bearer header:Authorization" in text

    def test_minimal_meta(self):
        doc = LapisDocument(
            meta=LapisMeta(api="Min", base="https://api.min.com")
        )
        text = emit(doc)
        assert "api: Min" in text
        assert "version:" not in text
        assert "desc:" not in text
        assert "auth:" not in text


class TestEmitTypes:
    def test_enum_type(self):
        doc = LapisDocument(
            meta=LapisMeta(api="T", base="https://t.com"),
            types=[LapisEnumType(name="Status", values=["active", "inactive"])],
        )
        text = emit(doc)
        assert "[types]" in text
        assert "Status: active | inactive" in text

    def test_object_type(self):
        doc = LapisDocument(
            meta=LapisMeta(api="T", base="https://t.com"),
            types=[
                LapisObjectType(
                    name="Address",
                    fields=[
                        LapisField(name="street", type_expr="str"),
                        LapisField(name="city", type_expr="str"),
                        LapisField(
                            name="country",
                            type_expr="str",
                            optional=True,
                            default='"US"',
                        ),
                    ],
                )
            ],
        )
        text = emit(doc)
        assert "Address:" in text
        assert "  street: str" in text
        assert "  city: str" in text
        assert '  country?: str = "US"' in text

    def test_since_and_deprecated(self):
        doc = LapisDocument(
            meta=LapisMeta(api="T", base="https://t.com"),
            types=[
                LapisObjectType(
                    name="Item",
                    fields=[
                        LapisField(name="id", type_expr="str"),
                        LapisField(
                            name="meta",
                            type_expr="{str:any}",
                            optional=True,
                            since="2.1",
                        ),
                        LapisField(
                            name="old",
                            type_expr="str",
                            optional=True,
                            deprecated=True,
                            deprecated_note="Use new_field",
                        ),
                    ],
                )
            ],
        )
        text = emit(doc)
        assert "@since:2.1" in text
        assert '@deprecated "Use new_field"' in text


class TestEmitOps:
    def test_basic_operation(self):
        doc = LapisDocument(
            meta=LapisMeta(api="T", base="https://t.com"),
            ops=[
                LapisOperation(
                    name="get_item",
                    method="GET",
                    path="/items/{item_id}",
                    description="Get an item.",
                    output_ref="Item",
                )
            ],
        )
        text = emit(doc)
        assert "[ops]" in text
        assert "get_item GET /items/{item_id}" in text
        assert "  Get an item." in text
        assert "  < Item" in text

    def test_operation_with_inputs(self):
        doc = LapisDocument(
            meta=LapisMeta(api="T", base="https://t.com"),
            ops=[
                LapisOperation(
                    name="create_item",
                    method="POST",
                    path="/items",
                    inputs=[
                        LapisField(name="name", type_expr="str"),
                        LapisField(name="desc", type_expr="str", optional=True),
                    ],
                )
            ],
        )
        text = emit(doc)
        assert "  > name: str" in text
        assert "  > desc?: str" in text

    def test_modifiers(self):
        doc = LapisDocument(
            meta=LapisMeta(api="T", base="https://t.com"),
            ops=[
                LapisOperation(
                    name="list_items",
                    method="GET",
                    path="/items",
                    modifiers=["+paginated", "+stream"],
                )
            ],
        )
        text = emit(doc)
        assert "list_items GET /items +paginated +stream" in text

    def test_since(self):
        doc = LapisDocument(
            meta=LapisMeta(api="T", base="https://t.com"),
            ops=[
                LapisOperation(
                    name="new_op",
                    method="POST",
                    path="/new",
                    since="2.1",
                )
            ],
        )
        text = emit(doc)
        assert "@since:2.1" in text

    def test_output_fields(self):
        doc = LapisDocument(
            meta=LapisMeta(api="T", base="https://t.com"),
            ops=[
                LapisOperation(
                    name="send",
                    method="POST",
                    path="/send",
                    outputs=[
                        LapisField(name="sent_at", type_expr="datetime"),
                        LapisField(name="status", type_expr="str"),
                    ],
                )
            ],
        )
        text = emit(doc)
        assert "  < sent_at: datetime" in text
        assert "  < status: str" in text

    def test_input_with_location(self):
        doc = LapisDocument(
            meta=LapisMeta(api="T", base="https://t.com"),
            ops=[
                LapisOperation(
                    name="op",
                    method="POST",
                    path="/op",
                    inputs=[
                        LapisField(
                            name="token", type_expr="str", location="header"
                        ),
                    ],
                )
            ],
        )
        text = emit(doc)
        assert "  > token: str @header" in text


class TestEmitWebhooks:
    def test_webhook(self):
        doc = LapisDocument(
            meta=LapisMeta(api="T", base="https://t.com"),
            webhooks=[
                LapisWebhook(
                    name="order_paid",
                    method="POST",
                    path="/webhooks/order-paid",
                    description="Order paid.",
                    triggers=["When order status is paid"],
                    outputs=[
                        LapisField(
                            name="event_id",
                            type_expr="str",
                            header_name="X-Event-ID",
                        ),
                        LapisField(name="order_id", type_expr="str"),
                    ],
                )
            ],
        )
        text = emit(doc)
        assert "[webhooks]" in text
        assert "order_paid -> POST /webhooks/order-paid" in text
        assert "  Order paid." in text
        assert "  ! When order status is paid" in text
        assert "  < event_id: str @header:X-Event-ID" in text
        assert "  < order_id: str" in text


class TestEmitErrors:
    def test_errors(self):
        doc = LapisDocument(
            meta=LapisMeta(api="T", base="https://t.com"),
            errors=[
                LapisError(code=400, name="validation_error", description="Invalid input."),
                LapisError(
                    code=409,
                    name="conflict",
                    description="Already exists.",
                    ops=["create_item"],
                    fields=[LapisField(name="existing_id", type_expr="str")],
                ),
            ],
            error_base_type="ApiError",
        )
        text = emit(doc)
        assert "[errors]" in text
        assert "# Base structure for all errors: ApiError" in text
        assert "400 validation_error" in text
        assert "  Invalid input." in text
        assert "409 conflict @ops:create_item" in text
        assert "  ~ existing_id: str" in text


class TestEmitLimits:
    def test_limits_with_plans(self):
        doc = LapisDocument(
            meta=LapisMeta(api="T", base="https://t.com"),
            limits=LapisLimits(
                headers="X-RateLimit-Limit, X-RateLimit-Remaining",
                on_exceed="429 retry_after",
                max_body="5MB",
                plans=[
                    LapisPlan(
                        name="free",
                        rates=[LapisRateLimit(count=60, window="m", scope="@key")],
                        quotas=[
                            LapisQuota(
                                count=1000,
                                period="mo",
                                scope="@key",
                                description="monthly",
                            )
                        ],
                        max_batch=10,
                    ),
                ],
            ),
        )
        text = emit(doc)
        assert "[limits]" in text
        assert "headers: X-RateLimit-Limit, X-RateLimit-Remaining" in text
        assert "on_exceed: 429 retry_after" in text
        assert "max_body: 5MB" in text
        assert "plan: free" in text
        assert "  rate: 60/m @key" in text
        assert '  quota: 1000/mo @key "monthly"' in text
        assert "  max_batch: 10" in text

    def test_limits_without_plans(self):
        doc = LapisDocument(
            meta=LapisMeta(api="T", base="https://t.com"),
            limits=LapisLimits(
                rates=[LapisRateLimit(count=100, window="h")],
            ),
        )
        text = emit(doc)
        assert "rate: 100/h" in text

    def test_unlimited_quota(self):
        doc = LapisDocument(
            meta=LapisMeta(api="T", base="https://t.com"),
            limits=LapisLimits(
                quotas=[LapisQuota(unlimited=True)],
            ),
        )
        text = emit(doc)
        assert "quota: unlimited" in text


class TestEmitFlows:
    def test_flow(self):
        doc = LapisDocument(
            meta=LapisMeta(api="T", base="https://t.com"),
            flows=[
                LapisFlow(
                    name="onboard",
                    description="Customer onboarding",
                    chain="create_customer -> create_invoice -> send_invoice",
                    conditions=[
                        LapisFlowCondition(op="send_invoice", description="invoice is valid"),
                    ],
                    narrative="Full onboarding flow.",
                )
            ],
        )
        text = emit(doc)
        assert "[flows]" in text
        assert 'onboard "Customer onboarding"' in text
        assert "  create_customer -> create_invoice -> send_invoice" in text
        assert "  ? send_invoice: invoice is valid" in text
        assert "  Full onboarding flow." in text


class TestEmitFullDocument:
    def test_version_header(self):
        doc = LapisDocument(meta=LapisMeta(api="T", base="https://t.com"))
        text = emit(doc)
        assert text.startswith("# LAPIS v0.1.0")

    def test_ends_with_newline(self):
        doc = LapisDocument(meta=LapisMeta(api="T", base="https://t.com"))
        text = emit(doc)
        assert text.endswith("\n")

    def test_empty_optional_sections_omitted(self):
        doc = LapisDocument(meta=LapisMeta(api="T", base="https://t.com"))
        text = emit(doc)
        assert "[types]" not in text
        assert "[ops]" not in text
        assert "[webhooks]" not in text
        assert "[errors]" not in text
        assert "[limits]" not in text
        assert "[flows]" not in text
