"""Native SubscribeStar ingester — the SubscribeStar ADAPTER over the
platform-agnostic core (`ingest_core.Ingester`).

Thin counterpart to patreon_ingester: wires the SubscribeStar client/downloader/
ledger models/constraints/key into the core and supplies the SubscribeStar
failure mapping. The three modes (tick / backfill / recovery / recapture), the
seen + dead-letter ledgers, cursor checkpointing, and the post-first capture all
live in the core — identical to Patreon. `download_service.download_source`
drives `SubscribeStarIngester.run` exactly as it drives the Patreon one.

`campaign_id` is the creator URL (the client derives host + slug from it), so no
campaign-id resolver is needed. FC runs on a plain-HTTP homelab; nothing here
uses a secure-context Web API.
"""

from __future__ import annotations

import asyncio
import logging
from collections.abc import Callable
from pathlib import Path

from ..models import SubscribeStarFailedMedia, SubscribeStarSeenMedia
from .ingest_core import DEAD_LETTER_THRESHOLD, Ingester
from .subscribestar_client import MediaItem, SubscribeStarAPIError, SubscribeStarClient
from .subscribestar_downloader import SubscribeStarDownloader

__all__ = [
    "DEAD_LETTER_THRESHOLD",
    "SubscribeStarIngester",
    "_ledger_key",
    "verify_subscribestar_credential",
]

log = logging.getLogger(__name__)

_LEDGER_KEY_MAX = 128


def _ledger_key(media: MediaItem) -> str:
    """Stable per-media identity for the cross-run seen-ledger. SubscribeStar's
    full-res URL is an opaque `/post_uploads?payload=...` (no content hash), so
    `media.filehash` is normally None and the stable proxy is the gallery item id
    scoped to its post: `<post_id>:<media_id>`. Bounded to the column width."""
    if media.filehash:
        return media.filehash
    return f"{media.post_id}:{media.media_id}"[:_LEDGER_KEY_MAX]


class SubscribeStarIngester(Ingester):
    """Walk a SubscribeStar creator's posts, download unseen media, return a
    `DownloadResult`. A thin adapter over `ingest_core.Ingester`; `client` /
    `downloader` are injectable seams so unit tests run without network."""

    def __init__(
        self,
        images_root: Path,
        cookies_path: str | None,
        session_factory: Callable[[], object],
        *,
        validate: bool = True,
        rate_limit: float = 0.0,
        request_sleep: float = 0.0,
        auth_token: str | None = None,
        client: SubscribeStarClient | None = None,
        downloader: SubscribeStarDownloader | None = None,
    ):
        # auth_token: accepted for the uniform native-ingester construction
        # (download_backends passes it to every adapter); SubscribeStar
        # authenticates by cookies, so it's unused here.
        del auth_token
        self.images_root = Path(images_root)
        self.cookies_path = str(cookies_path) if cookies_path else None
        resolved_client = (
            client
            if client is not None
            else SubscribeStarClient(cookies_path, request_sleep=request_sleep)
        )
        resolved_downloader = (
            downloader
            if downloader is not None
            else SubscribeStarDownloader(
                self.images_root, cookies_path, validate=validate, rate_limit=rate_limit,
            )
        )
        super().__init__(
            client=resolved_client,
            downloader=resolved_downloader,
            session_factory=session_factory,
            seen_model=SubscribeStarSeenMedia,
            failed_model=SubscribeStarFailedMedia,
            seen_constraint="uq_subscribestar_seen_media_source_id",
            failed_constraint="uq_subscribestar_failed_media_source_id",
            ledger_key=_ledger_key,
            platform="subscribestar",
            error_base=SubscribeStarAPIError,
            # API_DRIFT message phrasing; the base Ingester._failure_result owns
            # the auth/drift/HTTP→error_type mapping (shared across platforms).
            drift_label="SubscribeStar markup",
        )


async def verify_subscribestar_credential(
    url: str,
    cookies_path: str | None,
    overrides: dict | None,
) -> tuple[bool | None, str]:
    """Native SubscribeStar credential probe — fetches ONE feed page via
    SubscribeStarClient.verify_auth. `campaign_id` is just the creator URL (no
    resolver). Returns the uniform `(ok, message)` contract so
    download_backends.verify_credential treats it like the gallery-dl probe."""
    client = SubscribeStarClient(cookies_path)
    loop = asyncio.get_running_loop()
    return await loop.run_in_executor(None, client.verify_auth, url)
