import inspect
import typing
from collections.abc import Sequence
from datetime import date, datetime, time, timedelta
from decimal import Decimal
from types import UnionType
from typing import Any, Generator, Literal, TypeVar

import pydantic
from pydantic_core import PydanticUndefined
from typing_extensions import Self

BASE_TYPES = {
    type(None),
    bool,
    int,
    float,
    Decimal,
    complex,
    str,
    bytes,
    # bytearray, # Unsupported by pydantic
    datetime,
    date,
    time,
    timedelta,
}

COMPOSITE_TYPES = {
    list,
    set,
    frozenset,
    tuple,
    dict,
}

UNION_TYPES = {
    typing.Union,
    UnionType,
}

COMPOSITE_TYPES_EXT = {
    *COMPOSITE_TYPES,
    *UNION_TYPES,
}


T = TypeVar("T")


class Unreachable(Exception):
    def __init__(self: Self) -> None:
        super().__init__(
            "Unreachable: a unreachable code branch has been reached. This is a bug."
        )


def timedelta_adapter(v: timedelta) -> str:
    return pydantic.TypeAdapter(timedelta).dump_json(v).decode().strip('"')


def flatten(iter: Sequence[Sequence[T]]) -> Generator[T, None, None]:
    for item in iter:
        for subitem in item:
            yield subitem


class PydanticDefault(pydantic.BaseModel):
    present: bool
    value: Any


def get_default(v: pydantic.fields.FieldInfo) -> PydanticDefault:
    # Does this hold?
    assert v.default_factory is not PydanticUndefined

    if v.default_factory is not None:
        assert v.default is PydanticUndefined

        # Can't run the default factory that takes the validated data.
        if len(inspect.signature(v.default_factory).parameters) != 0:
            raise ValueError(
                "Default factory using the validated data is not currently supported."
            )

        default = v.default_factory()  # type: ignore (can't tell we asserted no parameters)
        return PydanticDefault(present=True, value=default)
    elif v.default is not PydanticUndefined:
        assert v.default_factory is None
        return PydanticDefault(present=True, value=v.default)
    else:
        assert v.default is PydanticUndefined
        assert v.default_factory is None
        return PydanticDefault(present=False, value=PydanticUndefined)


def get_discriminator_str(
    a: pydantic.fields.FieldInfo,
) -> str | None:
    """Extract the discriminator string from a field."""
    if a.discriminator is None:
        return None
    if isinstance(a.discriminator, str):
        return a.discriminator
    if hasattr(a.discriminator, "discriminator"):
        inner = a.discriminator.discriminator
        if isinstance(inner, str):
            return inner
        raise NotImplementedError(
            f"Callable discriminators are not supported for compatibility checking: {a.discriminator}"
        )
    else:
        raise NotImplementedError(
            f"Unsupported discriminator type: {type(a.discriminator)}"
        )


def get_field_alias(
    field: pydantic.fields.FieldInfo, _config: pydantic.ConfigDict
) -> str | None:
    # TODO account for config-level aliases
    if field.serialization_alias is not None:
        if field.alias_priority != 2:
            raise ValueError("Config-level aliases are disallowed.")
        return field.serialization_alias
    elif field.alias is not None:
        if field.alias_priority != 2:
            raise ValueError("Config-level aliases are disallowed.")
        return field.alias
    else:
        return None


def get_field_serialization_name(
    name: str, field: pydantic.fields.FieldInfo, config: pydantic.ConfigDict
) -> str | None:
    if field.exclude:
        return None

    alias = get_field_alias(field, config)
    if alias is not None:
        return alias
    else:
        return name


def get_extra(field: pydantic.ConfigDict) -> Literal["allow", "ignore", "forbid"]:
    extra = field.get("extra")
    if extra is None:
        return "ignore"
    else:
        return extra
