"""
Django system checks for Alliance Auth
"""

from sqlite3.dbapi2 import sqlite_version_info
from typing import List

from celery import current_app
from packaging.version import InvalidVersion, Version as Pep440Version

from django import db
from django.conf import settings
from django.core.checks import CheckMessage, Error, Warning, register
from django.utils import timezone

from allianceauth.utils.cache import get_redis_client

"""
A = System Packages
B = Configuration
"""


@register()
def django_settings(app_configs, **kwargs) -> List[CheckMessage]:
    """
    Check that Django settings are correctly configured

    :param app_configs:
    :type app_configs:
    :param kwargs:
    :type kwargs:
    :return:
    :rtype:
    """

    errors: List[CheckMessage] = []

    # Check for SITE_URL
    if hasattr(settings, "SITE_URL"):
        # Check if SITE_URL is empty
        if settings.SITE_URL == "":
            errors.append(
                Error(
                    msg="'SITE_URL' is empty.",
                    hint="Make sure to set 'SITE_URL' to the URL of your Auth instance. (Without trailing slash)",
                    id="allianceauth.checks.B011",
                )
            )
        # Check if SITE_URL has a trailing slash
        elif settings.SITE_URL[-1] == "/":
            errors.append(
                Warning(
                    msg="'SITE_URL' has a trailing slash. This may lead to incorrect links being generated by Auth.",
                    hint="",
                    id="allianceauth.checks.B005",
                )
            )
    # SITE_URL not found
    else:
        errors.append(
            Error(
                msg="No 'SITE_URL' found is settings. This may lead to incorrect links being generated by Auth or Errors in 3rd party modules.",
                hint="",
                id="allianceauth.checks.B006",
            )
        )

    # Check for CSRF_TRUSTED_ORIGINS
    if hasattr(settings, "CSRF_TRUSTED_ORIGINS") and hasattr(settings, "SITE_URL"):
        # Check if SITE_URL is not in CSRF_TRUSTED_ORIGINS
        if settings.SITE_URL not in settings.CSRF_TRUSTED_ORIGINS:
            errors.append(
                Warning(
                    msg="'SITE_URL' not found in 'CSRF_TRUSTED_ORIGINS'. Auth may not load pages correctly until this is rectified.",
                    hint="",
                    id="allianceauth.checks.B007",
                )
            )
    # CSRF_TRUSTED_ORIGINS not found
    else:
        errors.append(
            Error(
                msg="No 'CSRF_TRUSTED_ORIGINS' found is settings, Auth may not load pages correctly until this is rectified",
                hint="",
                id="allianceauth.checks.B008",
            )
        )

    # Check for ESI_USER_CONTACT_EMAIL
    if hasattr(settings, "ESI_USER_CONTACT_EMAIL"):
        # Check if ESI_USER_CONTACT_EMAIL is empty
        if settings.ESI_USER_CONTACT_EMAIL == "":
            errors.append(
                Error(
                    msg="'ESI_USER_CONTACT_EMAIL' is empty. A valid email is required as maintainer contact for CCP.",
                    hint="",
                    id="allianceauth.checks.B009",
                )
            )
    # ESI_USER_CONTACT_EMAIL not found
    else:
        errors.append(
            Error(
                msg="No 'ESI_USER_CONTACT_EMAIL' found is settings. A valid email is required as maintainer contact for CCP.",
                hint="",
                id="allianceauth.checks.B010",
            )
        )

    return errors


