"""
Construcción del cliente autenticado de Google.

El material de la clave se descifra en memoria, se le entrega a la biblioteca de
Google y no se guarda en ninguna variable de módulo, ni se registra, ni se
devuelve. La función que lo descifra es corta a propósito: cuanto menos código
toca el texto plano, menos lugares hay donde pueda terminar en un rastreo de
excepción.

El servicio se arma con el descubrimiento estático incluido en la biblioteca y
no con el remoto. Además de evitar una llamada de red por cada construcción,
hace que la suite de pruebas pueda correr sin salir a internet, que es lo que
impide que un test gaste cuota real de una propiedad real.
"""

import json

from google.auth.exceptions import GoogleAuthError
from google.oauth2 import service_account
from googleapiclient.discovery import build

from apps.credentials.crypto import decrypt
from apps.credentials.models import Module
from apps.credentials.services import resolve_credential
from apps.gsc.errors import GoogleCallError, GoogleErrorCode

#: Alcances que la cuenta de servicio necesita para todo lo que el producto
#: hace. El primero cubre Search Console —inspeccionar URLs, listar sitemaps y
#: enviarlos— y el segundo, pedirle a Google que indexe una dirección.
#:
#: Van juntos en una sola lista, y no uno por API, porque el material de la
#: clave es el mismo: la credencial se firma una vez con los dos alcances y
#: sirve para las dos APIs. Pedir el de indexación no habilita nada por sí solo
#: —la API tiene que estar habilitada en el proyecto de Google Cloud y la cuenta
#: de servicio autorizada como propietaria—, así que ampliarlo no cambia lo que
#: el producto puede hacer hasta que alguien complete esos dos pasos.
SCOPES = [
    'https://www.googleapis.com/auth/webmasters',
    'https://www.googleapis.com/auth/indexing',
]

API_NAME = 'searchconsole'
API_VERSION = 'v1'

#: La Indexing API es otro servicio, con otro techo y otro dueño de ese techo:
#: sus cuotas son por proyecto de Google Cloud y no por propiedad.
INDEXING_API_NAME = 'indexing'
INDEXING_API_VERSION = 'v3'


def google_credentials(credential):
    """
    Arma las credenciales de Google a partir del material cifrado.

    Recibe la fila, no el texto plano: así el descifrado ocurre en un único
    lugar y quien llama nunca tiene el secreto entre las manos.

    Un material que la biblioteca de Google no puede usar sale de acá como
    `GoogleCallError`, igual que un rechazo de la red. La frontera promete que
    todo fallo hacia Google vuelve clasificado, y una excepción cruda de
    `google-auth` rompía esa promesa donde menos se ve: no la atrapaba nadie y
    llegaba a la pantalla como error del servidor.
    """
    try:
        info = json.loads(decrypt(credential.encrypted_key))
        return service_account.Credentials.from_service_account_info(info, scopes=SCOPES)
    except (GoogleAuthError, ValueError) as exc:
        raise GoogleCallError(
            code=GoogleErrorCode.INVALID_KEY,
            detail=str(exc),
        ) from exc


def build_service(credential):
    """Devuelve el servicio de Search Console firmado con esta credencial."""
    return build(
        API_NAME,
        API_VERSION,
        credentials=google_credentials(credential),
        cache_discovery=False,
        static_discovery=True,
    )


def build_indexing_service(credential):
    """Devuelve el servicio de la Indexing API firmado con esta credencial."""
    return build(
        INDEXING_API_NAME,
        INDEXING_API_VERSION,
        credentials=google_credentials(credential),
        cache_discovery=False,
        static_discovery=True,
    )


def service_for(account):
    """
    Servicio listo para trabajar en nombre de una cuenta.

    Resuelve la credencial por módulo en el momento de usarla. Nunca la recibe
    por parámetro desde una fila de dominio: eso es lo que permite reemplazar
    una credencial sin tocar ningún dominio (FR-012).
    """
    credential = resolve_credential(account, Module.SEARCH_CONSOLE)
    return build_service(credential)


def indexing_service_for(account):
    """
    Servicio de indexación en nombre de una cuenta.

    Sale de la misma credencial que Search Console —el módulo es el mismo— y no
    de una credencial aparte: son dos APIs del mismo proyecto de Google Cloud,
    firmadas con la misma clave.
    """
    credential = resolve_credential(account, Module.SEARCH_CONSOLE)
    return build_indexing_service(credential)
