"""Citation validation and field completion.

Detects the citation type from record fields and source metadata, checks
that all required fields for the detected type are present, attempts to
fill missing fields from source metadata, and adds warnings for any
fields that remain unfilled.
"""

from __future__ import annotations

from typing import Any

from alice_cli.cite.models import CitationRecord, CitationType

# ── Required fields per citation type ─────────────────────────────────────────

REQUIRED_FIELDS: dict[CitationType, list[str]] = {
    CitationType.ARTICLE: ["title", "authors", "year", "journal"],
    CitationType.CONFERENCE: ["title", "authors", "year"],
    CitationType.BOOK: ["title", "authors", "year", "publisher"],
    CitationType.SOFTWARE: ["title", "authors", "year", "url"],
    CitationType.DATASET: ["title", "authors", "year", "publisher"],
    CitationType.THESIS: ["title", "authors", "year"],
    CitationType.PREPRINT: ["title", "authors", "year"],
}

# ── Mapping from entry_type strings to CitationType ───────────────────────────

_ENTRY_TYPE_MAP: dict[str, CitationType] = {
    "article": CitationType.ARTICLE,
    "journal-article": CitationType.ARTICLE,
    "conference": CitationType.CONFERENCE,
    "inproceedings": CitationType.CONFERENCE,
    "conference-paper": CitationType.CONFERENCE,
    "proceedings-article": CitationType.CONFERENCE,
    "book": CitationType.BOOK,
    "book-chapter": CitationType.BOOK,
    "edited-book": CitationType.BOOK,
    "monograph": CitationType.BOOK,
    "software": CitationType.SOFTWARE,
    "dataset": CitationType.DATASET,
    "thesis": CitationType.THESIS,
    "phdthesis": CitationType.THESIS,
    "mastersthesis": CitationType.THESIS,
    "dissertation": CitationType.THESIS,
    "preprint": CitationType.PREPRINT,
    "posted-content": CitationType.PREPRINT,
}


def _is_field_empty(record: CitationRecord, field: str) -> bool:
    """Return True if the field on the record is empty/missing."""
    value = getattr(record, field, None)
    if value is None:
        return True
    if isinstance(value, str):
        return not value.strip()
    if isinstance(value, list):
        return len(value) == 0
    return False


def detect_citation_type(
    record: CitationRecord,
    source_metadata: dict[str, Any] | None = None,
) -> CitationType:
    """Detect the citation type from metadata or record fields.

    Priority:
    1. ``record.citation_type`` if already set
    2. ``source_metadata["type"]`` mapped through ``_ENTRY_TYPE_MAP``
    3. ``record.entry_type`` mapped through ``_ENTRY_TYPE_MAP``
    4. Heuristic: arXiv → PREPRINT, GitHub URL → SOFTWARE, ISBN → BOOK
    5. Default: ARTICLE
    """
    # 1. Already set on the record
    if record.citation_type is not None:
        return record.citation_type

    # 2. From source metadata
    if source_metadata:
        meta_type = source_metadata.get("type", "")
        if isinstance(meta_type, str):
            mapped = _ENTRY_TYPE_MAP.get(meta_type.lower().strip())
            if mapped is not None:
                return mapped

    # 3. From entry_type field
    if record.entry_type:
        mapped = _ENTRY_TYPE_MAP.get(record.entry_type.lower().strip())
        if mapped is not None:
            return mapped

    # 4. Heuristic detection from fields
    if record.arxiv_id:
        return CitationType.PREPRINT
    if record.url and "github.com" in record.url:
        return CitationType.SOFTWARE
    if record.isbn:
        return CitationType.BOOK

    # 5. Default
    return CitationType.ARTICLE


# ── Metadata field mapping ────────────────────────────────────────────────────

_METADATA_FIELD_MAP: dict[str, str] = {
    "title": "title",
    "year": "year",
    "journal": "journal",
    "publisher": "publisher",
    "url": "url",
    "volume": "volume",
    "number": "number",
    "pages": "pages",
    "doi": "doi",
}


def _try_fill_from_metadata(
    record: CitationRecord,
    field: str,
    source_metadata: dict[str, Any],
) -> bool:
    """Attempt to fill a missing field from source_metadata.

    Returns True if the field was successfully filled.
    """
    if field == "authors":
        # Authors can come from several metadata keys
        for key in ("authors", "author", "creators"):
            raw = source_metadata.get(key)
            if raw and isinstance(raw, list) and len(raw) > 0:
                # Handle list of strings or list of dicts
                authors: list[str] = []
                for item in raw:
                    if isinstance(item, str) and item.strip():
                        authors.append(item.strip())
                    elif isinstance(item, dict):
                        name = item.get("name", "")
                        if not name:
                            family = item.get("family", "")
                            given = item.get("given", "")
                            if family and given:
                                name = f"{family}, {given}"
                            elif family:
                                name = family
                        if name:
                            authors.append(name)
                if authors:
                    record.authors = authors
                    return True
        return False

    # For scalar fields, try the direct mapping and common alternatives
    meta_key = _METADATA_FIELD_MAP.get(field, field)
    value = source_metadata.get(meta_key)

    # Try alternative keys for some fields
    if not value and field == "journal":
        value = source_metadata.get("container-title")
        if isinstance(value, list) and value:
            value = value[0]
    if not value and field == "publisher":
        value = source_metadata.get("publisher")
    if not value and field == "url":
        value = source_metadata.get("URL") or source_metadata.get("link")
    if not value and field == "year":
        # Try extracting from date fields
        for date_key in ("publication_date", "published", "date", "issued"):
            date_val = source_metadata.get(date_key)
            if isinstance(date_val, str) and len(date_val) >= 4:
                value = date_val[:4]
                break
            if isinstance(date_val, dict):
                date_parts = date_val.get("date-parts", [[]])
                if date_parts and date_parts[0]:
                    value = str(date_parts[0][0])
                    break

    if value and isinstance(value, str) and value.strip():
        setattr(record, field, value.strip())
        return True

    return False


def validate_record(
    record: CitationRecord,
    source_metadata: dict[str, Any] | None = None,
) -> CitationRecord:
    """Check required fields for the detected type, attempt to fill missing
    fields from *source_metadata*, and add warnings for unfillable fields.

    The record is mutated in place and also returned for convenience.
    """
    # Detect and set citation type
    citation_type = detect_citation_type(record, source_metadata)
    record.citation_type = citation_type

    required = REQUIRED_FIELDS.get(citation_type, [])

    for field in required:
        if _is_field_empty(record, field):
            # Attempt to fill from metadata
            filled = False
            if source_metadata:
                filled = _try_fill_from_metadata(record, field, source_metadata)

            if not filled:
                record.warnings.append(
                    f"Missing required field '{field}' for {citation_type.value} citation"
                )

    return record