@register()
def system_package_redis(app_configs, **kwargs) -> List[CheckMessage]:
    """
    Check that Redis is a supported version

    :param app_configs:
    :type app_configs:
    :param kwargs:
    :type kwargs:
    :return:
    :rtype:
    """

    allianceauth_redis_install_link = "https://allianceauth.readthedocs.io/en/latest/installation/allianceauth.html#redis-and-other-tools"

    errors: List[CheckMessage] = []

    try:
        # Traditional Redis Version
        redis_version = Pep440Version(get_redis_client().info()["redis_version"])
        # Exit early if Valkey, this will be handled in Valkey checks
        if "valkey_version" in get_redis_client().info():
            return errors
    except InvalidVersion:
        errors.append(Warning("Unable to confirm Redis Version"))

        return errors

    if (
        redis_version.major == 7 and redis_version.minor == 2 and timezone.now() > timezone.datetime(year=2025, month=8, day=31, tzinfo=timezone.utc)
    ):
        errors.append(
            Error(
                msg=f"Redis {redis_version.public} in Security Support only, Updating Suggested",
                hint=allianceauth_redis_install_link,
                id="allianceauth.checks.A001",
            )
        )
    elif redis_version.major == 7 and redis_version.minor == 0:
        errors.append(
            Warning(
                msg=f"Redis {redis_version.public} in Security Support only, Updating Suggested",
                hint=allianceauth_redis_install_link,
                id="allianceauth.checks.A002",
            )
        )
    elif redis_version.major == 6 and redis_version.minor == 2:
        errors.append(
            Warning(
                msg=f"Redis {redis_version.public} in Security Support only, Updating Suggested",
                hint=allianceauth_redis_install_link,
                id="allianceauth.checks.A018",
            )
        )
    elif redis_version.major in [6, 5]:
        errors.append(
            Error(
                msg=f"Redis {redis_version.public} EOL",
                hint=allianceauth_redis_install_link,
                id="allianceauth.checks.A003",
            )
        )

    return errors


@register()
def system_package_valkey(app_configs, **kwargs) -> List[CheckMessage]:
    """
    Check that Valkey is a supported version

    :param app_configs:
    :type app_configs:
    :param kwargs:
    :type kwargs:
    :return:
    :rtype:
    """

    allianceauth_redis_install_link = "https://allianceauth.readthedocs.io/en/latest/installation/allianceauth.html#redis-and-other-tools"

    errors: List[CheckMessage] = []

    try:
        # Valkey has both redis_version and valkey_version in info
        if "valkey_version" in get_redis_client().info():
            valkey_version: Pep440Version = Pep440Version(get_redis_client().info()["valkey_version"])
        else:
            # Just here to typehint the following code, this will return out of the check
            valkey_version = Pep440Version("0.0.0")
            return errors
    except InvalidVersion:
        errors.append(Warning("Unable to confirm Redis Version"))

        return errors

    if valkey_version.major == 7 and valkey_version.minor == 2 and timezone.now() > timezone.datetime(year=2027, month=4, day=16, tzinfo=timezone.utc):
        errors.append(
            Error(
                msg=f"Valkey {valkey_version.public} in Security Support only, Updating Suggested",
                hint=allianceauth_redis_install_link,
                id="allianceauth.checks.A021",
            )
        )
    elif valkey_version.major == 8 and valkey_version.minor == 0 and timezone.now() > timezone.datetime(year=2027, month=9, day=15, tzinfo=timezone.utc):
        errors.append(
            Warning(
                msg=f"Valkey {valkey_version.public} in Security Support only, Updating Suggested",
                hint=allianceauth_redis_install_link,
                id="allianceauth.checks.A022",
            )
        )
    elif valkey_version.major == 8 and valkey_version.minor == 1 and timezone.now() > timezone.datetime(year=2028, month=5, day=31, tzinfo=timezone.utc):
        errors.append(
            Warning(
                msg=f"Valkey {valkey_version.public} in Security Support only, Updating Suggested",
                hint=allianceauth_redis_install_link,
                id="allianceauth.checks.A023",
            )
        )
    elif valkey_version.major == 9 and valkey_version.minor == 0 and timezone.now() > timezone.datetime(year=2028, month=10, day=21, tzinfo=timezone.utc):
        errors.append(
            Warning(
                msg=f"Valkey {valkey_version.public} in Security Support only, Updating Suggested",
                hint=allianceauth_redis_install_link,
                id="allianceauth.checks.A024",
            )
        )
    return errors


