"""
Rutas del proyecto.

Convención fijada en el plan: sin prefijos de agrupación artificial. Los
recursos de primer nivel viven en la raíz y sólo se anidan cuando la pertenencia
es real, como la cobertura de un dominio concreto. En particular el ingreso está
en `/login`, no bajo un prefijo de autenticación que no describe nada.

**`search-console/` no es una excepción a esa regla: es la regla aplicada.** La
plataforma dejó de ser una sola herramienta, así que esas pantallas ya no son
recursos de primer nivel: pertenecen a una herramienta concreta, y esa
pertenencia es tan real como la de la cobertura respecto de su dominio. Lo que
queda en la raíz es lo que efectivamente es de la plataforma y no de una
herramienta: el ingreso, la elección de herramienta, las sesiones y las claves
de API.

La API pública **no** lleva el prefijo. Está versionada desde el primer día para
no romper a quien ya la integró, y este producto se integra desde un pipeline de
despliegue: mover sus direcciones ahora rompería despliegues ajenos para
resolver un problema de navegación que la API no tiene, porque distingue por
recurso. Si algún día dos herramientas publican el mismo recurso, para eso está
`v2`.
"""

from django.contrib import admin
from django.urls import include, path

from apps.accounts import views as account_views
from apps.accounts import user_management as user_views
from apps.coverage import views as coverage_views
from apps.credentials import views as credential_views
from apps.domains import views as domain_views
from apps.indexing import views as indexing_views
from apps.jobs import views as job_views
from apps.notifications import views as notification_views
from apps.onboarding import views as onboarding_views
from apps.seo import views as seo_views
from apps.sitemaps import views as sitemap_views
from apps.web import views as web_views

handler404 = 'apps.core.not_found.page'
handler500 = 'apps.core.server_error.page'

