"""Shared publisher HTML access-signal detection helpers."""

from __future__ import annotations


from ...utils import normalize_text
from ...quality.reason_codes import (
    ABSTRACT_ONLY,
    CLOUDFLARE_CHALLENGE,
    INSUFFICIENT_BODY,
    PUBLISHER_ACCESS_DENIED,
    PUBLISHER_NOT_FOUND,
    PUBLISHER_PAYWALL,
    REDIRECTED_TO_ABSTRACT,
    STRUCTURED_ARTICLE_NOT_FULLTEXT,
    STRUCTURED_MISSING_BODY_SECTIONS,
)
from .parsing import choose_parser
from .ui_tokens import SPRINGER_PREVIEW_PHRASE

from bs4 import BeautifulSoup

CHALLENGE_PATTERNS = (
    "just a moment",
    "verify you are human",
    "checking your browser",
    "challenge-error-text",
    "attention required",
    "cloudflare",
    "radware bot manager",
    "perfdrive",
    "confirm you are a human",
    "h-captcha",
)
CLOUDFLARE_CHALLENGE_TITLE_TOKENS = tuple(
    token
    for token in CHALLENGE_PATTERNS
    if token in {"just a moment", "attention required", "checking your browser"}
)
ACCESS_DENIED_TOKEN = "access denied"
LOGIN_GATE_TOKENS = ("sign in", "sign-in", "log in", "login")
ACCESS_GATE_CHECK_ACCESS_LABEL = "check access"
ACCESS_GATE_ACCESS_PROVIDED_BY_LABEL = "access provided by"
ACCESS_GATE_BUY_NOW_LABEL = "buy now"
ACCESS_GATE_VIEW_OPTIONS_LABEL = "view access options"
ACCESS_GATE_INSTITUTIONAL_LOGIN_LABEL = "institutional login"
ACCESS_GATE_LABELS = (
    ACCESS_GATE_CHECK_ACCESS_LABEL,
    ACCESS_GATE_ACCESS_PROVIDED_BY_LABEL,
    ACCESS_GATE_BUY_NOW_LABEL,
    ACCESS_GATE_VIEW_OPTIONS_LABEL,
    ACCESS_GATE_INSTITUTIONAL_LOGIN_LABEL,
)
MARKDOWN_ACCESS_NOISE_LABELS = (
    ACCESS_GATE_ACCESS_PROVIDED_BY_LABEL,
    "buy article",
    ACCESS_GATE_VIEW_OPTIONS_LABEL,
    "you have full access to this",
)
MARKDOWN_SHORT_ACCESS_GATE_TOKENS = (
    *LOGIN_GATE_TOKENS,
    ACCESS_GATE_VIEW_OPTIONS_LABEL,
    ACCESS_GATE_CHECK_ACCESS_LABEL,
    ACCESS_GATE_BUY_NOW_LABEL,
)
COMMON_ACCESS_BLOCK_TOKENS = (
    "unable to complete your request",
    "your request has been blocked",
    "verify you are human",
    "captcha",
    ACCESS_DENIED_TOKEN,
)
SUPPLEMENTARY_BLOCKING_TITLE_TOKENS = (
    *CLOUDFLARE_CHALLENGE_TITLE_TOKENS,
    *LOGIN_GATE_TOKENS,
    ACCESS_DENIED_TOKEN,
)
SUPPLEMENTARY_BLOCKING_BODY_TOKENS = (
    *(token for token in CHALLENGE_PATTERNS if token in {"checking your browser", "cloudflare"}),
    "enable javascript and cookies",
    "please sign in",
    ACCESS_GATE_INSTITUTIONAL_LOGIN_LABEL,
    ACCESS_DENIED_TOKEN,
)
ASSET_ACCESS_BLOCK_LABELS = (
    "unauthorized",
    "forbidden",
    "authentication",
    "authorization",
    "permission",
    ACCESS_DENIED_TOKEN,
    "access gate",
    "license",
)
ASSET_BLOCKING_REASON_TOKENS = ASSET_ACCESS_BLOCK_LABELS
ACCESS_GATE_PATTERNS = (
    ACCESS_GATE_CHECK_ACCESS_LABEL,
    "purchase access",
    "purchase digital access to this article",
    "institutional access",
    "log in to your account",
    "login to your account",
    "subscribe to continue",
    "access through your institution",
    "rent or buy",
    "purchase this article",
    "purchase article",
    "access the full article",
    "get full access to this article",
    "get access",
    "access this article",
    "buy article pdf",
    ACCESS_GATE_BUY_NOW_LABEL,
    "sign in to access",
    ACCESS_GATE_VIEW_OPTIONS_LABEL,
    "view all access options to continue reading this article",
    ACCESS_GATE_INSTITUTIONAL_LOGIN_LABEL,
    SPRINGER_PREVIEW_PHRASE,
)
NOT_FOUND_PATTERNS = (
    "doi not found",
    "page not found",
    "article not found",
    "content not found",
)
FAILURE_MESSAGES = {
    CLOUDFLARE_CHALLENGE: "Encountered a challenge or CAPTCHA page while loading publisher HTML.",
    PUBLISHER_NOT_FOUND: "Publisher page was not found for this DOI.",
    PUBLISHER_ACCESS_DENIED: "Publisher denied access to the full-text page.",
    PUBLISHER_PAYWALL: "Publisher paywall or access gate detected on the page.",
    REDIRECTED_TO_ABSTRACT: "Publisher redirected the full-text URL to an abstract page.",
    ABSTRACT_ONLY: "Publisher HTML only exposed abstract-level content without article body text.",
    INSUFFICIENT_BODY: "HTML extraction did not produce enough article body text.",
    STRUCTURED_ARTICLE_NOT_FULLTEXT: "Structured full text did not indicate complete article availability.",
    STRUCTURED_MISSING_BODY_SECTIONS: "Structured full text did not include article body sections beyond the abstract and references.",
}