@register()
def system_package_mysql(app_configs, **kwargs) -> List[CheckMessage]:
    """
    Check that MySQL is a supported version

    :param app_configs:
    :type app_configs:
    :param kwargs:
    :type kwargs:
    :return:
    :rtype:
    """

    mysql_quick_guide_link = "https://dev.mysql.com/doc/mysql-apt-repo-quick-guide/en/"

    errors: List[CheckMessage] = []

    for connection in db.connections.all():
        if connection.vendor == "mysql":
            try:
                mysql_version = Pep440Version(
                    ".".join(str(i) for i in connection.mysql_version)
                )
            except InvalidVersion:
                errors.append(Warning("Unable to confirm MySQL Version"))

                return errors

            #  MySQL 8
            if mysql_version.major == 8:
                if mysql_version.minor == 4 and timezone.now() > timezone.datetime(
                    year=2032, month=4, day=30, tzinfo=timezone.utc
                ):
                    errors.append(
                        Error(
                            msg=f"MySQL {mysql_version.public} EOL",
                            hint=mysql_quick_guide_link,
                            id="allianceauth.checks.A004",
                        )
                    )
                elif mysql_version.minor == 3:
                    errors.append(
                        Warning(
                            msg=f"MySQL {mysql_version.public} Non LTS",
                            hint=mysql_quick_guide_link,
                            id="allianceauth.checks.A005",
                        )
                    )
                elif mysql_version.minor == 2:
                    errors.append(
                        Warning(
                            msg=f"MySQL {mysql_version.public} Non LTS",
                            hint=mysql_quick_guide_link,
                            id="allianceauth.checks.A006",
                        )
                    )
                elif mysql_version.minor == 1:
                    errors.append(
                        Error(
                            msg=f"MySQL {mysql_version.public} EOL",
                            hint=mysql_quick_guide_link,
                            id="allianceauth.checks.A007",
                        )
                    )
                elif mysql_version.minor == 0 and timezone.now() > timezone.datetime(
                    year=2026, month=4, day=30, tzinfo=timezone.utc
                ):
                    errors.append(
                        Error(
                            msg=f"MySQL {mysql_version.public} EOL",
                            hint=mysql_quick_guide_link,
                            id="allianceauth.checks.A008",
                        )
                    )

            # MySQL below 8
            # This will also catch Mariadb 5.x
            elif mysql_version.major < 8:
                errors.append(
                    Error(
                        msg=f"MySQL or MariaDB {mysql_version.public} EOL",
                        hint=mysql_quick_guide_link,
                        id="allianceauth.checks.A009",
                    )
                )

    return errors