#: Search Console: todo lo que pertenece a esa herramienta y a ninguna otra.
#:
#: Los nombres de ruta **no** llevan el prefijo. La dirección la lee una persona
#: y por eso dice de qué herramienta es; el nombre sólo lo usa `route()` y no lo
#: ve nadie, así que anteponerle algo obligaría a tocar treinta llamadas del
#: frontend a cambio de nada. El día que dos herramientas publiquen una pantalla
#: homónima, el test que compara esta lista con la unión de TypeScript va a
#: obligar a resolverlo bien.
search_console_patterns = [
    path('onboarding', onboarding_views.wizard, name='onboarding'),
    path('onboarding/dismiss', onboarding_views.dismiss, name='onboarding.dismiss'),
    # El paso viaja en la dirección y no en el cuerpo porque cada comprobación es
    # una operación distinta: así el registro del servidor dice cuál se pidió sin
    # tener que abrir el formulario.
    path('onboarding/<str:step>/verify', onboarding_views.verify, name='onboarding.verify'),
    path('settings', credential_views.settings_page, name='settings'),
    path('settings/credential', credential_views.upload, name='credential.upload'),
    path(
        'settings/credential/<uuid:credential_id>/verify',
        credential_views.verify,
        name='credential.verify',
    ),
    # El listado de dominios **no tiene ruta**. La interfaz trabaja contra un
    # solo sitio, así que una lista de varios ofrece una elección que no existe;
    # sacarla del menú no alcanzaba, porque la dirección seguía contestando y la
    # pantalla seguía llevando a la ficha de cualquier dominio de la cuenta.
    #
    # No se borró nada: la vista sigue en `apps/domains/views.py` (`index`) y su
    # pantalla en `frontend/pages/Domains/Index.tsx`. El día que la cuenta
    # necesite más de uno, se vuelve a agregar esta línea, su nombre a
    # `PUBLISHED_ROUTES` y su entrada al menú.
    #
    # Cuidado al reponerla: la API registra `name='domains'` en
    # `config/api_v1.py`, así que mientras esta línea no exista `reverse('domains')`
    # resuelve a `/api/v1/domains` sin avisar. Por eso no quedó ninguna llamada.
    #
    # Una sola dirección para la pantalla y su envío, como en el ingreso: dos
    # nombres para la misma ruta obligarían a recordar cuál usa cada lado.
    path('domains/new', domain_views.new, name='domain.new'),
    # El alta desde la conexión, que es donde vive mientras la interfaz admita un
    # solo sitio. Es otra dirección y no un parámetro de `domains/new` porque lo
    # que cambia es adónde vuelve el resultado, y eso se decide al rutear.
    path('domains/connect', domain_views.connect, name='domain.connect'),
    # La ficha y su edición comparten dirección, y el verbo las distingue: el
    # `PATCH` es el mismo que publica la API (FR-057). Va después de
    # `domains/new` porque el convertidor de UUID no acepta «new», pero el orden
    # deja explícito cuál gana si mañana el convertidor cambia.
    path('domains/<uuid:domain_id>', domain_views.show, name='domain.show'),
    path(
        'domains/<uuid:domain_id>/check-access',
        domain_views.check,
        name='domain.check',
    ),
    path(
        'domains/<uuid:domain_id>/deactivate',
        domain_views.deactivate,
        name='domain.deactivate',
    ),
    path(
        'domains/<uuid:domain_id>/coverage',
        coverage_views.index,
        name='coverage',
    ),
    path(
        'domains/<uuid:domain_id>/coverage/inspect',
        coverage_views.inspect,
        name='coverage.inspect',
    ),
    path(
        'domains/<uuid:domain_id>/coverage/export',
        coverage_views.export,
        name='coverage.export',
    ),
    # La descarga cuelga de la herramienta y no del dominio, como la ficha de un
    # lote: el identificador ya ubica el archivo, y repetir el dominio daría dos
    # direcciones para lo mismo y una de ellas equivocada.
    path('exports/<uuid:export_id>', coverage_views.download, name='export.download'),
    path('domains/<uuid:domain_id>/sitemaps', sitemap_views.index, name='sitemaps'),
    path('domains/<uuid:domain_id>/sitemaps/new', sitemap_views.create, name='sitemap.create'),
    path('domains/<uuid:domain_id>/sync', sitemap_views.sync, name='domain.sync'),
    # Los lotes se listan bajo su dominio porque siempre son de uno, y la ficha
    # cuelga de la herramienta porque el identificador ya la ubica sin ayuda:
    # obligar a repetir el dominio en la dirección de un lote sólo daría dos
    # maneras de escribir la misma página y una de ellas equivocada.
    path('domains/<uuid:domain_id>/batches', job_views.index, name='batches'),
    path('batches/<uuid:batch_id>', job_views.show, name='batch.show'),
    # La indexación cuelga de la herramienta por lo mismo que la ficha de un
    # lote: el identificador ya lo ubica. El alta sí cuelga del dominio, porque
    # ahí todavía no hay lote y lo que hay es una selección de sus URLs.
    path(
        'domains/<uuid:domain_id>/indexing',
        indexing_views.create,
        name='indexing.create',
    ),
    path('indexing/<uuid:batch_id>', indexing_views.show, name='indexing.batch'),
    path('indexing/<uuid:batch_id>/run', indexing_views.run, name='indexing.run'),
    path('indexing/<uuid:batch_id>/resume', indexing_views.resume, name='indexing.resume'),
    path('indexing/<uuid:batch_id>/delete', indexing_views.destroy, name='indexing.delete'),
    path(
        'indexing/<uuid:batch_id>/requests/<uuid:request_id>',
        indexing_views.update_request,
        name='indexing.request',
    ),
    path(
        'indexing/<uuid:batch_id>/evidence',
        indexing_views.evidence,
        name='indexing.evidence',
    ),
    path('notifications', notification_views.index, name='notifications'),
    path('notifications/read-all', notification_views.read_all, name='notifications.read_all'),
    path(
        'notifications/<uuid:notification_id>/read',
        notification_views.read,
        name='notification.read',
    ),
]

