'''CIBA Authentication'''
import logging
from uuid import uuid4

from px_device_identity import Device, DeviceProperties
from px_device_identity.device.jwt import (
    device_singing_base, get_unix_time_in_seconds
)
from px_device_identity.device.sign import Sign
from requests import ConnectionError, post
from typing import Union

log = logging.getLogger(__name__)


class CIBAAuthentication():
    '''CIBA Authentication'''

    def __init__(self, device: Device):
        self.device_properties: 'DeviceProperties' = device.properties
        self.device_jwt = device.get_device_jwt()['device_jwt']
        self.config_dir = device.config_dir
        self.key_dir = device.key_dir

    def _auth_request(self, login_hint: str, is_login_hint_token: bool, message: str, scope: str, resource: Union[None, str] = None):
        iat = get_unix_time_in_seconds()
        exp = iat + 300
        initial_request = {
            'scope': scope,
            'binding_message': message,
            'iss': self.device_properties.client_id,
            'aud': f"{self.device_properties.host}/oidc",
            'exp': exp,
            'iat': iat,
            'nbf': iat - 10,
            'jti': uuid4().hex,
        }
        
        if is_login_hint_token:
            initial_request['login_hint_token'] = login_hint
        else:
            initial_request['login_hint'] = login_hint
            
        if resource:
            initial_request['resource'] = resource
        
        signature_content = device_singing_base(
            initial_request,
            self.device_properties.client_id,
            self.device_properties.host + '/oidc',
            iat,
            exp
        )
        signature = Sign(
            self.device_properties,
            signature_content,
            self.key_dir,
        ).sign()
        result = f"{signature_content}.{signature}"
        return result

    def _auth_request_content(self, login_hint: str, is_login_hint_token: bool, message: str, scope: str, resource: Union[None, str] = None):
        '''CIBA auth request content'''
        auth_request = {
            "request": self._auth_request(
                login_hint,
                is_login_hint_token,
                message,
                scope,
                resource,
            ),
            "client_id": self.device_properties.client_id,
            "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
            "client_assertion": self.device_jwt,
        }
        return auth_request

    def _status_request_content(self, auth_req_id: str):
        '''CIBA status request content'''
        status_request = {
            'grant_type': 'urn:openid:params:grant-type:ciba',
            'auth_req_id': auth_req_id,
            'client_id': self.device_properties.client_id,
            'client_assertion_type': 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
            'client_assertion': self.device_jwt
        }
        return status_request

    def _make_request(self, url: str, data: dict):
        '''Make API request'''
        try:
            res = post(url, data=data, headers={
                'Accept': 'application/json, text/plain, */*',
                'Content-Type': 'application/x-www-form-urlencoded'
            })
            res.raise_for_status()
            return res.json()
        except ConnectionError as err:
            log.error(f"Connection to {url} failed.")
            raise err
        except Exception as err:
            log.error(err)
            raise err

    def login(self, login_hint: str, is_login_hint_token: bool, message: str = 'Authorize Login', scope: str = "openid contact profile", resource: Union[None, str] = None):
        '''
        CIBA (Backchannel) login request

        Params:
            login_hint_token: username
            message: Optional login message
                     Recommended format 'Authorize login to ApplicationName'
        '''
        log.debug(f"=> CIBA login request for {login_hint}")
        url = self.device_properties.host + '/oidc/backchannel'
        return self._make_request(url, self._auth_request_content(
            login_hint,
            is_login_hint_token,
            message,
            scope,
            resource
        ))

    def status(self, auth_req_id: str):
        '''CIBA (Backchannel) login status request'''
        log.debug(f"=> CIBA login status request for ID {auth_req_id}")
        url = self.device_properties.host + '/oidc/token'
        return self._make_request(url, self._status_request_content(auth_req_id))
