"""
El alta de un dominio, que es donde se decide la forma de la propiedad.

Elegir mal entre propiedad de dominio y prefijo de URL devuelve «la propiedad no
existe» aunque el sitio esté perfectamente dado de alta en Search Console. Es el
error más desconcertante del producto, y por eso el alta es una pantalla propia
en vez de un panel que se despliega sobre el listado: las dos opciones tienen
que verse a la vez para poder compararse.
"""

import pytest

from apps.core.errors import ValidationFailed
from apps.credentials.models import CredentialStatus
from apps.domains.models import Domain, PropertyType
from apps.domains.services import create_domain as register_domain
from tests.factories import create_account, create_credential, create_domain

pytestmark = pytest.mark.django_db


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


class TestTheScreen:
    def test_without_a_credential_the_screen_is_not_offered(self, client):
        """
        Un formulario que no puede terminar es peor que no ofrecer el formulario.

        Sin credencial comprobada el alta falla siempre, así que se va a
        resolverlo en vez de dejar completar campos para nada.
        """
        client.force_login(create_account())

        response = client.get('/search-console/domains/new')

        assert response.status_code == 302
        assert response.headers['Location'] == '/search-console/settings'

    def test_an_unverified_credential_is_not_enough_either(self, client):
        account = create_account()
        create_credential(account, status=CredentialStatus.UNVERIFIED)
        client.force_login(account)

        response = client.get('/search-console/domains/new')

        assert response.headers['Location'] == '/search-console/settings'

    def test_the_screen_brings_the_service_account_address(self, client, ready_account):
        """
        La dirección viaja **antes** del alta y no después.

        El paso siguiente al alta es autorizarla en Search Console. Tenerla en
        pantalla desde ahora ahorra un viaje de ida y vuelta.
        """
        response = client.get('/search-console/domains/new', HTTP_X_INERTIA='true')
        props = response.json()['props']

        assert props['client_email'].endswith('.iam.gserviceaccount.com')
        assert {kind['value'] for kind in props['property_types']} == set(PropertyType.values)

    def test_without_a_session_it_sends_to_the_login(self, client):
        response = client.get('/search-console/domains/new')

        assert response.headers['Location'].startswith('/login')


class TestTheCreation:
    def test_a_valid_domain_ends_up_created(self, client, ready_account):
        response = client.post(
            '/search-console/domains/new', {'hostname': 'ejemplo.com', 'property_type': 'DOMAIN'}
        )

        assert response.status_code == 302
        # A la conexión, que es donde el sitio se muestra ahora. El listado, que
        # era el destino de antes, ya no tiene dirección.
        assert response.headers['Location'] == '/search-console/settings'
        domain = Domain.objects.get(account=ready_account, hostname='ejemplo.com')
        assert domain.property_uri == 'sc-domain:ejemplo.com'

    def test_the_chosen_form_rules_over_the_identifier(self, client, ready_account):
        """Las dos formas no son intercambiables y el alta las respeta tal cual."""
        client.post(
            '/search-console/domains/new',
            {'hostname': 'ejemplo.com', 'property_type': 'URL_PREFIX'},
        )

        domain = Domain.objects.get(account=ready_account, hostname='ejemplo.com')
        assert domain.property_uri == 'https://ejemplo.com/'

    def test_what_is_written_is_normalized_before_being_saved(self, client, ready_account):
        """`https://Ejemplo.com/` y `ejemplo.com` son el mismo sitio."""
        client.post(
            '/search-console/domains/new',
            {'hostname': 'https://Ejemplo.com/', 'property_type': 'DOMAIN'},
        )

        assert Domain.objects.filter(account=ready_account, hostname='ejemplo.com').exists()

    def test_a_validation_error_returns_to_the_form(self, client, ready_account):
        response = client.post(
            '/search-console/domains/new', {'hostname': '', 'property_type': 'DOMAIN'}
        )

        assert response.headers['Location'] == '/search-console/domains/new'
        assert not Domain.objects.filter(account=ready_account).exists()

    def test_a_duplicate_brings_the_identifier_of_the_one_that_already_exists(self, ready_account):
        """
        Sin el identificador, «ya está dado de alta» obliga a buscarlo a mano.

        **Se ejercita contra el servicio y no contra la pantalla**, y no es un
        atajo: con un sitio ya conectado, el alta suelta rechaza antes de llegar
        acá —la interfaz admite uno solo—. La regla del duplicado no era de esa
        pantalla, es de `create_domain()`, y la API v1 la sigue usando tal cual.
        Probarla donde vive es lo que la mantiene defendida ahora que la pantalla
        no se alcanza.
        """
        existing = create_domain(ready_account, hostname='ejemplo.com')

        with pytest.raises(ValidationFailed) as failure:
            register_domain(ready_account, hostname='ejemplo.com', property_type='DOMAIN')

        # El identificador viaja como dato del error: es con lo que la pantalla
        # arma el enlace al dominio que ya existe, en vez de pedir que lo busque.
        assert failure.value.details['duplicate_id'] == str(existing.id)

    def test_the_same_domain_with_the_other_form_is_not_a_duplicate(self, ready_account):
        """
        Son dos propiedades distintas en Search Console, y se monitorean aparte.

        Tratarlas como la misma impediría dar de alta un caso perfectamente
        válido: el mismo sitio declarado de las dos maneras. Contra el servicio
        por el mismo motivo que el test de arriba, y además porque la regla es la
        que hace que la API pueda tener las dos.
        """
        create_domain(ready_account, hostname='ejemplo.com')

        register_domain(ready_account, hostname='ejemplo.com', property_type='URL_PREFIX')

        assert Domain.objects.filter(account=ready_account, hostname='ejemplo.com').count() == 2

    def test_the_domain_is_born_without_confirmed_access(self, client, ready_account):
        """
        Dar de alta no es empezar a monitorear (RT-05).

        Nace esperando acceso porque falta autorizar la cuenta de servicio en
        Search Console, y la pantalla no puede sugerir lo contrario.
        """
        client.post(
            '/search-console/domains/new', {'hostname': 'ejemplo.com', 'property_type': 'DOMAIN'}
        )

        domain = Domain.objects.get(account=ready_account, hostname='ejemplo.com')
        assert not domain.is_operational

    def test_the_company_does_not_connect_the_same_domain_twice(self, client, ready_account):
        other = create_account(email='ajena@ejemplo.test')
        create_domain(other, hostname='ajeno.com')

        client.post(
            '/search-console/domains/new', {'hostname': 'ajeno.com', 'property_type': 'DOMAIN'}
        )

        assert Domain.objects.filter(hostname='ajeno.com').count() == 1