@register()
def system_package_mariadb(app_configs, **kwargs) -> List[CheckMessage]:
    """
    Check that MariaDB is a supported version

    :param app_configs:
    :type app_configs:
    :param kwargs:
    :type kwargs:
    :return:
    :rtype:
    """

    mariadb_download_link = "https://mariadb.org/download/?t=repo-config"

    errors: List[CheckMessage] = []

    for connection in db.connections.all():
        # TODO: Find a way to determine MySQL vs. MariaDB
        if connection.vendor == "mysql":
            try:
                mariadb_version = Pep440Version(
                    ".".join(str(i) for i in connection.mysql_version)
                )
            except InvalidVersion:
                errors.append(Warning("Unable to confirm MariaDB Version"))

                return errors

            # MariaDB 11
            if mariadb_version.major == 11:
                if mariadb_version.minor == 4 and timezone.now() > timezone.datetime(
                    year=2029, month=5, day=19, tzinfo=timezone.utc
                ):
                    errors.append(
                        Error(
                            msg=f"MariaDB {mariadb_version.public} EOL",
                            hint=mariadb_download_link,
                            id="allianceauth.checks.A010",
                        )
                    )
                elif mariadb_version.minor == 2:
                    errors.append(
                        Warning(
                            msg=f"MariaDB {mariadb_version.public} Non LTS",
                            hint=mariadb_download_link,
                            id="allianceauth.checks.A018",
                        )
                    )

                    if timezone.now() > timezone.datetime(
                        year=2024, month=11, day=21, tzinfo=timezone.utc
                    ):
                        errors.append(
                            Error(
                                msg=f"MariaDB {mariadb_version.public} EOL",
                                hint=mariadb_download_link,
                                id="allianceauth.checks.A011",
                            )
                        )
                elif mariadb_version.minor == 1:
                    errors.append(
                        Warning(
                            msg=f"MariaDB {mariadb_version.public} Non LTS",
                            hint=mariadb_download_link,
                            id="allianceauth.checks.A019",
                        )
                    )

                    if timezone.now() > timezone.datetime(
                        year=2024, month=8, day=21, tzinfo=timezone.utc
                    ):
                        errors.append(
                            Error(
                                msg=f"MariaDB {mariadb_version.public} EOL",
                                hint=mariadb_download_link,
                                id="allianceauth.checks.A012",
                            )
                        )
                # Demote versions down here once EOL
                elif mariadb_version.minor in [0, 3]:
                    errors.append(
                        Error(
                            msg=f"MariaDB {mariadb_version.public} EOL",
                            hint=mariadb_download_link,
                            id="allianceauth.checks.A013",
                        )
                    )

            # MariaDB 10
            elif mariadb_version.major == 10:
                if mariadb_version.minor == 11 and timezone.now() > timezone.datetime(
                    year=2028, month=2, day=10, tzinfo=timezone.utc
                ):
                    errors.append(
                        Error(
                            msg=f"MariaDB {mariadb_version.public} EOL",
                            hint=mariadb_download_link,
                            id="allianceauth.checks.A014",
                        )
                    )
                elif mariadb_version.minor == 6 and timezone.now() > timezone.datetime(
                    year=2026, month=7, day=6, tzinfo=timezone.utc
                ):
                    errors.append(
                        Error(
                            msg=f"MariaDB {mariadb_version.public} EOL",
                            hint=mariadb_download_link,
                            id="allianceauth.checks.A0015",
                        )
                    )
                elif mariadb_version.minor == 5 and timezone.now() > timezone.datetime(
                    year=2025, month=6, day=24, tzinfo=timezone.utc
                ):
                    errors.append(
                        Error(
                            msg=f"MariaDB {mariadb_version.public} EOL",
                            hint=mariadb_download_link,
                            id="allianceauth.checks.A016",
                        )
                    )
                # Demote versions down here once EOL
                elif mariadb_version.minor in [0, 1, 2, 3, 4, 7, 9, 10]:
                    errors.append(
                        Error(
                            msg=f"MariaDB {mariadb_version.public} EOL",
                            hint=mariadb_download_link,
                            id="allianceauth.checks.A017",
                        )
                    )

    return errors


@register()
def system_package_sqlite(app_configs, **kwargs) -> List[CheckMessage]:
    """
    Check that SQLite is a supported version

    :param app_configs:
    :type app_configs:
    :param kwargs:
    :type kwargs:
    :return:
    :rtype:
    """

    errors: List[CheckMessage] = []

    for connection in db.connections.all():
        if connection.vendor == "sqlite":
            try:
                sqlite_version = Pep440Version(
                    ".".join(str(i) for i in sqlite_version_info)
                )
            except InvalidVersion:
                errors.append(Warning("Unable to confirm SQLite Version"))

                return errors
            if sqlite_version.major == 3 and sqlite_version.minor < 27:
                errors.append(
                    Error(
                        msg=f"SQLite {sqlite_version.public} Unsupported by Django",
                        hint="https://pkgs.org/download/sqlite3",
                        id="allianceauth.checks.A020",
                    )
                )

    return errors


