"""
Journal model — per-user cache for Tier 4 LLM-scored journals.

Tiers 1–3 read directly from the bundled read-only reference DB
(``journal_quality.db``) on every scoring pass — lookups are
100–300µs and don't need a second-level cache. Only Tier 4 (LLM
analysis via SearXNG) is expensive enough to warrant caching, so
this table holds only LLM-sourced rows (``score_source == "llm"``)
and the filter predicate matches on ``quality_model`` so scores
from a superseded LLM version miss the cache and get re-scored.

Historical note: earlier PR iterations included h_index, sjr_quartile,
publisher, is_predatory, etc. as a Tier 2/3 cache, but the read path
never consulted them — the final schema deliberately omits them.

This module used to live inside ``logs.py``. Moved to its own file
for discoverability — ``Journal`` is unrelated to the ``ResearchLog``
table that ``logs.py`` otherwise owns.
"""

from sqlalchemy import Column, Integer, Sequence, String, UniqueConstraint

from .base import Base


class Journal(Base):
    """Per-user cache for Tier 4 LLM-scored journals. See module docstring."""

    __tablename__ = "journals"

    id = Column(Integer, Sequence("journal_id_seq"), primary_key=True)

    # --- Identity ---
    # Cleaned/normalized journal name. UNIQUE provides the backing
    # index SQLite needs for the exact-name lookup path; a separate
    # non-unique index would be pure duplication.
    name = Column(String(255), nullable=False, unique=True)
    # Lower-cased NFKC-normalized copy of ``name`` for indexed case-
    # insensitive lookups on the citation-save hot path. The UNIQUE
    # constraint in ``__table_args__`` provides the backing index; a
    # separate non-unique ``index=True`` would be a second redundant
    # B-tree on the same column.
    name_lower = Column(String(255), nullable=True)

    # --- LLM cache payload ---
    # Composite quality score (1-10). See constants.VALID_QUALITY_SCORES.
    quality = Column(Integer, nullable=True)
    # Always ``"llm"`` for rows in this table; kept as a string so
    # downgrade + historical rows still parse.
    # KNOWN-DEFERRED: currently always holds "llm" because this table
    # is a Tier 4 LLM cache only. Retained so that a future "downgrade"
    # of this cache to include non-LLM rows (e.g., manual admin
    # overrides) does not require a schema change. Post-merge candidate
    # for removal if no multi-source plan materializes.
    score_source = Column(String(50), nullable=True)
    # LLM model identifier (from utilities.llm_utils.get_model_identifier),
    # indexed because the cache predicate filters on it so a change in
    # configured model invalidates stale scores.
    quality_model = Column(String(255), nullable=True, index=True)
    # Unix timestamp (seconds, not milliseconds) of last analysis — used
    # for the TTL / reanalysis_period freshness check. Read/write via
    # ``int(time.time())``; an Integer column (matching SearchCache) is
    # cheaper on the cache hot path than UtcDateTime would be.
    quality_analysis_time = Column(Integer, nullable=False)

    __table_args__ = (
        UniqueConstraint("name_lower", name="uq_journals_name_lower"),
    )

    def __repr__(self):
        return f"<Journal(name='{self.name}', quality={self.quality})>"
