"""
Dar de baja el sitio: lo que deja de pasar, y lo que no se pierde.

La interfaz admite un solo sitio por cuenta, así que cambiar de sitio es dar de
baja el que hay y conectar otro. **Casi todo lo que esa acción tiene que apagar
es invisible**: un dado de baja que se sigue revalidando contra Google no se ve
en ninguna pantalla —el sitio ya no aparece— y sin embargo gasta cuota de
verificación en cada ciclo y puede emitir un aviso sobre algo que nadie está
mirando.

Por eso las pruebas de este archivo miran las tareas de fondo, el tope del plan y
las guardas de servicio, y no las vistas: el defecto que importa no llega nunca a
una pantalla desde donde alguien pueda notarlo.
"""

import pytest

from apps.billing import limits
from apps.core.errors import DomainNotOperational, ValidationFailed
from apps.domains import tasks
from apps.domains.models import AccessState
from apps.domains.services import (
    active_domain,
    active_domains,
    active_owned_domain,
    create_domain,
    deactivate_domain,
    ensure_can_connect_another,
    ensure_operational,
    owned_domain,
)
from tests.factories import create_account, create_credential
from tests.factories import create_domain as make_domain


@pytest.mark.django_db
def test_a_deactivated_site_leaves_the_active_list():
    account = create_account()
    kept = make_domain(account, hostname='queda.test')
    dropped = make_domain(account, hostname='se-va.test')

    deactivate_domain(dropped)

    assert list(active_domains(account)) == [kept]
    assert active_domain(account) == kept


@pytest.mark.django_db
def test_the_row_survives_with_its_history():
    """
    Dar de baja no borra: la cobertura y los lotes del sitio son historia real.

    La fila sigue estando y `owned_domain()` —el que usa la API— la encuentra.
    Lo que cambia es que deja de considerarse activa.
    """
    account = create_account()
    domain = make_domain(account)

    deactivate_domain(domain)

    assert owned_domain(account, domain.id) is not None
    assert owned_domain(account, domain.id).deactivated_at is not None


@pytest.mark.django_db
def test_deactivating_twice_keeps_the_first_date():
    """
    La primera fecha es la que dice cuándo dejó de trabajarse.

    Pisarla con la del segundo clic contaría mal el tiempo que estuvo detenido.
    """
    account = create_account()
    domain = make_domain(account)

    first = deactivate_domain(domain).deactivated_at
    again = deactivate_domain(domain).deactivated_at

    assert first == again


