import logging
from typing import Iterable, Optional

from requests import Session

from django.conf import settings
from django.core.exceptions import ValidationError
from django.utils.functional import SimpleLazyObject
from django.utils.translation import gettext_lazy as _

from lms.utils.requests import requests_retry_session

log = logging.getLogger(__name__)


class AchieveryClient:
    NO_LEVELS = -1
    INIT_LEVEL = 1

    def __init__(self, api_url, token, retries: int = 5):
        self.API_URL = api_url
        self.TOKEN = token
        self.HEADERS = {
            'Authorization': 'OAuth {token}'.format(token=self.TOKEN),
            'Content-Type': 'application/json',
        }
        self.retries = retries
        self.session = Session()

        self.session = requests_retry_session(
            retries=self.retries,
            session=self.session,
        )

    @staticmethod
    def get_params(fields):
        return {'_fields': ','.join(fields)} if fields else None

    def request(self, method, url, params=None, **kwargs):
        kwargs['headers'] = self.HEADERS
        if params:
            kwargs['params'] = params

        response = self.session.request(method, url, allow_redirects=False, **kwargs)
        response.raise_for_status()

        return response.json()

    def get_achievement(self, achievement_id, fields: Optional[Iterable[str]] = None):
        url = f'{self.API_URL}/achievements/{achievement_id}/'
        params = self.get_params(fields)

        return self.request(method='GET', url=url, params=params)

    def get_user_achievement(self, achievement_id, login):
        url = f'{self.API_URL}/given/'
        params = {
            'achievement.id': achievement_id,
            'person.login': login,
            '_fields': 'id,revision,level,is_active'
        }

        response = self.request(method='GET', url=url, params=params)

        return {
            'id': response['objects'][0]['id'],
            'revision': response['objects'][0]['revision'],
            'level': response['objects'][0]['level'],
            'is_active': response['objects'][0]['is_active'],
        } if response['objects'] else None

    def create_user_achievement(self, achievement_id, login, level, comment=None,
                                is_active=True, fields: Optional[Iterable[str]] = None):
        url = f'{self.API_URL}/given/'
        params = self.get_params(fields)

        data = {
            'achievement.id': achievement_id,
            'person.login': login,
            'is_active': is_active,
            'level': level,
        }
        if comment:
            data['comment'] = comment

        return self.request(method='POST', url=url, params=params, json=data)

    def update_user_achievement(self, given_id, revision, level=None, comment=None,
                                is_active=True, fields: Optional[Iterable[str]] = None):
        url = f'{self.API_URL}/given/{given_id}/'
        params = self.get_params(fields)

        data = {
            'revision': revision,
            'is_active': is_active,
        }
        if level:
            data['level'] = level
        if comment:
            data['comment'] = comment

        return self.request(method='PUT', url=url, params=params, json=data)

    def add_user_achievement(self, achievement_id, login, comment=None,
                             is_active=True, fields: Optional[Iterable[str]] = None):
        log.info(_(f"Обновление/выдача ачивки {achievement_id} пользователю {login}"))

        is_counter = self.get_achievement(
            achievement_id=achievement_id,
            fields=['is_counter']
        )['is_counter']

        kwargs = {
            'comment': comment,
            'fields': fields,
            'is_active': is_active,
        }

        given_data = self.get_user_achievement(achievement_id=achievement_id, login=login)

        if given_data:
            was_active = given_data['is_active']
            if not was_active and not is_active:
                raise ValidationError(_(f"Ачивка {achievement_id} неактивна и is_active=False, пользователь {login}"))

            given_id = given_data['id']
            level = given_data['level']
            revision = given_data['revision']

            # если ачивка неактивна, то сначала ее надо активировать отдельным запросом
            if not was_active and is_active:
                self.update_user_achievement(
                    given_id=given_id,
                    revision=revision,
                    is_active=True,
                )
                revision += 1

            if level == self.NO_LEVELS and was_active and is_active and not comment:  # нечего обновлять
                return _(f"Ачивка {achievement_id} уже выдана пользователю {login}")

            if is_counter:
                level += 1

            kwargs.update(
                {
                    'given_id': given_id,
                    'revision': revision,
                    'level': level,
                },
            )

            return self.update_user_achievement(**kwargs)

        level = self.INIT_LEVEL if is_counter else self.NO_LEVELS
        kwargs.update(
            {
                'achievement_id': achievement_id,
                'login': login,
                'level': level,
            },
        )

        return self.create_user_achievement(**kwargs)


def get_client() -> AchieveryClient:
    return AchieveryClient(
        api_url=settings.ACHIEVERY_API_URL,
        token=settings.ACHIEVERY_OAUTH_TOKEN,
    )


achievery_client = SimpleLazyObject(lambda: get_client())
