import logging
from typing import (
    List,
    Optional,
    Tuple,
)
from urllib.parse import urljoin

import requests
from boss.model.service import AuthenticationService
from boss.model.service.dto import AuthenticationData
from boss_api.shared.exception import UnexpectedError
from helper import retry
from message_handler_builder import Configs
from requests import Response

logger = logging.getLogger(__name__)


class AuthenticateServiceImpl(AuthenticationService):

    def __init__(
        self,
        configs: Configs,
    ) -> None:

        self.configs = configs

    def authenticate(
        self,
        username: int,
        password: str,
    ) -> Optional[AuthenticationData]:

        try:
            body = dict(
                username=str(username),
                password=str(password),
            )
            errors_list = []

            response = retry(
                logger=logger,
                method_name="authenticate",
                n=self.configs.api_retry_policy.count,
                sleep_seconds=self.configs.api_retry_policy.sleep,
                trying_to_do=lambda: requests.request(
                    method="POST",
                    url=self._get_url(),
                    json=body,
                    headers={
                        "x-api-key": self.configs.pump_credentials.api_key,
                        "Content-Type": "application/json",
                    },
                    timeout=5,
                ),
                is_success=lambda resp: resp.status_code == 200,
                success=lambda resp: resp,
                failed=lambda resp: errors_list.append((resp.status_code, resp)),
                on_exception=lambda exception: errors_list.append(exception)
            )
            if response is not None:
                response_data = response.json()
                return AuthenticationData(
                    generate_by=response_data["generateBy"],
                    user_id=response_data["userId"],
                    user_name=response_data["userName"],
                    long_name=response_data["longName"],
                    user_level=response_data["userLevel"],
                    authentication_key=response_data["authenticationKey"],
                    generation_date=response_data["generationDate"],
                    expiration_date=response_data["expirationDate"],
                    interval=response_data["interval"],
                )

            response_error = self._get_response_error(errors_list=errors_list)

            logger.info(f"Fail on authenticate api request: {response_error.status_code} - {response_error.text}")

            if response_error.status_code in [401, 403, 404]:
                return None

            unexpected_error = UnexpectedError()
            unexpected_error.error_message = response_error.text
            raise unexpected_error
        except Exception as ex:
            logger.error(f"Error on authenticate api request: {str(ex)}")
            raise

    def _get_url(
        self,
    ) -> str:

        endpoint = self.configs.endpoints.authenticate.format(store_code=self.configs.store_id)
        return urljoin(
            base=self.configs.pump_credentials.base_url.rstrip("/"),
            url=endpoint.lstrip("/"),
        )

    @staticmethod
    def _get_response_error(
        errors_list: List[Tuple[int, Response]],
    ) -> Optional[Response]:

        if not errors_list:
            return None

        for last_error in reversed(errors_list):
            if not isinstance(last_error, Exception):
                _, response = last_error
                return response

        raise errors_list[-1]
