"""
Ping management command.
Prints '0;OK' if all ok.
Prints '2;{error description}' in case of error.
"""
from django.conf import settings
from django.core.management.base import BaseCommand
from django_replicated.dbchecker import db_is_alive
from requests import get


PING_URL = 'http://127.0.0.1/ping'


class PingException(Exception):
    pass


class WrongStatus(PingException):
    """
    wrong http status code in response
    """

    def __str__(self):
        return f'Status code != 200, it\'s {self.args[0]}!'


def resolve_aliases(aliases):
    """
    Превратить алиасы в имена хостов.

    @type aliases: list
    @rtype: list
    """
    return [settings.DATABASES[alias]['HOST'] for alias in aliases]


class DatabaseError(PingException):

    """
    Некоторые из инстансов базы недоступны.
    """
    aliases = []

    def __init__(self, aliases, *args, **kwargs):
        self.aliases = aliases
        super(DatabaseError, self).__init__(*args, **kwargs)

    def __str__(self):
        return f'Hosts \"{",".join(resolve_aliases(self.aliases))}\" are not available.'


class CriticalDatabaseError(DatabaseError):

    def __str__(self):
        return f'No database instances available: \"{",".join(resolve_aliases(self.aliases))}\"!'


def validate(response):
    """
    Validate ping handle response:
        - http status must be 200

    @type response: requests.Response
    @param response: ping handle http response
    @raise WrongStatus
    """
    if response.status_code != 200:
        raise WrongStatus(response.status_code)


def check_databases(aliases):
    """
    Бросить исключение, если некоторые из инстансов базы недоступны.

    @type aliases: list
    @param aliases: список имен алиасов из settings.DATABASES
    @rtype: None
    """
    unavailable_aliases = []

    for alias in aliases:
        if not db_is_alive(alias, number_of_tries=3):
            unavailable_aliases.append(alias)

    if unavailable_aliases:
        if len(aliases) == len(unavailable_aliases):
            # все инстансы базы недоступны
            raise CriticalDatabaseError(unavailable_aliases)
        raise DatabaseError(unavailable_aliases)


def format_error(message):
    return f'2;{message}'


class Command(BaseCommand):
    help = 'Ping service and return status in monrun-compliant format'
    requires_model_validation = False

    def handle(self, *args, **options):
        try:
            headers = {'Host': settings.PYPI_HOST}
            response = get(PING_URL, headers=headers, timeout=25)
            check_databases(settings.DATABASES.keys())
            # validate должно быть всегда последней проверкой.
            validate(response)
        except DatabaseError as exc:
            print(format_error(f'Database error: "{exc}"'))

        except PingException as exc:
            print(format_error(repr(exc)))

        except Exception as exc:
            print(format_error(f'Unhandled exception: "{repr(exc)}"'))

        else:
            print('0;OK')
