import json
import time

import requests
from pydantic import BaseModel

from ..helpers import MessageHandler, RequestManager
from ..pephub_oauth.const import PEPHUB_DEVICE_INIT_URI, PEPHUB_DEVICE_TOKEN_URI
from ..pephub_oauth.exceptions import (
    PEPHubResponseException,
    PEPHubTokenExchangeException,
)
from ..pephub_oauth.models import (
    InitializeDeviceCodeResponse,
    PEPHubDeviceTokenResponse,
)


class PEPHubAuth(RequestManager):
    """Class responsible for authorization to PEPhub."""

    def login_to_pephub(self):
        pephub_response = self._request_pephub_for_device_code()
        print(
            f"User verification code: {pephub_response.device_code}, please go to the website: "
            f"{pephub_response.auth_url} to authenticate."
        )

        # Sleep 2 minutes and then try 3 times exchange device code on token
        time.sleep(2)

        number_of_token_exchange_attempts = 3
        for i in range(number_of_token_exchange_attempts):
            try:
                user_token = self._exchange_device_code_on_token(
                    pephub_response.device_code
                )
            except PEPHubTokenExchangeException:
                time.sleep(2)
            else:
                print("Successfully logged in!")
                return user_token

        # If you didn't log in press enter to try again.
        input("If you logged in, press enter to continue...")
        try:
            user_token = self._exchange_device_code_on_token(
                pephub_response.device_code
            )
        except PEPHubTokenExchangeException:
            MessageHandler.print_warning("You are not logged in")
        else:
            MessageHandler.print_success("Successfully logged in!")
            return user_token

    def _request_pephub_for_device_code(self) -> InitializeDeviceCodeResponse:
        """Requests device code from pephub."""
        response = PEPHubAuth.send_request(
            method="POST",
            url=PEPHUB_DEVICE_INIT_URI,
            params=None,
            headers=None,
        )
        return self._handle_pephub_response(response, InitializeDeviceCodeResponse)

    def _exchange_device_code_on_token(self, device_code: str) -> str:
        """
        Send request with device code to pephub in order to exchange it on JWT.

        Args:
            device_code: device code that was generated by pephub
        """
        response = PEPHubAuth.send_request(
            method="POST",
            url=PEPHUB_DEVICE_TOKEN_URI,
            params=None,
            headers={"device-code": device_code},
        )
        pephub_token_response = self._handle_pephub_response(
            response, PEPHubDeviceTokenResponse
        )
        return pephub_token_response.jwt_token

    @staticmethod
    def _handle_pephub_response(
        response: requests.Response, model: type[BaseModel]
    ) -> BaseModel | InitializeDeviceCodeResponse | PEPHubDeviceTokenResponse:
        """
        Decode the response from PEPhub and pack the returned data into appropriate model.

        Args:
            response: Response from pephub
            model: Model that the data will be packed to.

        Returns:
            Response data as an instance of correct model.
        """
        if response.status_code == 401:
            raise PEPHubTokenExchangeException()
        if response.status_code != 200:
            raise PEPHubResponseException()
        try:
            content = json.loads(PEPHubAuth.decode_response(response))
        except json.JSONDecodeError:
            raise Exception("Something went wrong with PEPhub response")

        return model(**content)