@register()
def sql_settings(app_configs, **kwargs) -> List[CheckMessage]:
    """
    Check that SQL settings are correctly configured

    :param app_configs:
    :type app_configs:
    :param kwargs:
    :type kwargs:
    :return:
    :rtype:
    """

    errors: List[CheckMessage] = []

    for connection in db.connections.all():
        if connection.vendor == "mysql":
            try:
                if connection.settings_dict["OPTIONS"]["charset"] != "utf8mb4":
                    errors.append(
                        Error(
                            msg=f"SQL Charset is not set to utf8mb4 DB: {connection.alias}",
                            hint="https://gitlab.com/allianceauth/allianceauth/-/commit/89be2456fb2d741b86417e889da9b6129525bec8",
                            id="allianceauth.checks.B001",
                        )
                    )
            except KeyError:
                errors.append(
                    Error(
                        msg=f"SQL Charset is not set to utf8mb4 DB: {connection.alias}",
                        hint="https://gitlab.com/allianceauth/allianceauth/-/commit/89be2456fb2d741b86417e889da9b6129525bec8",
                        id="allianceauth.checks.B001",
                    )
                )

            # This hasn't actually been set on AA yet
            # try:
            #     if (
            #         connection.settings_dict["OPTIONS"]["collation"]
            #         != "utf8mb4_unicode_ci"
            #     ):
            #         errors.append(
            #             Error(
            #                 msg=f"SQL Collation is not set to utf8mb4_unicode_ci DB:{connection.alias}",
            #                 hint="https://gitlab.com/allianceauth/allianceauth/-/commit/89be2456fb2d741b86417e889da9b6129525bec8",
            #                 id="allianceauth.checks.B001",
            #             )
            #         )
            # except KeyError:
            #     errors.append(
            #         Error(
            #             msg=f"SQL Collation is not set to utf8mb4_unicode_ci DB:{connection.alias}",
            #             hint="https://gitlab.com/allianceauth/allianceauth/-/commit/89be2456fb2d741b86417e889da9b6129525bec8",
            #             id="allianceauth.checks.B001",
            #         )
            #     )

        # if connection.vendor == "sqlite":

    return errors


@register()
def celery_settings(app_configs, **kwargs) -> List[CheckMessage]:
    """
    Check that Celery settings are correctly configured

    :param app_configs:
    :type app_configs:
    :param kwargs:
    :type kwargs:
    :return:
    :rtype:
    """

    errors: List[CheckMessage] = []

    try:
        if current_app.conf.broker_transport_options != {
            "priority_steps": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
            "queue_order_strategy": "priority",
        }:
            errors.append(
                Error(
                    msg="Celery Priorities are not set correctly",
                    hint="https://gitlab.com/allianceauth/allianceauth/-/commit/8861ec0a61790eca0261f1adc1cc04ca5f243cbc",
                    id="allianceauth.checks.B003",
                )
            )
    except KeyError:
        errors.append(
            Error(
                msg="Celery Priorities are not set",
                hint="https://gitlab.com/allianceauth/allianceauth/-/commit/8861ec0a61790eca0261f1adc1cc04ca5f243cbc",
                id="allianceauth.checks.B003",
            )
        )

    try:
        if not current_app.conf.broker_connection_retry_on_startup:
            errors.append(
                Error(
                    msg="Celery broker_connection_retry_on_startup not set correctly",
                    hint="https://gitlab.com/allianceauth/allianceauth/-/commit/380c41400b535447839e5552df2410af35a75280",
                    id="allianceauth.checks.B004",
                )
            )
    except KeyError:
        errors.append(
            Error(
                msg="Celery broker_connection_retry_on_startup not set",
                hint="https://gitlab.com/allianceauth/allianceauth/-/commit/380c41400b535447839e5552df2410af35a75280",
                id="allianceauth.checks.B004",
            )
        )

    return errors


# IDEAS

# Any other celery things weve manually changed over the years
# I'd be happy to add Community App checks, old versions the owners dont want to support etc.


# Check Default Collation on DB
# Check Charset Collation on all tables
