"""
En qué orden se consultan las URLs cuando el cupo no alcanza (T083).

El cupo diario casi nunca alcanza para el sitio entero, así que el orden **es**
la política del producto: decide qué parte del sitio tiene dato fresco y cuál
queda con una foto vieja. Son tres criterios, en este orden:

1. Las que nunca se consultaron. Son las únicas sobre las que el producto no
   puede decir nada, y ése es el vacío que hay que cerrar primero.
2. Las problemáticas. Donde Google informó un error o una exclusión es donde la
   respuesta de mañana puede ser distinta.
3. Las más viejas.

El puntaje va antes que la antigüedad a propósito. Con el orden anterior —fecha
primero— el puntaje sólo desempataba entre URLs consultadas en el mismo instante
exacto, así que el criterio existía escrito y no se aplicaba nunca.
"""

from datetime import timedelta

import pytest
from django.utils import timezone

from apps.coverage.services import pending_urls, priority_score, record_inspection
from apps.sitemaps.models import CoverageState, Url
from tests.doubles import response
from tests.factories import create_account, create_credential, create_domain

pytestmark = pytest.mark.django_db


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


def add(domain, path: str, *, state: str, days_ago: int | None) -> Url:
    """Una URL con el estado y la antigüedad que la prueba necesita."""
    return Url.objects.create(
        domain=domain,
        loc=f'https://ejemplo.test/{path}',
        in_sitemap=True,
        coverage_state=state,
        priority_score=priority_score(state),
        last_checked_at=(None if days_ago is None else timezone.now() - timedelta(days=days_ago)),
    )


def order_of(domain) -> list[str]:
    return [url.loc.rsplit('/', 1)[-1] for url in pending_urls(domain)]


def test_the_ones_never_checked_go_first(domain):
    add(domain, 'con-error', state=CoverageState.FETCH_ERROR, days_ago=0)
    add(domain, 'sin-consultar', state=CoverageState.UNKNOWN, days_ago=None)

    assert order_of(domain)[0] == 'sin-consultar'


def test_a_problematic_url_goes_before_a_stable_one_checked_at_the_same_time(domain):
    add(domain, 'indexada', state=CoverageState.INDEXED, days_ago=3)
    add(domain, 'con-error', state=CoverageState.FETCH_ERROR, days_ago=3)
    add(domain, 'sin-indexar', state=CoverageState.CRAWLED_NOT_INDEXED, days_ago=3)

    assert order_of(domain) == ['con-error', 'sin-indexar', 'indexada']


def test_between_two_equally_problematic_ones_the_oldest_goes_first(domain):
    add(domain, 'vieja', state=CoverageState.FETCH_ERROR, days_ago=9)
    add(domain, 'reciente', state=CoverageState.FETCH_ERROR, days_ago=1)

    assert order_of(domain) == ['vieja', 'reciente']


def test_what_the_site_decided_does_not_beat_what_google_could_change(domain):
    """
    Un `noindex` lo puso el propio sitio: no va a cambiar hasta que el sitio
    cambie. Una página rastreada y todavía sin indexar está en tránsito.
    """
    add(domain, 'noindex', state=CoverageState.EXCLUDED_NOINDEX, days_ago=2)
    add(domain, 'en-transito', state=CoverageState.DISCOVERED_NOT_INDEXED, days_ago=2)

    assert order_of(domain) == ['en-transito', 'noindex']


def test_the_score_is_written_when_the_inspection_is_recorded(domain):
    url = add(domain, 'uno', state=CoverageState.UNKNOWN, days_ago=None)

    record_inspection(url, response('url_inspection_indexada'))
    url.refresh_from_db()

    assert url.coverage_state == CoverageState.INDEXED
    assert url.priority_score == priority_score(CoverageState.INDEXED)


def test_the_score_describes_today_and_does_not_accumulate(domain):
    """
    Se recalcula en cada lectura. Una URL que se arregla deja de ser prioritaria
    en la corrida siguiente, sin que nadie tenga que limpiarle nada.
    """
    url = add(domain, 'uno', state=CoverageState.FETCH_ERROR, days_ago=1)
    assert url.priority_score > 0

    record_inspection(url, response('url_inspection_indexada'))
    url.refresh_from_db()

    assert url.priority_score == 0
