import logging
import yenv
import requests
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry

from django.conf import settings

from intranet.femida.src.utils.tvm2_client import get_service_ticket

logger = logging.getLogger(__name__)


class ContestError(Exception):
    pass


class ContestConflictError(ContestError):
    pass


class ContestForbiddenError(ContestError):
    pass


RETRIES_COUNT = 3
BACKOFF_FACTOR = 0.1
STATUS_FORCELIST = (500, 502)


class ContestBaseAPI:
    headers = {}
    url = None
    api_name = None
    use_tvm = False

    @classmethod
    def _get_response(cls, method, path, data=None, json=None, params=None, headers=None):
        url = cls.url + path
        params = params or {}
        data = data or {}
        _headers = dict(cls.headers)
        _headers.update(headers or {})
        if cls.use_tvm:
            _headers['X-Ya-Service-Ticket'] = get_service_ticket(settings.TVM_CONTEST_CLIENT_ID)
        return cls._session().request(
            method=method,
            url=url,
            params=params,
            data=data,
            json=json,
            headers=_headers,
            timeout=settings.CONTEST_API_TIMEOUT,
            # В тестинге ContestAPI протухший сертификат
            verify=settings.YANDEX_INTERNAL_CERT_PATH if yenv.type == 'production' else False,
        )

    @classmethod
    def _session(cls):
        """
        https://www.peterbe.com/plog/best-practice-with-retries-with-requests
        """
        session = requests.Session()
        retry = Retry(
            total=RETRIES_COUNT,
            read=RETRIES_COUNT,
            connect=RETRIES_COUNT,
            backoff_factor=BACKOFF_FACTOR,
            status_forcelist=STATUS_FORCELIST,
        )
        adapter = HTTPAdapter(max_retries=retry)
        session.mount('http://', adapter)
        session.mount('https://', adapter)
        return session

    @classmethod
    def _request(cls, method, path, data=None, json=None, params=None, headers=None,
                 response_is_byte=False):
        try:
            response = cls._get_response(method, path, data, json, params, headers)
        except requests.RequestException:
            logger.exception('Failed request to %s, url: %s', cls.api_name, path)
            raise ContestError

        if not response.ok:
            logger.error(
                'Bad status code (%s) from %s, url: %s, message: %s',
                response.status_code, cls.api_name, path, response.content
            )
            if response.status_code == 409:
                raise ContestConflictError
            if response.status_code == 403:
                raise ContestForbiddenError

            raise ContestError
        if response_is_byte:
            return str(response.content)
        try:
            return response.json()
        except ValueError:
            logger.exception('Invalid JSON in %s response, url: %s', cls.api_name, path)
            raise ContestError


class ContestAPI(ContestBaseAPI):
    headers = {
        'Accept': 'application/json',
        'Authorization': 'OAuth {}'.format(settings.CONTEST_TOKEN),
    }
    url = settings.CONTEST_API_URL
    api_name = 'Contest API'

    @classmethod
    def create_participation(cls, contest_id, login):
        return cls._request(
            method='POST',
            path='contests/' + str(contest_id) + '/participants',
            data={
                'login': login,
            },
        )

    @classmethod
    def get_contest(cls, contest_id):
        return cls._request(
            method='GET',
            path='contests/' + str(contest_id),
        )

    @classmethod
    def get_contest_standings(cls, contest_id):
        return cls._request(
            method='GET',
            path='contests/' + str(contest_id) + '/standings',
            params={
                'pageSize': 10000,
                'forJudge': True,
            }
        )

    @classmethod
    def get_participation(cls, contest_id, participant_id=None):
        path = 'contests/' + str(contest_id)
        if participant_id is None:
            path += '/participation'
        else:
            path += '/participants/' + str(participant_id)
        return cls._request(
            method='GET',
            path=path,
        )

    @classmethod
    def get_clarifications(cls, contest_id):
        return cls._request(
            method='GET',
            path='contests/' + str(contest_id) + '/clarifications',
        )

    @classmethod
    def get_messages(cls, contest_id):
        return cls._request(
            method='GET',
            path='contests/' + str(contest_id) + '/messages',
        )

    @classmethod
    def get_submissions(cls, contest_id):
        return cls._request(
            method='GET',
            path='contests/' + str(contest_id) + '/submissions',
        )

    @classmethod
    def get_problems(cls, contest_id):
        return cls._request(
            method='GET',
            path='contests/' + str(contest_id) + '/problems'
        )

    @classmethod
    def get_statement(cls, problem_id, path):
        try:
            return cls._request(
                method='GET',
                path='/problems',
                params={
                    'problemId': problem_id,
                    'path': path
                },
                response_is_byte=True
            )
        except ContestForbiddenError:
            return "<html>Add femida-robot to task</html>"


class ContestTVMApi(ContestBaseAPI):
    url = settings.CONTEST_PRIVATE_API_URL
    api_name = 'TVM Contest API'
    use_tvm = True

    @classmethod
    def get_contest_info(cls, contest_id):
        return cls._request(
            method='GET',
            path='api/private/femida/contest/' + str(contest_id)
        )

    @classmethod
    def get_problem_file(cls, problem_id, path):
        return cls._request(
            method='GET',
            path='api/private/femida/problem-statement',
            params={
                "problemId": str(problem_id),
                "path": str(path),
            },
            response_is_byte=True
        )


class ContestPrivateAPI(ContestBaseAPI):
    url = settings.CONTEST_PRIVATE_API_URL
    api_name = 'Private Contest API'

    @classmethod
    def register_by_answer_id(cls, contest_id, answer_id, passcode):
        return cls._request(
            method='POST',
            path='action/tech/forms/anonymous/registration',
            params={
                'contestId': contest_id,
                'passcode': passcode,
            },
            headers={
                'X-ANSWER-ID': answer_id,
            },
        )

    @classmethod
    def get_participant_ids(cls, answer_ids):
        return cls._request(
            method='POST',
            path='action/tech/forms/anonymous/registration/search',
            headers={'Content-Type': 'application/json'},
            json={
                'answerIds': answer_ids,
            },
        )
