"""
Revalidación periódica del acceso a las propiedades.

Perder el acceso a una propiedad no produce ningún error: Search Console deja de
contestar y el dominio se queda con el último dato que tuvo. Sin esta tarea, la
pérdida se descubre recién cuando alguien abre la ficha y aprieta «Comprobar
acceso» —o peor, cuando nota que la cobertura lleva semanas sin moverse—.

Corre antes que la sincronización y que la inspección del día a propósito: si el
acceso se perdió, las dos siguientes no tienen nada que hacer sobre ese dominio,
y enterarse primero evita gastar cupo en llamadas que van a fallar.
"""

import logging

from celery import shared_task

from apps.core.errors import ApiError
from apps.domains.models import AccessState, Domain
from apps.domains.services import check_access

logger = logging.getLogger(__name__)

#: Qué estados vale la pena revalidar.
#:
#: Los operativos, para enterarnos si dejaron de serlo. Y los que esperan
#: autorización, porque ahí la comprobación trae la buena noticia: alguien
#: autorizó la cuenta de servicio en Search Console y nadie volvió a la pantalla
#: a apretar el botón.
#:
#: Revocado y suspendido quedan afuera: son decisiones administrativas y la
#: máquina de estados ni siquiera admite el paso a operativo desde ahí, así que
#: comprobarlos sería gastar una llamada para confirmar lo que ya sabemos.
REVALIDATED = (AccessState.OPERATIONAL, AccessState.AWAITING_ACCESS)


@shared_task(name='apps.domains.tasks.revalidate_all_access')
def revalidate_all_access() -> dict:
    """
    Encola una comprobación por dominio.

    Una tarea por dominio y no un recorrido en una sola: una propiedad que tarda
    en contestar no tiene por qué demorar a las demás, y un fallo en la tercera
    no puede dejar sin comprobar a la cuarta.

    Los dados de baja quedan afuera. Es la fuga que más barato pasa inadvertida:
    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 de «acceso
    perdido» sobre algo que la cuenta dejó de mirar.
    """
    domain_ids = (
        Domain.objects.filter(access_state__in=REVALIDATED, deactivated_at__isnull=True)
        .values_list('id', flat=True)
    )
    for domain_id in domain_ids:
        revalidate_one_access.delay(str(domain_id))

    return {'queued': len(domain_ids)}


@shared_task(name='apps.domains.tasks.revalidate_one_access', bind=True)
def revalidate_one_access(self, domain_id: str) -> dict:
    """
    Le pregunta a Google si seguimos teniendo acceso, y registra la respuesta.

    El aviso de acceso perdido lo deja `check_access`, que es donde ocurre el
    hecho: ponerlo acá haría que comprobar desde la pantalla no avisara y
    comprobar por esta vía sí, para el mismo cambio de estado.

    Los errores previstos no se reintentan. Que Google no responda hoy se
    resuelve mañana con la corrida siguiente, y reintentar en el momento sólo
    gastaría la reserva de comprobaciones de la cuenta.
    """
    domain = Domain.objects.filter(id=domain_id).select_related('account').first()
    if domain is None:
        return {'skipped': 'el dominio ya no existe'}

    before = domain.access_state

    try:
        domain = check_access(domain)
    except ApiError as exc:
        logger.warning('No se revalidó %s: %s', domain.hostname, exc.message)
        return {'skipped': exc.code, 'detail': exc.message}

    return {'domain_id': str(domain.id), 'before': before, 'after': domain.access_state}
