"""
Abandonar el recorrido a mitad y volver más tarde.

La trampa del dominio es concreta y conocida: al retomar, mostrar el formulario
de alta vacío y terminar con un segundo «ejemplo.test» que se reparte la cuota
con el primero. Por eso el progreso guarda el contexto —los identificadores de lo
que se fue creando— y por eso cada paso lo reencuentra antes de crear nada
(FR-019).

Lo que se prueba acá es esa idempotencia paso por paso: repetir cada comprobación
tiene que dejar exactamente una fila de cada cosa, y retomar tiene que abrir donde
se había quedado con los objetos ya creados nombrados, no un asistente en blanco.
"""

import json
from pathlib import Path

import httpx
import pytest
from django.conf import settings

from apps.credentials.models import Credential, GoogleProject
from apps.domains.models import Domain
from apps.jobs.models import Batch
from apps.onboarding import services
from apps.onboarding.models import OnboardingProgress
from apps.sitemaps.models import Sitemap
from tests.doubles import FakeClient, response
from tests.factories import TEST_KEY, create_account

pytestmark = pytest.mark.django_db

FIXTURES = Path(settings.BASE_DIR) / 'tests' / 'fixtures' / 'sitemaps'


@pytest.fixture
def account():
    return create_account()


@pytest.fixture
def site() -> httpx.Client:
    files = {
        'https://ejemplo.test/sitemap.xml': (FIXTURES / 'indice.xml').read_bytes(),
        'https://ejemplo.test/sitemap-1.xml': (FIXTURES / 'urlset.xml').read_bytes(),
        'https://ejemplo.test/sitemap-2.xml': (
            FIXTURES / 'urlset_sin_espacio_de_nombres.xml'
        ).read_bytes(),
    }

    def respond(request: httpx.Request) -> httpx.Response:
        body = files.get(str(request.url))
        if body is None:
            return httpx.Response(404, text='no encontrado')
        return httpx.Response(200, content=body)

    return httpx.Client(transport=httpx.MockTransport(respond))


def step(data: dict, code: str) -> dict:
    return next(p for p in data['steps'] if p['code'] == code)


def up_to_the_domain(account):
    """Deja el recorrido con proyecto, credencial comprobada y dominio operativo."""
    services.verify(account, 'GOOGLE_PROJECT', {'project_id': TEST_KEY['project_id']})
    services.verify(
        account,
        'SERVICE_ACCOUNT_KEY',
        {'project_id': TEST_KEY['project_id'], 'key_file': json.dumps(TEST_KEY)},
        client=FakeClient(list_sites=response('sites_list')),
    )
    return services.verify(
        account,
        'ADD_DOMAIN',
        {'hostname': 'ejemplo.test', 'property_type': 'DOMAIN'},
        client=FakeClient(get_site={}),
    )


# --- Retomar en el mismo punto ----------------------------------------------


def test_coming_back_later_resumes_at_the_same_step(account):
    up_to_the_domain(account)

    # Otra visita, como quien vuelve al día siguiente: sólo se consulta.
    data = services.state(account)

    assert data['current_step'] == 'ADD_SITEMAP'
    assert data['completed_steps'] == [
        'GOOGLE_PROJECT',
        'ENABLE_API',
        'SERVICE_ACCOUNT_KEY',
        'AUTHORIZE_PROPERTY',
        'ADD_DOMAIN',
    ]
    assert all(step(data, code)['completed_at'] for code in data['completed_steps'])


def test_on_resuming_the_already_created_objects_come_named(account):
    """
    Es lo que evita el formulario en blanco.

    Quien vuelve tiene que leer «ejemplo.test» y no volver a escribirlo: la
    segunda escritura es la que termina en dos dominios iguales.
    """
    up_to_the_domain(account)

    created = services.state(account)['created']

    assert created['project_id'] == TEST_KEY['project_id']
    assert created['client_email'] == TEST_KEY['client_email']
    assert created['hostname'] == 'ejemplo.test'
    assert created['property_uri'] == 'sc-domain:ejemplo.test'
    assert step(services.state(account), 'ADD_DOMAIN')['evidence']['hostname'] == 'ejemplo.test'


def test_the_progress_is_a_single_one_per_account(account):
    services.state(account)
    services.state(account)
    services.dismiss(account)

    assert OnboardingProgress.objects.filter(account=account).count() == 1


# --- Retomar no duplica -----------------------------------------------------