@pytest.mark.django_db
def test_the_screens_stop_finding_it(settings):
    """
    Las cuatro pantallas que cuelgan de un sitio contestan 404.

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

    assert active_owned_domain(account, domain.id) is not None

    deactivate_domain(domain)

    assert active_owned_domain(account, domain.id) is None


@pytest.mark.django_db
def test_connecting_the_same_site_again_revives_its_row():
    """
    Volver a conectar el mismo sitio reactiva su fila, y **tiene que hacerlo**.

    `one_property_per_account` es una restricción de base: crear otra fila con el
    mismo `property_uri` reventaría con `IntegrityError`. Que además recupere la
    cobertura y los lotes es la consecuencia buena de no tener alternativa.
    """
    account = create_account()
    create_credential(account)
    domain = make_domain(account, hostname='ejemplo.test')
    deactivate_domain(domain)

    revived = create_domain(account, hostname='ejemplo.test', property_type='DOMAIN')

    assert revived.id == domain.id
    assert revived.deactivated_at is None


@pytest.mark.django_db
def test_reviving_keeps_the_access_it_had():
    """
    Darlo de baja acá no le cambió el permiso en Search Console.

    Reiniciar el estado fabricaría un «sin comprobar» que no es cierto.
    """
    account = create_account()
    create_credential(account)
    domain = make_domain(account, hostname='ejemplo.test', access_state=AccessState.OPERATIONAL)
    deactivate_domain(domain)

    revived = create_domain(account, hostname='ejemplo.test', property_type='DOMAIN')

    assert revived.access_state == AccessState.OPERATIONAL


@pytest.mark.django_db
def test_a_deactivated_site_cannot_be_worked_on():
    """
    `ensure_operational()` es la puerta por la que pasan lotes e inspecciones.

    Incluidas las de la API, que no consulta ninguna vista: sin esta guarda
    alcanzaría con conservar un identificador para seguir trabajando sobre algo
    que la cuenta dio de baja.
    """
    account = create_account()
    create_credential(account)
    domain = make_domain(account, access_state=AccessState.OPERATIONAL)

    ensure_operational(domain)  # activo y operativo: no levanta nada

    deactivate_domain(domain)

    with pytest.raises(DomainNotOperational):
        ensure_operational(domain)


@pytest.mark.django_db
def test_the_scheduled_revalidation_skips_it(monkeypatch):
    """
    **La fuga que nada en pantalla delataría.**

    Un sitio dado de baja que se sigue revalidando gasta cuota de verificación en
    cada ciclo y puede emitir un aviso de «acceso perdido» sobre algo que ya no
    aparece en ninguna vista. `access_state` describe lo que pasa en Search
    Console —puede seguir siendo operativo ahí—, así que filtrar por estado no
    alcanza.
    """
    account = create_account()
    kept = make_domain(account, hostname='queda.test', access_state=AccessState.OPERATIONAL)
    dropped = make_domain(account, hostname='se-va.test', access_state=AccessState.OPERATIONAL)
    deactivate_domain(dropped)

    queued = []
    monkeypatch.setattr(
        tasks.revalidate_one_access, 'delay', lambda domain_id: queued.append(domain_id)
    )

    result = tasks.revalidate_all_access()

    assert queued == [str(kept.id)]
    assert result == {'queued': 1}


@pytest.mark.django_db
def test_deactivating_frees_the_plan_slot(settings):
    """
    `max_domains` es cuántos sitios se pueden tener **a la vez**.

    Contando las filas sin más, una cuenta con `max_domains = 1` —el plan que el
    producto de un solo sitio vuelve normal— no podría cambiar de sitio nunca: el
    dado de baja seguiría ocupando el único lugar.

    Se prueba con `BILLING_ENABLED` encendido a propósito: apagado, `check()`
    autoriza siempre antes de llegar al conteo, y el test pasaría en verde sin
    ejercitar la línea que importa.
    """
    account = create_account()
    domain = make_domain(account, hostname='uno.test')

    settings.BILLING_ENABLED = True
    account.plan.max_domains = 1
    account.plan.save()

    assert not limits.check(account, limits.Action.ADD_DOMAIN)

    deactivate_domain(domain)

    assert limits.check(account, limits.Action.ADD_DOMAIN)


@pytest.mark.django_db
def test_the_interface_refuses_a_second_site():
    """
    La regla es de la interfaz, no de la base.

    El backend sigue admitiendo varios dominios por cuenta y quien integra por la
    API v1 puede seguir dando de alta los que quiera; lo que admite uno solo es
    lo que se ve. Por eso la guarda es una función aparte que llaman las dos
    puertas de la interfaz —la conexión y el recorrido— y no vive dentro de
    `create_domain()`.
    """
    account = create_account()
    make_domain(account, hostname='primero.test')

    with pytest.raises(ValidationFailed) as failure:
        ensure_can_connect_another(account)

    assert failure.value.details['reason'] == 'SITE_ALREADY_CONNECTED'
    assert failure.value.details['params']['hostname'] == 'primero.test'


@pytest.mark.django_db
def test_with_the_site_disconnected_another_one_can_come_in():
    account = create_account()
    domain = make_domain(account, hostname='primero.test')

    deactivate_domain(domain)

    ensure_can_connect_another(account)  # no levanta nada


@pytest.mark.django_db
def test_the_api_still_creates_a_second_site():
    """
    La contracara del test anterior: el backend **no** se cerró.

    Si algún día esta prueba falla es porque la guarda de la interfaz se filtró
    a `create_domain()`, y eso cambiaría el contrato de la API v1 sin que nadie
    lo haya decidido.
    """
    account = create_account()
    create_credential(account)
    make_domain(account, hostname='primero.test')

    second = create_domain(account, hostname='segundo.test', property_type='DOMAIN')

    assert second.deactivated_at is None
    assert active_domains(account).count() == 2
