"""BibTeX merger module.

Merges two lists of CitationRecords, deduplicating by DOI match or title
similarity using ``compute_field_similarity`` from the Red Queen verifier.
The merge is commutative: ``merge(a, b)`` produces the same set of entries
as ``merge(b, a)``.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import TYPE_CHECKING

from alice_cli.cite.redqueen import compute_field_similarity

if TYPE_CHECKING:
    from alice_cli.cite.models import CitationRecord


@dataclass
class MergeResult:
    """Result of merging two BibTeX files."""

    merged_records: list[CitationRecord] = field(default_factory=list)
    duplicates_found: int = 0
    file_a_count: int = 0
    file_b_count: int = 0


def _count_populated_fields(record: CitationRecord) -> int:
    """Count non-empty fields on a CitationRecord for duplicate resolution."""
    count = 0
    if record.title.strip():
        count += 1
    if record.authors:
        count += 1
    if record.year.strip():
        count += 1
    if record.journal.strip():
        count += 1
    if record.volume.strip():
        count += 1
    if record.number.strip():
        count += 1
    if record.pages.strip():
        count += 1
    if record.publisher.strip():
        count += 1
    if record.doi.strip():
        count += 1
    if record.url.strip():
        count += 1
    if record.abstract.strip():
        count += 1
    if record.isbn.strip():
        count += 1
    if record.arxiv_id.strip():
        count += 1
    if record.pmid.strip():
        count += 1
    if record.extra_fields:
        count += len(record.extra_fields)
    return count


def _find_duplicates(
    records_a: list[CitationRecord],
    records_b: list[CitationRecord],
    similarity_threshold: float,
) -> list[tuple[int, int]]:
    """Return pairs of ``(index_a, index_b)`` that are duplicates.

    Duplicate detection uses two strategies in order:
    1. Exact DOI match (both records have a non-empty DOI and they match).
    2. Title similarity above *similarity_threshold* when at least one record
       lacks a DOI (uses ``compute_field_similarity`` from redqueen).

    Each record from *records_b* is matched to at most one record from
    *records_a*.  Each record from *records_a* is matched to at most one
    record from *records_b*.
    """
    pairs: list[tuple[int, int]] = []
    matched_a: set[int] = set()
    matched_b: set[int] = set()

    # Pass 1 – exact DOI match
    for idx_b, rec_b in enumerate(records_b):
        if idx_b in matched_b:
            continue
        if not rec_b.doi.strip():
            continue
        for idx_a, rec_a in enumerate(records_a):
            if idx_a in matched_a:
                continue
            if not rec_a.doi.strip():
                continue
            if rec_a.doi.strip().lower() == rec_b.doi.strip().lower():
                pairs.append((idx_a, idx_b))
                matched_a.add(idx_a)
                matched_b.add(idx_b)
                break

    # Pass 2 – title similarity for unmatched records
    for idx_b, rec_b in enumerate(records_b):
        if idx_b in matched_b:
            continue
        if not rec_b.title.strip():
            continue
        for idx_a, rec_a in enumerate(records_a):
            if idx_a in matched_a:
                continue
            if not rec_a.title.strip():
                continue
            sim = compute_field_similarity(rec_a.title, rec_b.title)
            if sim >= similarity_threshold:
                pairs.append((idx_a, idx_b))
                matched_a.add(idx_a)
                matched_b.add(idx_b)
                break

    return pairs


def _pick_winner(rec_a: CitationRecord, rec_b: CitationRecord) -> CitationRecord:
    """Choose which duplicate to retain.

    Retains the entry with more populated fields.  When tied, breaks
    deterministically by comparing ``citation_key`` lexicographically so that
    ``merge(a, b)`` and ``merge(b, a)`` keep the same record.
    """
    count_a = _count_populated_fields(rec_a)
    count_b = _count_populated_fields(rec_b)

    if count_a > count_b:
        return rec_a
    if count_b > count_a:
        return rec_b

    # Tie-break: prefer the record whose citation_key sorts first
    if rec_a.citation_key <= rec_b.citation_key:
        return rec_a
    return rec_b


def merge_bibtex(
    records_a: list[CitationRecord],
    records_b: list[CitationRecord],
    similarity_threshold: float = 0.85,
) -> MergeResult:
    """Merge two lists of CitationRecords, deduplicating by DOI or title similarity.

    Uses ``compute_field_similarity`` from ``redqueen.py`` for title comparison.
    Retains the entry with more populated fields on duplicate detection.
    Commutative: ``merge(a, b)`` produces the same set of entries as
    ``merge(b, a)``.
    """
    duplicate_pairs = _find_duplicates(records_a, records_b, similarity_threshold)

    dup_indices_a = {idx_a for idx_a, _ in duplicate_pairs}
    dup_indices_b = {idx_b for _, idx_b in duplicate_pairs}

    merged: list[CitationRecord] = []

    # Add winners from duplicate pairs
    for idx_a, idx_b in duplicate_pairs:
        winner = _pick_winner(records_a[idx_a], records_b[idx_b])
        merged.append(winner)

    # Add non-duplicate records from A
    for idx_a, rec in enumerate(records_a):
        if idx_a not in dup_indices_a:
            merged.append(rec)

    # Add non-duplicate records from B
    for idx_b, rec in enumerate(records_b):
        if idx_b not in dup_indices_b:
            merged.append(rec)

    # Sort merged records by citation_key for deterministic output (commutativity)
    merged.sort(key=lambda r: r.citation_key)

    return MergeResult(
        merged_records=merged,
        duplicates_found=len(duplicate_pairs),
        file_a_count=len(records_a),
        file_b_count=len(records_b),
    )
