"""
Conectar el sitio, desde la pantalla que se llama «conexión».

Antes esta pantalla terminaba en «la clave sirve» y todavía faltaba un paso —dar
de alta la propiedad— que vivía en otra vista, alcanzable sólo por el menú. Es lo
que hacía que el estado más frecuente fuera «falta autorizar»: la conexión no
dejaba conectado.

Lo que se fija acá es el recorrido entero de esa mitad: que el formulario aparezca
recién cuando la clave sirve, que conectar deje el sitio a la vista, que no entre
un segundo, y que darlo de baja devuelva la pantalla a su estado inicial —con el
cupo liberado— sin perder nada de lo que el sitio produjo.
"""

import pytest

from apps.domains.models import AccessState, Domain
from apps.domains.services import deactivate_domain
from tests.factories import create_account, create_credential
from tests.factories import create_domain as make_domain

pytestmark = pytest.mark.django_db

SETTINGS = '/search-console/settings'
CONNECT = '/search-console/domains/connect'


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


@pytest.fixture(autouse=True)
def without_google(monkeypatch):
    """
    La comprobación de acceso no sale a la red.

    Se hace inerte en vez de devolver un resultado: lo que estos tests miran es
    el alta y la baja, y un doble que además dictara el estado de acceso los
    ataría a una decisión que se prueba en otro lado.
    """
    monkeypatch.setattr('apps.domains.views.check_access', lambda domain, **_: domain)


def screen(client) -> dict:
    return client.get(SETTINGS, HTTP_X_INERTIA='true').json()['props']


class TestBeforeConnecting:
    def test_the_screen_arrives_without_a_site(self, client, account):
        assert screen(client)['site'] is None

    def test_it_brings_the_property_shapes_to_choose_from(self, client, account):
        """
        Elegir la forma equivocada devuelve «la propiedad no existe» aunque el
        sitio esté perfectamente dado de alta, así que las dos opciones tienen
        que llegar para poder compararse.
        """
        shapes = [shape['value'] for shape in screen(client)['property_types']]

        assert shapes == ['DOMAIN', 'URL_PREFIX']


class TestConnecting:
    def test_the_site_ends_up_connected_and_visible(self, client, account):
        response = client.post(CONNECT, {'hostname': 'ejemplo.test', 'property_type': 'DOMAIN'})

        assert response.status_code == 302
        assert response.url == SETTINGS

        site = screen(client)['site']
        assert site['hostname'] == 'ejemplo.test'
        assert site['access_state'] == AccessState.AWAITING_ACCESS

    def test_a_rejection_lands_back_on_the_connection(self, client, account):
        """
        El resultado de un envío tiene que volver a la pantalla desde la que se
        hizo (RT-11).

        El alta suelta está fuera del menú: mandar ahí un error de validación
        dejaría a la persona en una pantalla que ya no forma parte del recorrido,
        con el formulario vacío y sin saber cómo volver.
        """
        response = client.post(CONNECT, {'hostname': 'no es un dominio', 'property_type': 'DOMAIN'})

        assert response.url == SETTINGS
        assert not Domain.objects.filter(account=account).exists()

    def test_a_second_site_does_not_get_in(self, client, account):
        make_domain(account, hostname='primero.test')

        client.post(CONNECT, {'hostname': 'segundo.test', 'property_type': 'DOMAIN'})

        assert not Domain.objects.filter(account=account, hostname='segundo.test').exists()
        assert screen(client)['site']['hostname'] == 'primero.test'

    def test_the_rejection_says_which_site_is_in_the_way(self, client, account):
        """
        El error nombra el sitio conectado, no dice «ya hay uno».

        Sin el nombre, quien lo lee tiene que ir a buscar cuál es para decidir si
        lo da de baja, y eso es justo lo que la pantalla ya tiene a la vista.
        """
        make_domain(account, hostname='primero.test')

        client.post(CONNECT, {'hostname': 'segundo.test', 'property_type': 'DOMAIN'})
        errors = screen(client)['errors']

        assert errors['hostname']['code'] == 'SITE_ALREADY_CONNECTED'
        assert errors['hostname']['params']['hostname'] == 'primero.test'


class TestDisconnecting:
    def test_the_screen_goes_back_to_its_empty_state(self, client, account):
        domain = make_domain(account, hostname='ejemplo.test')

        response = client.post(f'/search-console/domains/{domain.id}/deactivate')

        assert response.url == SETTINGS
        assert screen(client)['site'] is None

    def test_nothing_is_deleted(self, client, account):
        """
        Dar de baja estampa una fecha. La fila sigue, con su cobertura y sus
        lotes: borrarla en cascada perdería meses de datos por lo que casi
        siempre es un cambio de opinión.
        """
        domain = make_domain(account, hostname='ejemplo.test')

        client.post(f'/search-console/domains/{domain.id}/deactivate')

        domain.refresh_from_db()
        assert domain.deactivated_at is not None

    def test_another_site_can_come_in_afterwards(self, client, account):
        domain = make_domain(account, hostname='primero.test')
        client.post(f'/search-console/domains/{domain.id}/deactivate')

        client.post(CONNECT, {'hostname': 'segundo.test', 'property_type': 'DOMAIN'})

        assert screen(client)['site']['hostname'] == 'segundo.test'

    def test_connecting_the_same_one_again_picks_up_its_row(self, client, account):
        """
        `one_property_per_account` es una restricción de base: sin la rama que
        reactiva, volver al mismo sitio reventaría con `IntegrityError`.
        """
        domain = make_domain(account, hostname='ejemplo.test')
        client.post(f'/search-console/domains/{domain.id}/deactivate')

        client.post(CONNECT, {'hostname': 'ejemplo.test', 'property_type': 'DOMAIN'})

        assert screen(client)['site']['id'] == str(domain.id)
        assert Domain.objects.filter(account=account).count() == 1

    def test_the_screens_that_hang_from_it_answer_404(self, client, account):
        """
        Un sitio dado de baja no existe para la interfaz.

        Sin esto alcanzaría con haber guardado una dirección para seguir mirando
        la cobertura de algo que la cuenta dio de baja.
        """
        domain = make_domain(account)
        deactivate_domain(domain)

        for address in (
            f'/search-console/domains/{domain.id}',
            f'/search-console/domains/{domain.id}/coverage',
            f'/search-console/domains/{domain.id}/sitemaps',
            f'/search-console/domains/{domain.id}/batches',
        ):
            assert client.get(address).status_code == 404, address
