from typing import Any, cast

from fastapi import FastAPI, UploadFile
from fastapi._compat import (
    Undefined,
    is_uploadfile_sequence_annotation,
)
from fastapi._compat.shared import is_bytes_sequence_annotation
from fastapi.testclient import TestClient
from pydantic import BaseModel, ConfigDict
from pydantic.fields import FieldInfo


def test_model_field_default_required():
    from fastapi._compat import v2

    # For coverage
    field_info = FieldInfo(annotation=str)
    field = v2.ModelField(name="foo", field_info=field_info)
    assert field.default is Undefined


def test_complex():
    app = FastAPI()

    @app.post("/")
    def foo(foo: str | list[int]):
        return foo

    client = TestClient(app)

    response = client.post("/", json="bar")
    assert response.status_code == 200, response.text
    assert response.json() == "bar"

    response2 = client.post("/", json=[1, 2])
    assert response2.status_code == 200, response2.text
    assert response2.json() == [1, 2]


def test_propagates_pydantic2_model_config():
    app = FastAPI()

    class Missing:
        def __bool__(self):
            return False

    class EmbeddedModel(BaseModel):
        model_config = ConfigDict(arbitrary_types_allowed=True)
        value: str | Missing = Missing()

    class Model(BaseModel):
        model_config = ConfigDict(
            arbitrary_types_allowed=True,
        )
        value: str | Missing = Missing()
        embedded_model: EmbeddedModel = EmbeddedModel()

    @app.post("/")
    def foo(req: Model) -> dict[str, str | None]:
        value = req.value
        if isinstance(value, Missing):
            value = None
        embedded_value = req.embedded_model.value
        if isinstance(embedded_value, Missing):
            embedded_value = None
        return {
            "value": value,
            "embedded_value": embedded_value,
        }

    client = TestClient(app)

    response = client.post("/", json={})
    assert response.status_code == 200, response.text
    assert response.json() == {
        "value": None,
        "embedded_value": None,
    }

    response2 = client.post(
        "/", json={"value": "foo", "embedded_model": {"value": "bar"}}
    )
    assert response2.status_code == 200, response2.text
    assert response2.json() == {
        "value": "foo",
        "embedded_value": "bar",
    }


def test_is_bytes_sequence_annotation_union():
    # For coverage
    # TODO: in theory this would allow declaring types that could be lists of bytes
    # to be read from files and other types, but I'm not even sure it's a good idea
    # to support it as a first class "feature"
    assert is_bytes_sequence_annotation(list[str] | list[bytes])


def test_is_uploadfile_sequence_annotation():
    # For coverage
    # TODO: in theory this would allow declaring types that could be lists of UploadFile
    # and other types, but I'm not even sure it's a good idea to support it as a first
    # class "feature"
    assert is_uploadfile_sequence_annotation(list[str] | list[UploadFile])


def test_serialize_sequence_value_with_optional_list():
    """Test that serialize_sequence_value handles optional lists correctly."""
    from fastapi._compat import v2

    field_info = FieldInfo(annotation=cast(Any, list[str] | None))
    field = v2.ModelField(name="items", field_info=field_info)
    result = v2.serialize_sequence_value(field=field, value=["a", "b", "c"])
    assert result == ["a", "b", "c"]
    assert isinstance(result, list)


def test_serialize_sequence_value_with_optional_list_pipe_union():
    """Test that serialize_sequence_value handles optional lists correctly (with new syntax)."""
    from fastapi._compat import v2

    field_info = FieldInfo(annotation=cast(Any, list[str] | None))
    field = v2.ModelField(name="items", field_info=field_info)
    result = v2.serialize_sequence_value(field=field, value=["a", "b", "c"])
    assert result == ["a", "b", "c"]
    assert isinstance(result, list)


def test_serialize_sequence_value_with_none_first_in_union():
    """Test that serialize_sequence_value handles Union[None, List[...]] correctly."""
    from typing import Union

    from fastapi._compat import v2

    # Use Union[None, list[str]] to ensure None comes first in the union args
    field_info = FieldInfo(annotation=cast(Any, Union[None, list[str]]))  # noqa: UP007
    field = v2.ModelField(name="items", field_info=field_info)
    result = v2.serialize_sequence_value(field=field, value=["x", "y"])
    assert result == ["x", "y"]
    assert isinstance(result, list)
