"""The frozen target-selection rule from docs/PROBLEMS-DEEP.md section 1.4.

The four numerical gates are:

1. The planet is a confirmed transiting planet.
2. TESS magnitude is at most 11.0.
3. Orbital period is at most 10.0 days.
4. At least two TESS sectors were observed.

Section 1.4 says "brighter than about 11" and "shorter than 10 days". The
boundaries are applied inclusively, so 11.0 and 10.0 pass. That is the
interpretation tested here and written into every report.

Sector novelty is not a numerical gate in this code. Whether a sector was used
in the published ephemeris cannot be established from archive summary fields.
The code returns a status instead of guessing, and only reports
POSTDATES_REFERENCE_YEAR when the recorded sector start year is later than the
recorded ephemeris reference year. Even then, absence from the published fit is
not verified.
"""

from __future__ import annotations

from dataclasses import dataclass

from .model import (
    Candidate,
    NoveltyAssessment,
    NoveltyStatus,
    Sector,
    utc_year,
)

MAX_TMAG = 11.0
MAX_PERIOD_DAYS = 10.0
MIN_SECTORS = 2


@dataclass(frozen=True)
class Thresholds:
    """Thresholds for one selection run. Defaults are the frozen rule."""

    max_tmag: float = MAX_TMAG
    max_period_days: float = MAX_PERIOD_DAYS
    min_sectors: int = MIN_SECTORS


DEFAULT_THRESHOLDS = Thresholds()


@dataclass(frozen=True)
class Evaluation:
    """One candidate and the outcome of every gate."""

    candidate: Candidate
    selected: bool
    reasons: tuple[str, ...]
    novelty: NoveltyAssessment


def assess_sector_novelty(candidate: Candidate) -> NoveltyAssessment:
    """Flag whether any sector postdates the ephemeris reference year.

    The only evidence used is recorded data: the sector start timestamps and the
    reference year parsed from the archive's ephemeris reference. A sector that
    started in a later calendar year than the reference publication year cannot
    have been part of that fit. A sector from the same year, or a missing
    reference year, is UNVERIFIED.
    """

    reference_year = candidate.ephemeris.reference_year
    sectors = candidate.unique_sectors()
    if reference_year is None:
        return NoveltyAssessment(
            status=NoveltyStatus.UNVERIFIED,
            detail="No ephemeris reference year is recorded, so no sector can be shown to postdate it.",
        )
    if not sectors:
        return NoveltyAssessment(
            status=NoveltyStatus.UNVERIFIED,
            detail="No TESS sector timestamps are recorded, so no sector can be shown to postdate the ephemeris.",
        )

    later = tuple(
        sector.number
        for sector in sectors
        if utc_year(sector.start_utc) > reference_year
    )
    if later:
        listed = ", ".join(str(number) for number in later)
        return NoveltyAssessment(
            status=NoveltyStatus.POSTDATES_REFERENCE_YEAR,
            detail=(
                f"Sector(s) {listed} started after the ephemeris reference year "
                f"{reference_year}. Absence from the published fit is not verified."
            ),
            sectors_after_reference_year=later,
        )
    return NoveltyAssessment(
        status=NoveltyStatus.UNVERIFIED,
        detail=(
            f"No sector start year is later than the ephemeris reference year "
            f"{reference_year}. The published fit may still omit a sector."
        ),
    )


def evaluate_candidate(
    candidate: Candidate, thresholds: Thresholds = DEFAULT_THRESHOLDS
) -> Evaluation:
    """Apply the four numerical gates and attach the sector-novelty status."""

    reasons: list[str] = []

    if not candidate.confirmed_transiting:
        reasons.append("not a confirmed transiting planet")

    if candidate.tmag is None:
        reasons.append("TESS magnitude missing")
    elif candidate.tmag > thresholds.max_tmag:
        reasons.append(
            f"TESS magnitude {candidate.tmag:g} is fainter than {thresholds.max_tmag:g}"
        )

    period = candidate.ephemeris.period_days
    if period is None:
        reasons.append("orbital period missing")
    elif period > thresholds.max_period_days:
        reasons.append(
            f"period {period:g} d is longer than {thresholds.max_period_days:g} d"
        )

    sector_count = len(candidate.unique_sectors())
    if sector_count < thresholds.min_sectors:
        reasons.append(
            f"{sector_count} sector(s) observed, fewer than {thresholds.min_sectors}"
        )

    return Evaluation(
        candidate=candidate,
        selected=not reasons,
        reasons=tuple(reasons),
        novelty=assess_sector_novelty(candidate),
    )


def sort_key(candidate: Candidate) -> tuple[float, float, int, str]:
    """Deterministic order: brighter first, then shorter period, then TIC."""

    tmag = candidate.tmag if candidate.tmag is not None else float("inf")
    period = (
        candidate.ephemeris.period_days
        if candidate.ephemeris.period_days is not None
        else float("inf")
    )
    return (tmag, period, candidate.tic_id, candidate.planet_name)


def evaluate_all(
    candidates: list[Candidate], thresholds: Thresholds = DEFAULT_THRESHOLDS
) -> tuple[Evaluation, ...]:
    """Evaluate in deterministic order so output does not depend on input order."""

    ordered = sorted(candidates, key=sort_key)
    return tuple(evaluate_candidate(candidate, thresholds) for candidate in ordered)


def shortlist(
    candidates: list[Candidate], thresholds: Thresholds = DEFAULT_THRESHOLDS
) -> tuple[Candidate, ...]:
    """Return only candidates that pass every gate, in deterministic order."""

    return tuple(
        evaluation.candidate
        for evaluation in evaluate_all(candidates, thresholds)
        if evaluation.selected
    )


def unique_sector_count(candidate: Candidate) -> int:
    return len(candidate.unique_sectors())


def sector_years(sectors: tuple[Sector, ...]) -> tuple[int, ...]:
    return tuple(utc_year(sector.start_utc) for sector in sectors)
