"""
Traducción del veredicto de Google a nuestro vocabulario (R4).

Es una función pura y probada por separado, no un método pegado al cliente. La
razón es que se aplica sobre datos que ya guardamos crudos: si mañana entendemos
mejor un estado, se puede recalcular todo el historial sin gastar una sola
consulta de cuota.

La regla es **cerrar en falso**: sólo `PASS` cuenta como indexada, y todo lo que
no se reconozca cae en «sin indexar, otro motivo», nunca en «sin consultar». La
asimetría es deliberada. Decir «no sabemos» de algo que Google sí contestó
esconde una respuesta real; decir «indexada» de algo que no lo está manda a
alguien a no revisar una página que necesitaba revisión. De los dos errores
posibles, el segundo es el que cuesta caro.
"""

from apps.sitemaps.models import CoverageState

# El único veredicto que cuenta como indexada. No es una credencial: es el valor
# literal que Google devuelve en el campo «verdict».
VERDICT_PASS = 'PASS'  # noqa: S105

#: Estados de cobertura tal como los escribe Google, en inglés y con su
#: puntuación. Son cadenas de texto y no un enum: Google no publica un
#: catálogo cerrado, así que la lista se completa observando respuestas reales y
#: lo que no figura acá cae en el estado genérico, no en el desconocido.
BY_COVERAGE_STATE = {
    'crawled - currently not indexed': CoverageState.CRAWLED_NOT_INDEXED,
    'discovered - currently not indexed': CoverageState.DISCOVERED_NOT_INDEXED,
    'duplicate without user-selected canonical': CoverageState.DUPLICATE_CANONICAL,
    'duplicate, google chose different canonical than user': CoverageState.DUPLICATE_CANONICAL,
    'duplicate, submitted url not selected as canonical': CoverageState.DUPLICATE_CANONICAL,
    'alternate page with proper canonical tag': CoverageState.DUPLICATE_CANONICAL,
    # Google escribe este estado con comillas tipográficas, y algunas respuestas
    # llegan con la recta. Van las dos: reconocer sólo una haría que el mismo
    # estado se clasificara distinto según qué comilla mandó Google ese día.
    'excluded by ‘noindex’ tag': CoverageState.EXCLUDED_NOINDEX,  # noqa: RUF001
    "excluded by 'noindex' tag": CoverageState.EXCLUDED_NOINDEX,
    'blocked by robots.txt': CoverageState.BLOCKED_ROBOTS,
    'indexed, though blocked by robots.txt': CoverageState.BLOCKED_ROBOTS,
    'page with redirect': CoverageState.REDIRECT,
    'not found (404)': CoverageState.FETCH_ERROR,
    'soft 404': CoverageState.FETCH_ERROR,
    'server error (5xx)': CoverageState.FETCH_ERROR,
    'blocked due to unauthorized request (401)': CoverageState.FETCH_ERROR,
    'blocked due to access forbidden (403)': CoverageState.FETCH_ERROR,
    'blocked due to other 4xx issue': CoverageState.FETCH_ERROR,
    'url is unknown to google': CoverageState.DISCOVERED_NOT_INDEXED,
}

#: Señales que Google entrega en campos aparte. Se miran cuando el estado de
#: cobertura no alcanza, porque a veces el motivo está sólo acá.
BY_ROBOTS_STATE = {'DISALLOWED': CoverageState.BLOCKED_ROBOTS}

BY_INDEXING_STATE = {
    'BLOCKED_BY_META_TAG': CoverageState.EXCLUDED_NOINDEX,
    'BLOCKED_BY_HTTP_HEADER': CoverageState.EXCLUDED_NOINDEX,
    'BLOCKED_BY_ROBOTS_TXT': CoverageState.BLOCKED_ROBOTS,
}

BY_PAGE_FETCH_STATE = {
    'SOFT_404': CoverageState.FETCH_ERROR,
    'NOT_FOUND': CoverageState.FETCH_ERROR,
    'ACCESS_DENIED': CoverageState.FETCH_ERROR,
    'ACCESS_FORBIDDEN': CoverageState.FETCH_ERROR,
    'SERVER_ERROR': CoverageState.FETCH_ERROR,
    'INTERNAL_CRAWL_ERROR': CoverageState.FETCH_ERROR,
    'INVALID_URL': CoverageState.FETCH_ERROR,
    'BLOCKED_4XX': CoverageState.FETCH_ERROR,
    'BLOCKED_ROBOTS_TXT': CoverageState.BLOCKED_ROBOTS,
    'REDIRECT_ERROR': CoverageState.REDIRECT,
}


def translate(index_status: dict) -> str:
    """
    Devuelve el estado interno correspondiente a una respuesta de Google.

    Nunca devuelve `UNKNOWN`: si llegó una respuesta, hay dato, aunque no lo
    sepamos clasificar. `UNKNOWN` significa «no preguntamos», y eso no es algo
    que esta función pueda concluir.
    """
    if not index_status:
        return CoverageState.OTHER_NOT_INDEXED

    if index_status.get('verdict') == VERDICT_PASS:
        return CoverageState.INDEXED

    coverage = (index_status.get('coverageState') or '').strip().lower()
    if coverage in BY_COVERAGE_STATE:
        return BY_COVERAGE_STATE[coverage]

    for field, table in (
        ('robotsTxtState', BY_ROBOTS_STATE),
        ('indexingState', BY_INDEXING_STATE),
        ('pageFetchState', BY_PAGE_FETCH_STATE),
    ):
        translated = table.get(index_status.get(field) or '')
        if translated:
            return translated

    return CoverageState.OTHER_NOT_INDEXED


def raw_fields(index_status: dict) -> dict:
    """
    Extrae los campos crudos que se guardan junto al estado traducido.

    Se guardan enteros y sin interpretar. Son la prueba de lo que Google dijo, y
    lo que permite recalcular la traducción sin volver a preguntar.
    """
    index_status = index_status or {}
    return {
        'raw_verdict': index_status.get('verdict', '') or '',
        'raw_coverage_state': (index_status.get('coverageState', '') or '')[:255],
        'raw_robots_state': index_status.get('robotsTxtState', '') or '',
        'raw_indexing_state': index_status.get('indexingState', '') or '',
        'raw_page_fetch_state': index_status.get('pageFetchState', '') or '',
        'google_canonical': (index_status.get('googleCanonical', '') or '')[:2000],
        'user_canonical': (index_status.get('userCanonical', '') or '')[:2000],
    }
