import uuid
from typing import TYPE_CHECKING, no_type_check

import sqlalchemy as sa
from sqlalchemy.dialects.postgresql.base import UUID
from sqlalchemy.sql.sqltypes import CHAR
from sqlalchemy.sql.type_api import TypeDecorator

# Use the following as the value of server_default
# for primary keys of type GUID
GUID_DEFAULT_POSTGRESQL = sa.DefaultClause(sa.text("gen_random_uuid()"))
GUID_DEFAULT_SQLITE = uuid.uuid4

TypeDecorator = TypeDecorator[uuid.UUID] if TYPE_CHECKING else TypeDecorator  # type: ignore


class GUID(TypeDecorator):  # type: ignore
    """
    Platform-independent GUID type.

    Uses PostgreSQL's UUID type, otherwise uses CHAR(32), storing as stringified hex values.

    Taken from SQLAlchemy docs: https://docs.sqlalchemy.org/en/13/core/custom_types.html#backend-agnostic-guid-type

    from authx_core import GUID

    class User(Base):
        __tablename__ = "user"
        id = sa.Column(GUID, primary_key=True)
        name = sa.Column(sa.String, nullable=False)
    """

    impl = CHAR

    @no_type_check
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    @no_type_check
    def load_dialect_impl(self, dialect):
        if dialect.name == "postgresql":  # pragma: no cover
            return dialect.type_descriptor(UUID())
        else:
            return dialect.type_descriptor(CHAR(32))

    @no_type_check
    def process_bind_param(self, value, dialect):
        if value is None:
            return value
        elif dialect.name == "postgresql":  # pragma: no cover
            return str(value)
        else:
            return (
                "%.32x" % value.int
                if isinstance(value, uuid.UUID)
                else "%.32x" % uuid.UUID(value).int
            )

    @no_type_check
    def process_result_value(self, value, dialect):
        if value is not None and not isinstance(value, uuid.UUID):
            value = uuid.UUID(value)
        return value
