-- Auto re-acquisition of missing files via Lidarr (#2527 slice 3, milestone
-- #290). A file going missing has been a dead end until now: reconcile marks
-- it (#2523), every selection path skips it, the admin surface lists it, and
-- there it sits.
--
-- The unit here is the ALBUM, not the track, and that is the design decision
-- carrying most of the safety. Lidarr acquires releases; there is no
-- meaningful "fetch me one track" operation, and a track-kind request needs a
-- recording MBID plenty of files simply don't have. Grouping by album means
-- the case that produced #2523 -- three reorganised albums, ~40 missing files
-- -- becomes three requests instead of forty.

CREATE TABLE missing_reacquisitions (
    album_id        uuid PRIMARY KEY REFERENCES albums (id) ON DELETE CASCADE,
    attempts        int NOT NULL DEFAULT 0,
    last_attempt_at timestamptz,
    -- The request this album's most recent attempt produced. SET NULL rather
    -- than CASCADE: a purged request row must not erase the attempt history
    -- that stops us asking again in a loop.
    last_request_id uuid REFERENCES lidarr_requests (id) ON DELETE SET NULL,
    -- Set when the attempt budget is spent. Distinct from "attempts = max"
    -- so the reason survives a later change to the configured maximum, and so
    -- the admin surface can say "gave up on the 3rd of August" rather than
    -- inferring it.
    gave_up_at      timestamptz,
    created_at      timestamptz NOT NULL DEFAULT now(),
    updated_at      timestamptz NOT NULL DEFAULT now(),
    CONSTRAINT missing_reacquisitions_attempts_nonneg CHECK (attempts >= 0)
);

-- The sweeper's own read: albums due another attempt, oldest attempt first.
-- Partial on the not-given-up rows because a spent album is never selected
-- again and would otherwise grow the index forever.
CREATE INDEX missing_reacquisitions_due_idx
    ON missing_reacquisitions (last_attempt_at NULLS FIRST)
    WHERE gave_up_at IS NULL;

-- Settings, singleton in the style of network_settings (0053). Every value an
-- operator might want to tune lives here rather than in YAML (rule #25).
CREATE TABLE reacquisition_settings (
    id                  boolean PRIMARY KEY DEFAULT true,

    -- Master switch. Default true: the operator asked for this to happen by
    -- itself, and a feature that ships switched off is a feature nobody finds.
    enabled             boolean NOT NULL DEFAULT true,

    -- How long a file must have been missing before the FIRST attempt. This
    -- is what separates "automatic" from "trigger-happy": a filesystem lies
    -- transiently -- an unmounted volume, a container that started before its
    -- media mount attached, a NAS mid-reboot -- and every one of those
    -- resolves itself well inside a day at no cost. tracks.missing_since is
    -- never re-stamped (#2523), so it is a true "gone since" clock to measure
    -- against.
    grace_hours         int NOT NULL DEFAULT 24,

    -- Exponential spacing between attempts: base * 2^(attempts-1), clamped to
    -- backoff_max_hours. 6h -> 12h -> 24h -> 48h by default. An album Lidarr
    -- genuinely cannot find must get quieter, not keep pace.
    backoff_base_hours  int NOT NULL DEFAULT 6,
    backoff_max_hours   int NOT NULL DEFAULT 168,   -- one week

    -- Attempts before giving up. Three real tries spread over days is enough
    -- to ride out a transient Lidarr/indexer outage; past that the answer is
    -- "this release is not obtainable" and asking again is noise.
    max_attempts        int NOT NULL DEFAULT 3,

    -- Ceiling on requests created per sweep. Album grouping already collapses
    -- the common case, but a genuinely large loss (a whole drive slipping
    -- under reconcile's 25% mark cap) should still trickle rather than dump
    -- hundreds of requests into the queue at once.
    max_per_pass        int NOT NULL DEFAULT 20,

    -- Whether the sweeper approves what it creates. Requests are created
    -- pending and nothing reaches Lidarr until approval, so with this off the
    -- feature is a notification rather than an attempt -- which is why the
    -- default is on. Off is the review-first posture: rows appear in the
    -- admin Requests queue for a human to release.
    auto_approve        boolean NOT NULL DEFAULT true,

    CONSTRAINT reacquisition_settings_singleton CHECK (id = true),
    -- Ranges exist to stop a typo becoming a behaviour change: a 0-hour grace
    -- would fire on every transient unmount, and a 10000-per-pass cap would
    -- defeat the point of having one.
    CONSTRAINT reacquisition_settings_grace_range
        CHECK (grace_hours >= 1 AND grace_hours <= 720),
    CONSTRAINT reacquisition_settings_backoff_base_range
        CHECK (backoff_base_hours >= 1 AND backoff_base_hours <= 168),
    CONSTRAINT reacquisition_settings_backoff_max_range
        CHECK (backoff_max_hours >= 1 AND backoff_max_hours <= 720),
    CONSTRAINT reacquisition_settings_backoff_ordered
        CHECK (backoff_max_hours >= backoff_base_hours),
    CONSTRAINT reacquisition_settings_attempts_range
        CHECK (max_attempts >= 1 AND max_attempts <= 10),
    CONSTRAINT reacquisition_settings_per_pass_range
        CHECK (max_per_pass >= 1 AND max_per_pass <= 200)
);

INSERT INTO reacquisition_settings (id) VALUES (true) ON CONFLICT (id) DO NOTHING;
