from typing import Any

from django.db import models
from django.db.backends.base.base import BaseDatabaseWrapper
from django.db.models.sql.compiler import SQLCompiler


class GenerateUuid4(models.Func):
    """
    Generate a new UUID (v4) value.

    Most databases support some form of UUID generation, but the syntax
    varies between them. This expression is only implemented for SQLite
    and PostgreSQL.

    The expression can be used to generate a UUID value for a field
    where the value needs to be generated by the database, rather than
    by the application.

    As an example, if you have a model with a UUID field and you want to
    roll all the ids, if you try to do this:

        >>> User.objects.all().update(uuid=uuid.uuid4())

    You will end up with every row in the database having the same UUID.
    This is because the uuid.uuid4() function is evaluated once and the
    same value is used for every row:

        UPDATE "user" SET "uuid" =
        "3742138f-1399-47b2-a721-1710abefded6"

    If you want to generate a new UUID for each row whilst updating with
    a single "UPDATE" SQL statement, you can use this expression:

        >>> User.objects.all().update(uuid=GenerateUuid4())

    Which will generate something like this:

        UPDATE "user" SET "uuid" = <function_to_generate_uuid>

    The specific function that is called is vendor specific.

    """

    output_field = models.UUIDField()

    def as_sql(
        self,
        compiler: SQLCompiler,
        connection: BaseDatabaseWrapper,
        **extra_context: Any,
    ) -> tuple[str, list]:
        """
        Generate the SQL fragment for the expression.

        Note that this is only called when `as_{{vendor}}` method does
        not exist for the expression.

        (From https://docs.djangoproject.com/en/4.2/ref/models/lookups/)

        "Returns a tuple (sql, params), where sql is the SQL string, and
        params is the list or tuple of query parameters. The compiler is
        an SQLCompiler object, which has a compile() method that can be
        used to compile other expressions. The connection is the
        connection used to execute the query.

        Calling expression.as_sql() is usually incorrect - instead
        compiler.compile(expression) should be used. The
        compiler.compile() method will take care of calling
        vendor-specific methods of the expression.

        Custom keyword arguments may be defined on this method if it's
        likely that as_vendorname() methods or subclasses will need to
        supply data to override the generation of the SQL string. See
        Func.as_sql() for example usage."

        """
        raise NotImplementedError(
            f"GenerateUuid4 is not implemented for {connection.vendor}"
        )

    def as_sqlite(
        self,
        compiler: SQLCompiler,
        connection: BaseDatabaseWrapper,
        **extra_context: Any,
    ) -> tuple[str, list]:
        """
        Generate the SQL fragment for the expression in SQLite format.

        (From https://docs.djangoproject.com/en/4.2/ref/models/lookups/)

        "Works like as_sql() method. When an expression is compiled by
        compiler.compile(), Django will first try to call
        as_vendorname(), where vendorname is the vendor name of the
        backend used for executing the query. The vendorname is one of
        postgresql, oracle, sqlite, or mysql for Django's built-in
        backends."

        """
        return super().as_sql(
            compiler,
            connection,
            function="HEX(RANDOMBLOB(16))",
            # override the default template as otherwise we end up
            # trying to append parentheses: HEX(RANDOMBLOB(16))()
            template="%(function)s",
            **extra_context,
        )

    def as_postgresql(
        self,
        compiler: SQLCompiler,
        connection: BaseDatabaseWrapper,
        **extra_context: Any,
    ) -> tuple[str, list]:
        return super().as_sql(
            compiler, connection, function="gen_random_uuid", **extra_context
        )