def test_repeating_the_first_step_does_not_declare_a_second_project(account):
    services.verify(account, 'GOOGLE_PROJECT', {'project_id': TEST_KEY['project_id']})
    services.verify(account, 'GOOGLE_PROJECT', {'project_id': TEST_KEY['project_id']})

    assert GoogleProject.objects.filter(account=account).count() == 1


def test_verifying_the_key_again_without_uploading_another_does_not_create_a_second_one(account):
    """
    Comprobar es preguntar, no volver a cargar.

    Cada archivo subido sí crea una fila nueva —así se puede explicar después qué
    clave se usó y hasta cuándo—, pero el botón «Comprobar» no sube nada.
    """
    up_to_the_domain(account)

    services.verify(
        account, 'SERVICE_ACCOUNT_KEY', {}, client=FakeClient(list_sites=response('sites_list'))
    )

    assert Credential.objects.filter(account=account).count() == 1


def test_repeating_the_domain_creation_with_the_same_site_reuses_the_one_already_there(account):
    up_to_the_domain(account)
    first = Domain.objects.get(account=account)

    data = services.verify(
        account,
        'ADD_DOMAIN',
        {'hostname': 'ejemplo.test', 'property_type': 'DOMAIN'},
        client=FakeClient(get_site={}),
    )

    assert Domain.objects.filter(account=account).count() == 1
    assert step(data, 'ADD_DOMAIN')['evidence']['domain_id'] == str(first.id)


def test_the_same_site_written_differently_does_not_create_another_one_either(account):
    """
    «https://Ejemplo.test/» y «ejemplo.test» son el mismo sitio.

    La normalización es del alta de dominios y el recorrido la reutiliza: sin
    eso, retomar escribiendo la dirección completa dejaría dos filas que se pisan
    la cuota entre sí.
    """
    up_to_the_domain(account)

    services.verify(
        account,
        'ADD_DOMAIN',
        {'hostname': 'https://Ejemplo.test/', 'property_type': 'DOMAIN'},
        client=FakeClient(get_site={}),
    )

    assert Domain.objects.filter(account=account).count() == 1


def test_verifying_the_domain_without_typing_anything_uses_the_one_from_the_context(account):
    up_to_the_domain(account)

    data = services.verify(account, 'ADD_DOMAIN', {}, client=FakeClient(get_site={}))

    assert Domain.objects.filter(account=account).count() == 1
    assert step(data, 'ADD_DOMAIN')['state'] == 'PASSED'


def test_registering_the_same_sitemap_twice_leaves_only_one(account, site):
    up_to_the_domain(account)

    services.verify(
        account, 'ADD_SITEMAP', {'location': 'https://ejemplo.test/sitemap.xml'}, http=site
    )
    services.verify(
        account, 'ADD_SITEMAP', {'location': 'https://ejemplo.test/sitemap.xml'}, http=site
    )

    assert Sitemap.objects.filter(domain__account=account).count() == 1


def test_verifying_the_sync_again_does_not_queue_a_second_batch(account, site):
    """
    Un lote gasta cuota del sitio: relanzarlo por mirar la pantalla la regalaría.

    La segunda comprobación informa sobre el lote que ya corrió, con su estado.
    """
    up_to_the_domain(account)
    services.verify(
        account, 'ADD_SITEMAP', {'location': 'https://ejemplo.test/sitemap.xml'}, http=site
    )
    first = services.verify(
        account, 'FIRST_BATCH', {}, client=FakeClient(submit_sitemap={}), http=site
    )

    second = services.verify(
        account, 'FIRST_BATCH', {}, client=FakeClient(submit_sitemap={}), http=site
    )

    assert Batch.objects.filter(domain__account=account).count() == 1
    assert second['created']['batch_id'] == first['created']['batch_id']


def test_going_through_everything_twice_leaves_nothing_duplicated(account, site):
    for _ in range(2):
        up_to_the_domain(account)
        services.verify(
            account, 'ADD_SITEMAP', {'location': 'https://ejemplo.test/sitemap.xml'}, http=site
        )
        services.verify(account, 'FIRST_BATCH', {}, client=FakeClient(submit_sitemap={}), http=site)

    assert GoogleProject.objects.filter(account=account).count() == 1
    assert Domain.objects.filter(account=account).count() == 1
    assert Sitemap.objects.filter(domain__account=account, source='DECLARED').count() == 1
    assert Batch.objects.filter(domain__account=account).count() == 1
