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

from typing import List
from django import db
from django.core.checks import CheckMessage, Error, register, Warning
from allianceauth.utils.cache import get_redis_client
from django.utils import timezone
from packaging.version import InvalidVersion, Version as Pep440Version
from celery import current_app
from django.conf import settings
from sqlite3.dbapi2 import sqlite_version_info

"""
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:
        redis_version = Pep440Version(get_redis_client().info()["redis_version"])
    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_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
