"""Session provider for interaction with Renault servers."""

import asyncio
import logging
from typing import Any

import aiohttp
from marshmallow.schema import Schema

from . import gigya
from . import kamereon
from .const import CONF_COUNTRY
from .const import CONF_GIGYA_APIKEY
from .const import CONF_GIGYA_URL
from .const import CONF_KAMEREON_APIKEY
from .const import CONF_KAMEREON_URL
from .const import CONF_LOCALE
from .credential import Credential
from .credential import JWTCredential
from .credential_store import CredentialStore
from .exceptions import NotAuthenticatedException
from .exceptions import RenaultException
from .gigya.exceptions import GigyaResponseException
from .kamereon import models
from renault_api.helpers import get_api_keys

_LOGGER = logging.getLogger(__name__)


class RenaultSession:
    """Renault session for interaction with Renault servers."""

    def __init__(
        self,
        websession: aiohttp.ClientSession,
        locale: str | None = None,
        country: str | None = None,
        locale_details: dict[str, str] | None = None,
        credential_store: CredentialStore | None = None,
    ) -> None:
        """Initialise RenaultSession."""
        self._gigya_lock = asyncio.Lock()
        self._websession = websession
        self._credentials: CredentialStore = credential_store or CredentialStore()

        if locale_details:
            for k, v in locale_details.items():
                self._credentials[k] = Credential(v)
        if locale:
            self._credentials[CONF_LOCALE] = Credential(locale)
        if country:
            self._credentials[CONF_COUNTRY] = Credential(country)

    async def login(self, login_id: str, password: str) -> None:
        """Attempt login on Gigya."""
        self._credentials.clear_keys(gigya.GIGYA_KEYS)

        response = await gigya.login(
            self._websession,
            await self._get_gigya_root_url(),
            await self._get_gigya_api_key(),
            login_id,
            password,
        )
        credential = Credential(response.get_session_cookie())
        self._credentials[gigya.GIGYA_LOGIN_TOKEN] = credential

    @property
    def login_token(self) -> str | None:
        """Return the current Gigya login token.

        This token is obtained from the password on `login`, and is used to
        mint JWTs (access tokens) without re-supplying the password. Store it
        securely instead of the password, and restore it with `set_login_token`
        (or by pre-populating the credential store) on the next session.
        """
        return self._credentials.get_value(gigya.GIGYA_LOGIN_TOKEN)

    def set_login_token(self, login_token: str) -> None:
        """Restore a Gigya login token obtained from a previous session.

        This avoids storing and reusing the user password: a session restored
        this way can mint JWTs without ever calling `login`.
        """
        self._credentials.clear_keys(gigya.GIGYA_KEYS)
        self._credentials[gigya.GIGYA_LOGIN_TOKEN] = Credential(login_token)

    async def _get_credential(self, key: str) -> str:
        """Get specified credential, or raise RenaultException."""
        if key not in self._credentials:
            if CONF_LOCALE in self._credentials:
                await self._update_from_locale()

        value = self._credentials.get_value(key)
        if value:
            return value

        if key == gigya.GIGYA_LOGIN_TOKEN:
            raise NotAuthenticatedException("Gigya login token not available.")
        raise RenaultException(f"Credential `{key}` not found in credential cache.")

    async def _update_from_locale(self) -> None:
        """Update all missing setting based on locale."""
        locale = await self._get_credential(CONF_LOCALE)
        if CONF_COUNTRY not in self._credentials:
            self._credentials[CONF_COUNTRY] = Credential(locale[-2:])
        locale_details = await get_api_keys(locale=locale, websession=self._websession)
        for k, v in locale_details.items():
            if k not in self._credentials:
                self._credentials[k] = Credential(v)

    async def _get_country(self) -> str:
        """Get country from credential store."""
        return await self._get_credential(CONF_COUNTRY)

    async def _get_kamereon_api_key(self) -> str:
        """Get Kamereon api-key from credential store."""
        return await self._get_credential(CONF_KAMEREON_APIKEY)

    async def _get_kamereon_root_url(self) -> str:
        """Get Kamereon root url from credential store."""
        return await self._get_credential(CONF_KAMEREON_URL)

    async def _get_gigya_api_key(self) -> str:
        """Get Gigya api-key from credential store."""
        return await self._get_credential(CONF_GIGYA_APIKEY)

    async def _get_gigya_root_url(self) -> str:
        """Get Gigya root url from credential store."""
        return await self._get_credential(CONF_GIGYA_URL)

    async def _get_login_token(self) -> str:
        """Get current login token from credential store."""
        return await self._get_credential(gigya.GIGYA_LOGIN_TOKEN)

    async def _get_person_id(self) -> str:
        """Get person id from credential store or from Gigya."""
        async with self._gigya_lock:
            person_id = self._credentials.get_value(gigya.GIGYA_PERSON_ID)
            if person_id:
                return person_id
            login_token = await self._get_login_token()
            response = await gigya.get_account_info(
                self._websession,
                await self._get_gigya_root_url(),
                await self._get_gigya_api_key(),
                login_token,
            )
            person_id = response.get_person_id()
            self._credentials[gigya.GIGYA_PERSON_ID] = Credential(person_id)
            return person_id

    async def _get_jwt(self) -> str:
        """Get json web token from credential store or from Gigya.."""
        async with self._gigya_lock:
            jwt = self._credentials.get_value(gigya.GIGYA_JWT)
            if jwt:
                return jwt
            login_token = await self._get_login_token()
            try:
                response = await gigya.get_jwt(
                    self._websession,
                    await self._get_gigya_root_url(),
                    await self._get_gigya_api_key(),
                    login_token,
                )
            except GigyaResponseException as exc:
                if exc.error_code in [403005, 403013]:
                    self._credentials.clear_keys(gigya.GIGYA_KEYS)
                raise NotAuthenticatedException("Authentication expired.") from exc
            else:
                jwt = response.get_jwt()
                self._credentials[gigya.GIGYA_JWT] = JWTCredential(jwt)
                return jwt

    async def http_request(
        self,
        method: str,
        endpoint: str,
        json: dict[str, Any] | None = None,
        *,
        schema: Schema | None = None,
    ) -> models.KamereonResponse:
        """GET to specified endpoint."""
        url = (await self._get_kamereon_root_url()) + endpoint
        params = {"country": await self._get_country()}
        return await kamereon.request(
            websession=self._websession,
            method=method,
            url=url,
            api_key=await self._get_kamereon_api_key(),
            gigya_jwt=await self._get_jwt(),
            params=params,
            json=json,
            schema=schema,
        )

    async def get_person(self) -> models.KamereonPersonResponse:
        """GET to /persons/{person_id}."""
        return await kamereon.get_person(
            websession=self._websession,
            root_url=await self._get_kamereon_root_url(),
            api_key=await self._get_kamereon_api_key(),
            gigya_jwt=await self._get_jwt(),
            country=await self._get_country(),
            person_id=await self._get_person_id(),
        )

    async def get_account_vehicles(
        self, account_id: str
    ) -> models.KamereonVehiclesResponse:
        """GET to /accounts/{account_id}/vehicles."""
        return await kamereon.get_account_vehicles(
            websession=self._websession,
            root_url=await self._get_kamereon_root_url(),
            api_key=await self._get_kamereon_api_key(),
            gigya_jwt=await self._get_jwt(),
            country=await self._get_country(),
            account_id=account_id,
        )

    async def get_vehicle_details(
        self, account_id: str, vin: str
    ) -> models.KamereonVehicleDetailsResponse:
        """GET to /accounts/{account_id}/vehicles/{vin}/details."""
        return await kamereon.get_vehicle_details(
            websession=self._websession,
            root_url=await self._get_kamereon_root_url(),
            api_key=await self._get_kamereon_api_key(),
            gigya_jwt=await self._get_jwt(),
            country=await self._get_country(),
            account_id=account_id,
            vin=vin,
        )

    async def get_vehicle_data(
        self,
        account_id: str,
        vin: str,
        endpoint: str,
        params: dict[str, str] | None = None,
        *,
        adapter_type: str = "kca",
    ) -> models.KamereonVehicleDataResponse:
        """GET to /v{endpoint_version}/cars/{vin}/{endpoint}."""
        return await kamereon.get_vehicle_data(
            websession=self._websession,
            root_url=await self._get_kamereon_root_url(),
            api_key=await self._get_kamereon_api_key(),
            gigya_jwt=await self._get_jwt(),
            country=await self._get_country(),
            account_id=account_id,
            vin=vin,
            endpoint=endpoint,
            params=params,
            adapter_type=adapter_type,
        )

    async def get_vehicle_contracts(
        self,
        account_id: str,
        vin: str,
    ) -> models.KamereonVehicleContractsResponse:
        """GET to /v{endpoint_version}/cars/{vin}/contracts."""
        return await kamereon.get_vehicle_contracts(
            websession=self._websession,
            root_url=await self._get_kamereon_root_url(),
            api_key=await self._get_kamereon_api_key(),
            gigya_jwt=await self._get_jwt(),
            country=await self._get_country(),
            account_id=account_id,
            vin=vin,
            locale=await self._get_credential(CONF_LOCALE),
        )

    async def set_vehicle_action(
        self,
        account_id: str,
        vin: str,
        endpoint: str,
        attributes: dict[str, Any],
        *,
        adapter_type: str = "kca",
    ) -> models.KamereonVehicleDataResponse:
        """POST to /v{endpoint_version}/cars/{vin}/{endpoint}."""
        return await kamereon.set_vehicle_action(
            websession=self._websession,
            root_url=await self._get_kamereon_root_url(),
            api_key=await self._get_kamereon_api_key(),
            gigya_jwt=await self._get_jwt(),
            country=await self._get_country(),
            account_id=account_id,
            vin=vin,
            endpoint=endpoint,
            attributes=attributes,
            adapter_type=adapter_type,
        )