class HtmlExtractionFailure(Exception):
    def __init__(self, reason: str, message: str) -> None:
        super().__init__(message)
        self.reason = reason
        self.message = message


def summarize_html(html_text: str, limit: int = 1000) -> str:
    soup = BeautifulSoup(html_text, choose_parser())
    return " ".join(soup.stripped_strings)[:limit]


def html_failure_message(reason: str) -> str:
    return FAILURE_MESSAGES.get(reason, "The full-text route was not usable.")


def matched_access_gate_patterns(text: str) -> list[str]:
    normalized = normalize_text(text).lower()
    if not normalized:
        return []
    return [pattern for pattern in ACCESS_GATE_PATTERNS if pattern in normalized]


def contains_access_gate_text(text: str) -> bool:
    return bool(matched_access_gate_patterns(text))


def detect_html_access_signals(
    title: str,
    text: str,
    response_status: int | None,
    *,
    redirected_to_abstract: bool = False,
    include_paywall_text: bool = True,
    explicit_no_access: bool = False,
) -> list[str]:
    signals: list[str] = []
    if redirected_to_abstract:
        signals.append(REDIRECTED_TO_ABSTRACT)

    combined = normalize_text(" ".join([title, text])).lower()
    if any(pattern in combined for pattern in CHALLENGE_PATTERNS):
        signals.append(CLOUDFLARE_CHALLENGE)
    if response_status == 404 or any(pattern in combined for pattern in NOT_FOUND_PATTERNS):
        signals.append(PUBLISHER_NOT_FOUND)
    if response_status in {401, 402, 403} and CLOUDFLARE_CHALLENGE not in signals:
        signals.append(PUBLISHER_ACCESS_DENIED)
    if explicit_no_access:
        signals.append(PUBLISHER_ACCESS_DENIED)
    if include_paywall_text and contains_access_gate_text(combined):
        signals.append(PUBLISHER_PAYWALL)
    return list(dict.fromkeys(signals))


def detect_html_block(title: str, text: str, response_status: int | None) -> HtmlExtractionFailure | None:
    signals = detect_html_access_signals(title, text, response_status)
    if not signals:
        return None
    reason = signals[0]
    return HtmlExtractionFailure(reason, html_failure_message(reason))
