"""
Lo que la pantalla de conexión dice del acceso a la Indexing API.

La tarjeta **no comprueba nada**, y eso es la decisión y no una carencia: la
Indexing API no tiene lectura, así que lo único que se le podría pedir es
publicar una dirección de verdad, y eso gasta una de las doscientas del día del
proyecto entero. Un botón de «comprobar» sería un botón que hace en silencio lo
que dice estar comprobando.

Entonces lo que hay que defender es lo otro: que la conclusión salga **siempre**
de la evidencia guardada, que venga acompañada de la prueba —fecha y dirección—
y que ningún estado afirme más de lo que la evidencia sostiene.
"""

import pytest

from apps.gsc.errors import GoogleErrorCode
from apps.indexing.errors import IndexingErrorCode
from apps.indexing.models import IndexingRequest, IndexingState
from apps.indexing.services import ConnectionState, connection_state, create_batch, send_batch
from apps.jobs.models import BatchState
from apps.sitemaps.models import Url
from tests.doubles import FakeIndexingClient
from tests.factories import create_account, create_credential, create_domain

pytestmark = pytest.mark.django_db


@pytest.fixture
def account(client):
    account = create_account()
    create_credential(account)
    client.force_login(account)
    return account


@pytest.fixture
def domain(account):
    return create_domain(account)


def card(client) -> dict:
    """Las props de la tarjeta, tal como las recibe la vista."""
    response = client.get('/search-console/settings', HTTP_X_INERTIA='true')
    assert response.status_code == 200
    return response.json()['props']['indexing']


def ask(domain, answer=None):
    """Manda un pedido y devuelve el lote, con la respuesta que se le declare."""
    url = Url.objects.create(domain=domain, loc='https://ejemplo.test/a')
    batch = create_batch(domain, [url])
    batch.state = BatchState.QUEUED
    batch.save(update_fields=['state'])
    return send_batch(batch, client=FakeIndexingClient(default=answer))


# --- Los cinco estados ------------------------------------------------------


def test_without_a_single_request_it_says_it_does_not_know(client, domain):
    """
    «Sin confirmar» y no «funciona».

    Es el estado más fácil de falsear: la API puede estar perfectamente
    habilitada y nosotros no tenemos manera de saberlo sin gastar una
    publicación. Decir que funciona sería inventar una comprobación.
    """
    assert card(client)['state'] == ConnectionState.NEVER_ASKED


def test_an_accepted_request_is_what_lets_it_say_it_works(client, domain):
    batch = ask(domain)

    props = card(client)
    assert props['state'] == ConnectionState.WORKING
    # Y la prueba viaja al lado: sin fecha y sin dirección, «funciona» sería una
    # palabra nuestra en vez de un hecho con respaldo.
    assert props['checked_at'] is not None
    assert props['url'] == 'https://ejemplo.test/a'
    assert props['batch_id'] == str(batch.id)


def test_google_saying_the_api_is_off_is_what_says_the_api_is_off(client, domain):
    ask(domain, (GoogleErrorCode.API_NOT_ENABLED, 403))

    assert card(client)['state'] == ConnectionState.API_NOT_ENABLED


def test_google_turning_down_the_service_account_is_a_missing_permission(client, domain):
    ask(domain, (GoogleErrorCode.PERMISSION_DENIED, 403))

    assert card(client)['state'] == ConnectionState.NOT_AUTHORIZED


def test_an_answer_about_one_address_says_nothing_about_the_connection(client, domain):
    """
    Una dirección rechazada no habla de la conexión.

    Adjudicárselo mandaría a habilitar una API que puede estar encendida hace
    meses, que es el peor final posible para una pantalla de diagnóstico: la
    persona va a arreglar algo que no estaba roto y vuelve al mismo lugar.
    """
    ask(domain, (IndexingErrorCode.URL_REJECTED, 400))

    assert card(client)['state'] == ConnectionState.INCONCLUSIVE


# --- Lo que la conclusión no puede hacer ------------------------------------


