"""
Una clave a la que le falta un campo que sólo la biblioteca de Google mira.

Este archivo existe por un fallo concreto que llegó a pantalla: un archivo sin
`token_uri` pasaba la validación entera —tiene `type`, `project_id`,
`private_key`, `client_email`, todo lo que se muestra— y recién explotaba al
construir el firmante, en la primera llamada a Google. La excepción venía de
`google-auth`, no de nuestra frontera, así que no la atrapaba nadie y salía como
error del servidor en «Comprobar acceso».

Son dos agujeros encadenados y por eso hay dos grupos de pruebas: uno cierra la
puerta de entrada y el otro se asegura de que, si algo se cuela igual, vuelva
clasificado en vez de crudo.
"""

import json

import pytest

from apps.credentials.validation import InvalidKeyFile, KeyFileError, validate_key_file
from apps.gsc.auth import google_credentials
from apps.gsc.errors import GoogleCallError, GoogleErrorCode
from tests.factories import TEST_KEY, create_account, create_credential, key_json


class TestValidationRequiresTokenUri:
    def test_a_key_without_token_uri_is_rejected(self):
        """
        El campo no se muestra en ninguna pantalla, y aun así es obligatorio.

        Es la clase de campo que se olvida al armar la lista de requeridos:
        nadie lo mira, nadie lo pide, no aparece en la ficha de la credencial. La
        biblioteca de Google sí lo necesita para firmar.
        """
        with pytest.raises(InvalidKeyFile) as failure:
            validate_key_file(key_json(token_uri=None))

        assert failure.value.field == 'token_uri'
        assert 'token_uri' in failure.value.message

    def test_what_to_do_travels_as_the_code_and_the_field(self):
        """
        Qué hacer no viaja escrito: viaja el código, y la frase que dice «bajá el
        archivo de nuevo» la arma el catálogo del cliente en el idioma de quien
        subió la clave. El nombre del campo va como dato porque la oración lo
        nombra, y en cada idioma cae en un lugar distinto de la frase.
        """
        with pytest.raises(InvalidKeyFile) as failure:
            validate_key_file(key_json(token_uri=None))

        assert failure.value.code == KeyFileError.FIELD_MISSING
        assert failure.value.params['field'] == 'token_uri'

    def test_a_complete_key_still_passes(self):
        """La red no se cierra de más: el archivo que Google entrega es válido."""
        validated = validate_key_file(key_json())

        assert validated.project_id == TEST_KEY['project_id']


@pytest.mark.django_db
class TestTheBoundaryReturnsTheClassifiedFailure:
    """
    Lo que faltaba: que un material inservible no salga como excepción cruda.

    `apps/gsc/` promete que todo fallo hacia Google vuelve como
    `GoogleCallError`. Quien llama —la comprobación de acceso, la de credencial—
    atrapa exactamente eso. Una excepción de `google-auth` esquivaba el trato y
    llegaba entera hasta la pantalla.
    """

    def _credential_with(self, account, material: dict):
        from apps.credentials.crypto import encrypt

        credential = create_credential(account)
        credential.encrypted_key = encrypt(json.dumps(material))
        credential.save(update_fields=['encrypted_key'])
        return credential

    def test_material_without_token_uri_comes_back_as_invalid_key(self):
        account = create_account()
        incomplete = {k: v for k, v in TEST_KEY.items() if k != 'token_uri'}

        with pytest.raises(GoogleCallError) as failure:
            google_credentials(self._credential_with(account, incomplete))

        assert failure.value.code == GoogleErrorCode.INVALID_KEY

    def test_the_message_is_for_the_person_and_not_for_whoever_programs(self):
        """
        El texto que ve la persona sale de nuestra tabla, no de la biblioteca.

        «Service account info was not in the expected format, missing fields
        token_uri» es exacto y no le sirve a nadie que no programe.
        """
        account = create_account()
        incomplete = {k: v for k, v in TEST_KEY.items() if k != 'token_uri'}

        with pytest.raises(GoogleCallError) as failure:
            google_credentials(self._credential_with(account, incomplete))

        assert 'Google rechazó la clave' in failure.value.message
        # El texto de la biblioteca se conserva aparte, para el registro y el
        # panel interno: sirve para diagnosticar, no para mostrar.
        assert 'token_uri' in failure.value.detail

    def test_unreadable_material_does_not_come_out_raw_either(self):
        """Si el descifrado devuelve algo que no es JSON, el trato sigue en pie."""
        from apps.credentials.crypto import encrypt

        account = create_account()
        credential = create_credential(account)
        credential.encrypted_key = encrypt('esto no es json')
        credential.save(update_fields=['encrypted_key'])

        with pytest.raises(GoogleCallError) as failure:
            google_credentials(credential)

        assert failure.value.code == GoogleErrorCode.INVALID_KEY
