import logging
from urllib.parse import urljoin

import requests
from requests.auth import AuthBase

from django.core.exceptions import ImproperlyConfigured
from django.utils.functional import SimpleLazyObject

from mentor.utils.requests import requests_retry_session

from .settings import (
    STAFF_API_AUTH_METHOD,
    STAFF_API_BASE_URL,
    STAFF_API_OAUTH_METHOD_NAME,
    STAFF_API_OAUTH_TOKEN,
    STAFF_API_SKIP_TEST,
    STAFF_API_TVM2_METHOD_NAME,
    STAFF_API_TVM_ID,
)

log = logging.getLogger(__name__)


class StaffOAuth(AuthBase):
    def __init__(self, token):
        self.token = token

    def __call__(self, request):
        request.headers["Authorization"] = f"OAuth {self.token}"
        return request


class StaffTVMAuth(AuthBase):
    service_ticket_header = "X-Ya-Service-Ticket"

    def __init__(self, tvm2_id, tvm2_client, retries=3):
        self.tvm2_id = tvm2_id
        self.tvm2_client = tvm2_client
        self.retry_attempts = retries

    def __call__(self, request):
        for _ in range(self.retry_attempts):
            service_tickets = self.tvm2_client.get_service_tickets(self.tvm2_id)
            staff_ticket = service_tickets.get(self.tvm2_id)
            if staff_ticket:
                request.headers[self.service_ticket_header] = staff_ticket
                break
        else:
            log.error("Cannot get service ticket for Staff API")

        return request


class Endpoint:
    def __init__(self, client: "StaffClient", name: str, **kwargs):
        self.client = client
        self.name = name

    def request(self, **kwargs):
        return self.client.api_request(self.name, **kwargs)

    def get(self, **kwargs):
        return self.request(params=kwargs)

    def get_one(self, **kwargs):
        kwargs["_one"] = 1
        return self.get(**kwargs)

    def post(self, **kwargs):
        return self.request(method="post", params=kwargs)


class StaffClient:
    def __init__(self, base_url=None, timeout=10, retries=3, session=None, **kwargs):
        self.base_url = base_url or STAFF_API_BASE_URL
        self.timeout = timeout
        self.retries = retries
        self.session = session or requests.Session()

        if self.retries:
            backoff_factor = kwargs.get("backoff_factor", 0.3)
            status_forcelist = kwargs.get("status_forcelist", (500, 502, 504))

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

    def api_request(self, endpoint, method="get", **kwargs):
        url = urljoin(self.base_url, endpoint)

        if STAFF_API_SKIP_TEST:
            return {}

        response = self.session.request(method, url, timeout=self.timeout, **kwargs)
        response.raise_for_status()
        return self.parse_response(response)

    def parse_response(self, response):
        return response.json()

    def __getattr__(self, item):
        return type(item, (Endpoint,), {})(client=self, name=item)


def get_client() -> StaffClient:
    session = requests.session()

    if STAFF_API_AUTH_METHOD == STAFF_API_OAUTH_METHOD_NAME:
        if not STAFF_API_OAUTH_TOKEN:
            raise ImproperlyConfigured(
                f"Please set the STAFF_API_OAUTH_TOKEN settings for {STAFF_API_AUTH_METHOD} method"
            )

        session.auth = StaffOAuth(token=STAFF_API_OAUTH_TOKEN)

    elif STAFF_API_AUTH_METHOD == STAFF_API_TVM2_METHOD_NAME:
        if not STAFF_API_TVM_ID:
            raise ImproperlyConfigured(
                f"Please set the STAFF_API_TVM_ID settings for {STAFF_API_AUTH_METHOD} method"
            )

        from mentor.contrib.tvm.client import tvm2_client

        session.auth = StaffTVMAuth(tvm2_id=STAFF_API_TVM_ID, tvm2_client=tvm2_client)

    else:
        session = None

    return StaffClient(session=session)


staff_api = SimpleLazyObject(lambda: get_client())