def test_the_latest_answer_wins_over_an_older_one(client, domain):
    """
    Lo que vale es la última respuesta, no la primera ni la mejor.

    Un permiso que se arregló tiene que dejar de informarse como roto, y uno que
    se rompió ayer no puede quedar tapado por un éxito de la semana pasada.
    """
    ask(domain, (GoogleErrorCode.PERMISSION_DENIED, 403))
    assert card(client)['state'] == ConnectionState.NOT_AUTHORIZED

    second = Url.objects.create(domain=domain, loc='https://ejemplo.test/b')
    batch = create_batch(domain, [second])
    batch.state = BatchState.QUEUED
    batch.save(update_fields=['state'])
    send_batch(batch, client=FakeIndexingClient())

    props = card(client)
    assert props['state'] == ConnectionState.WORKING
    assert props['url'] == 'https://ejemplo.test/b'


def test_a_draft_nobody_sent_does_not_count_as_evidence(client, domain):
    """
    Un borrador no le dijo nada a Google, así que no puede contestar nada.

    Es el error que este servicio tiene que no cometer: contar filas en vez de
    respuestas dejaría «sin confirmar» convertido en «funciona» por el solo
    hecho de que alguien armó un lote y no lo mandó.
    """
    url = Url.objects.create(domain=domain, loc='https://ejemplo.test/a')
    create_batch(domain, [url])

    assert IndexingRequest.objects.filter(state=IndexingState.PENDING).count() == 1
    assert card(client)['state'] == ConnectionState.NEVER_ASKED


def test_without_a_credential_there_is_no_card_at_all(client):
    """
    Sin clave, lo que falta es un paso anterior.

    La Indexing API firma con la misma credencial que Search Console. Dibujar la
    tarjeta igual la mostraría rota por algo que no se arregla ahí.
    """
    account = create_account(email='sin-clave@ejemplo.test')
    client.force_login(account)

    assert card(client) is None


def test_the_card_carries_the_project_so_the_link_opens_the_right_one(client, domain):
    """
    El proyecto viaja desde el servidor y no lo deduce la pantalla.

    El enlace a la biblioteca de Google Cloud lo necesita: una pantalla que lo
    adivine manda a habilitar la API en el proyecto equivocado.
    """
    from tests.factories import TEST_KEY

    assert card(client)['project_id'] == TEST_KEY['project_id']


# --- Y el recorrido guiado dice lo mismo ------------------------------------


def test_the_tour_and_the_card_never_contradict_each_other(client, domain):
    """
    Las dos pantallas leen **la misma** función.

    Es el motivo por el que `connection_state()` vive en el servicio y no
    escrito en cada vista: con dos copias, alguien agrega un código en una y la
    tarjeta y el recorrido empiezan a decir cosas distintas de la misma cuenta.
    """
    from apps.onboarding.steps import ENABLE_INDEXING_API, CheckState

    ask(domain, (GoogleErrorCode.API_NOT_ENABLED, 403))

    tour = client.get('/search-console/onboarding', HTTP_X_INERTIA='true').json()['props']
    step = next(
        item for item in tour['onboarding']['steps'] if item['code'] == ENABLE_INDEXING_API
    )

    assert card(client)['state'] == ConnectionState.API_NOT_ENABLED
    assert step['state'] == CheckState.MISSING
    assert step['reason'] == 'INDEXING_API_NOT_ENABLED'


def test_connection_state_reads_only_what_google_answered(domain):
    """
    El servicio mismo, sin pantalla de por medio.

    Un pedido que quedó `REMOVED` —alguien lo sacó del lote antes de correrlo—
    nunca salió, así que no puede pesar en la conclusión.
    """
    url = Url.objects.create(domain=domain, loc='https://ejemplo.test/a')
    batch = create_batch(domain, [url])
    IndexingRequest.objects.filter(batch=batch).update(state=IndexingState.REMOVED)

    assert connection_state(domain.account).state == ConnectionState.NEVER_ASKED