#: SEO: el rendimiento del sitio en la búsqueda.
#:
#: **Ninguna dirección lleva el identificador del sitio.** Las cuatro pantallas
#: equivalentes de Search Console sí lo llevan, y se evaluó imitarlas; se decidió
#: al revés, y el motivo es que ese parámetro dejó de decidir algo. La interfaz
#: trabaja contra un solo sitio: el identificador viaja por la dirección, se
#: valida, y resuelve siempre al mismo. Es ceremonia que ensucia siete
#: direcciones para elegir entre una opción.
#:
#: Cuál es el sitio se resuelve en el servidor, con el mismo criterio que usa el
#: menú y en un solo lugar. El día que la cuenta maneje varios, ese día se
#: agrega el segmento —y recién ahí va a significar algo.
#:
#: La consulta y la dirección viajan **en la cadena de consulta y no en la
#: ruta**: una búsqueda puede contener una barra —«patios/pools»— y un segmento
#: de ruta la partiría en dos.
seo_patterns = [
    path('connection', seo_views.connection, name='seo.connection'),
    path('keywords', seo_views.keywords, name='seo.keywords'),
    path('keyword', seo_views.keyword, name='seo.keyword'),
    path('pages', seo_views.pages, name='seo.pages'),
    path('page', seo_views.page, name='seo.page'),
    path('cannibalization', seo_views.cannibalization, name='seo.cannibalization'),
    # La única del módulo que contesta JSON: la fila que se abre pide sus URLs
    # acá, y sólo la que alguien abre.
    path(
        'cannibalization/pages',
        seo_views.cannibalization_pages,
        name='seo.cannibalization.pages',
    ),
    path('backfill', seo_views.backfill, name='seo.backfill'),
    path('track', seo_views.track, name='seo.keyword.track'),
]

urlpatterns = [
    # La raíz elige herramienta. El tablero se mudó a `search-console/`.
    path('', web_views.home, name='home'),
    path('login', web_views.login, name='login'),
    path('logout', web_views.logout, name='logout'),
    # La raíz de la herramienta se declara aparte del `include` y sin barra
    # final, para que sea `/search-console` y no `/search-console/`: el resto de
    # las direcciones del producto no la lleva —`/domains`, `/login`— y una sola
    # que sí obligaría a recordar cuál es la excepción.
    path('search-console', web_views.search_console, name='search_console'),
    path('search-console/', include(search_console_patterns)),
    # La segunda herramienta, con el mismo criterio que la primera: la raíz sin
    # barra final y el resto adentro.
    path('seo', seo_views.overview, name='seo'),
    path('seo/', include(seo_patterns)),
    # Lo que sigue es de la cuenta y no de una herramienta: el perfil es de quien
    # entra, y las claves de API son nuestras —las usa un pipeline de despliegue—
    # y no de Google. Ponerlas bajo `search-console/` mandaría a buscarlas
    # adentro de una herramienta que no las emite.
    path('profile', account_views.profile, name='profile'),
    path('profile/preferences', account_views.preferences_update, name='profile.preferences'),
    path('profile/email', account_views.email_update, name='profile.email'),
    path('profile/password', account_views.password_update, name='profile.password'),
    # Las sesiones se muestran adentro del perfil, pero su dirección vieja sigue
    # existiendo: es una que alguien pudo guardar, y contestar 404 a un favorito
    # es romper algo que funcionaba. Las dos acciones —cerrar una, cerrar el
    # resto— no son pantallas y siguen viviendo acá.
    path('sessions', account_views.index, name='sessions'),
    path('sessions/close-others', account_views.close_others, name='sessions.close_others'),
    path('sessions/close-selected', account_views.close_selected, name='sessions.close_selected'),
    path('sessions/<uuid:session_id>/close', account_views.close, name='session.close'),
    path('api-keys', account_views.api_keys, name='api_keys'),
    path('api-keys/new', account_views.api_key_create, name='api_key.create'),
    path('api-keys/<uuid:key_id>/revoke', account_views.api_key_revoke, name='api_key.revoke'),
    path('users', user_views.index, name='users'),
    path('users/invite', user_views.invite, name='user.invite'),
    path('accept-invitation/<str:token>', user_views.accept_invitation, name='invitation.accept'),
    path('users/<uuid:account_id>/role', user_views.change_role, name='user.role'),
    path('users/<uuid:account_id>/deactivate', user_views.deactivate, name='user.deactivate'),
    path('admin/', admin.site.urls),
    path('api/', include('config.api_urls')),
]
