import datetime
import hashlib
import logging
from abc import ABC, abstractmethod
from typing import Optional

import redis
from django.conf import settings
from django.contrib.auth.models import User
from django.utils import timezone

from console import conslog
from core.utils import dtnow
from users.models import UserInfo

_log = logging.getLogger()


def get_active_users_query():
    return User.objects.exclude(username__startswith='deleted_')


def create_user(*args, **kwargs):
    user_info_fields_names = (ui.name for ui in UserInfo._meta.get_fields())
    user_info_kwargs = {name: kwargs.pop(name) for name in user_info_fields_names if name in kwargs}
    if isinstance(user_info_kwargs.get('expire_date', None), datetime.date):
        kwargs['is_active'] = kwargs.get('is_active', True) and user_info_kwargs['expire_date'] >= dtnow().date()
    if 'user' not in user_info_kwargs:
        user_info_kwargs['user'] = User.objects.create_user(*args, **kwargs)
        user_info_kwargs['user'].save()
    UserInfo.objects.create(**user_info_kwargs)
    return user_info_kwargs['user']


def delete_user(user_deleting, log_deletion=True):
    """ Method for deleting user by changing his is_active status to False and renaming him """
    now = datetime.datetime.now()
    hash = hashlib.sha256()
    hash.update(str(now).encode('utf-8'))
    try:
        username_before = user_deleting.username
        user_deleting.is_active = False
        user_deleting.username = f'deleted_{user_deleting.username}_{hash.hexdigest()[:10]}'
        user_deleting.save()
        username_after = user_deleting.username
        if log_deletion:
            log_message = f"User [{user_deleting}] deleted [{username_before}]. " \
                          f"new username: [{username_after}]"
            _log.info(log_message)
    except Exception as e:
        log_message = f"Exception {e} appears during deleting user"
        conslog.add_error_log(log_message, _log)


class UserStatusStrategyABC(ABC):

    @abstractmethod
    def set_date(self, date):
        ...

    @abstractmethod
    def get_date(self):
        ...


class InUserInfoStrategy(UserStatusStrategyABC):
    def __init__(self, user):
        self.user = user

    def set_date(self, date) -> None:
        self.user.userinfo.last_seen = date
        self.user.userinfo.save()

    def get_date(self) -> str:
        return self.user.userinfo.last_seen


class InRedisStrategy(UserStatusStrategyABC):

    user_status_field = 'USER_STATUS_{}'

    def __init__(self, user):
        self.user = user
        self.redis_instance = redis.StrictRedis(
            host=getattr(settings, 'REDIS_HOST', 'redis'),
            port=getattr(settings, 'REDIS_PORT', 6379)
        )

    def set_date(self, date):
        field = self.user_status_field.format(self.user.pk)
        data = str(date.timestamp())
        self.redis_instance.set(field, data)

    def get_date(self):
        field = self.user_status_field.format(self.user.pk)
        timestamp = self.redis_instance.get(field)
        if not timestamp:
            return
        return datetime.datetime.fromtimestamp(float(timestamp.decode()))


class UserStatusService:
    """User status service."""
    timeout_minute = 1

    def __init__(self, user, *, strategy: Optional[UserStatusStrategyABC] = None) -> None:
        self._status = None
        if not strategy:
            strategy = InUserInfoStrategy
        self._strategy = strategy(user)
        self.user = user

    def get_status(self) -> str:
        """Return user status online or offline"""
        last_date = self._strategy.get_date()
        if not last_date:
            return 'offline'
        last_date = last_date.replace(tzinfo=timezone.utc)
        now = dtnow()
        delta = datetime.timedelta(minutes=self.timeout_minute)
        if now - last_date > delta:
            return 'offline'
        return 'online'

    def set_status(self) -> None:
        """Set last date seen in db."""
        now = dtnow()
        self._strategy.set_date(now)
