"""Citation pipeline orchestrator.

Chains the five pipeline stages (clean → resolve → validate → redqueen →
format) into a single ``CitationPipeline`` class that processes raw reference
strings into formatted citation output.
"""

from __future__ import annotations

from typing import TYPE_CHECKING

import structlog

from alice_cli.cite.cleaner import clean_reference
from alice_cli.cite.formatter import format_citation
from alice_cli.cite.models import CitationRecord
from alice_cli.cite.validator import validate_record

if TYPE_CHECKING:
    from collections.abc import Callable

    from alice_cli.cite.cache import CitationCache
    from alice_cli.cite.redqueen import RedQueenVerifier
    from alice_cli.cite.resolver import ReferenceResolver

logger = structlog.get_logger(__name__)


class UnverifiedRecordError(Exception):
    """Raised in strict mode when a record cannot be verified."""

    def __init__(self, reference: str) -> None:
        self.reference = reference
        super().__init__(f"Unverified record for reference: {reference!r}")


class CitationPipeline:
    """Orchestrate the 5-stage citation pipeline.

    Parameters
    ----------
    resolver:
        Resolves cleaned references into candidate ``CitationRecord`` objects.
    verifier:
        Red Queen adversarial verifier for cross-referencing citations.
    formatter:
        The ``format_citation`` callable (record, output_format) → str.
    cache:
        Optional ``CitationCache`` for AgentCore Memory-backed caching.
    """

    def __init__(
        self,
        resolver: ReferenceResolver,
        verifier: RedQueenVerifier,
        formatter: Callable[[CitationRecord, str], str] = format_citation,
        cache: CitationCache | None = None,
    ) -> None:
        self._resolver = resolver
        self._verifier = verifier
        self._formatter = formatter
        self._cache = cache

    def run(
        self,
        raw_reference: str,
        output_format: str,
        strict: bool = False,
    ) -> tuple[CitationRecord, str]:
        """Run the full pipeline for a single reference.

        Returns ``(verified_record, formatted_string)``.

        In strict mode, raises :class:`UnverifiedRecordError` if the record
        is unverified after Red Queen verification.
        """
        # 1. Clean
        cleaned = clean_reference(raw_reference)

        # 2. Resolve (resolver handles cache internally)
        candidates = self._resolver.resolve(cleaned)
        if not candidates:
            # No candidates — create a minimal record from the cleaned text
            record = CitationRecord(title=cleaned.cleaned_text)
            record.warnings.append("No matching record found")
        else:
            record = candidates[0]

        # 3. Validate
        record = validate_record(record)

        # 4. Red Queen verify
        record = self._verifier.verify(record, record.resolved_source)

        # 5. Strict mode check
        if strict and record.verification_status != "verified":
            raise UnverifiedRecordError(raw_reference)

        # 6. Format
        formatted = self._formatter(record, output_format)

        return record, formatted

    def run_batch(
        self,
        references: list[str],
        output_format: str,
        strict: bool = False,
        on_candidates: Callable[[str, list[CitationRecord]], CitationRecord | None] | None = None,
    ) -> list[tuple[CitationRecord | None, str | None]]:
        """Process multiple references, preserving input order.

        Parameters
        ----------
        references:
            List of raw reference strings.
        output_format:
            Target format ("bibtex", "apa", or "mla").
        strict:
            When ``True``, unverified records are excluded (returned as
            ``(None, None)``).
        on_candidates:
            Optional callback for interactive disambiguation. Called with
            ``(raw_reference, candidates)`` when multiple candidates are
            found. Should return the selected record or ``None`` to skip.

        Returns
        -------
        list[tuple[CitationRecord | None, str | None]]
            Results in the same order as *references*. Each element is
            ``(record, formatted_string)`` or ``(None, None)`` for skipped
            or excluded entries.
        """
        results: list[tuple[CitationRecord | None, str | None]] = []

        for raw_ref in references:
            try:
                result = self._process_single(
                    raw_ref,
                    output_format,
                    strict,
                    on_candidates,
                )
                results.append(result)
            except Exception:
                logger.warning(
                    "Failed to process reference",
                    reference=raw_ref,
                    exc_info=True,
                )
                results.append((None, None))

        return results

    def _process_single(
        self,
        raw_reference: str,
        output_format: str,
        strict: bool,
        on_candidates: Callable[[str, list[CitationRecord]], CitationRecord | None] | None,
    ) -> tuple[CitationRecord | None, str | None]:
        """Process a single reference within a batch context."""
        # 1. Clean
        cleaned = clean_reference(raw_reference)

        # 2. Resolve
        candidates = self._resolver.resolve(cleaned)

        if not candidates:
            record = CitationRecord(title=cleaned.cleaned_text)
            record.warnings.append("No matching record found")
        elif len(candidates) > 1 and on_candidates is not None:
            # Interactive disambiguation
            selected = on_candidates(raw_reference, candidates)
            if selected is None:
                # User chose to skip
                return (None, None)
            record = selected
        else:
            record = candidates[0]

        # 3. Validate
        record = validate_record(record)

        # 4. Red Queen verify
        record = self._verifier.verify(record, record.resolved_source)

        # 5. Strict mode check
        if strict and record.verification_status != "verified":
            return (None, None)

        # 6. Format
        formatted = self._formatter(record, output_format)

        return record, formatted
