//! Footer status derivation — a pure function that every client (desktop,
//! watch, phone) uses to decide which one-line indicator to show in its
//! status bar. No UI framework dependency.
//!
//! The truth-table is deliberately tied to the **worker state**, not the
//! queue depth: if the worker is sitting in `sleep(backoff)` after a
//! transient failure, the queue still has items but the worker is `Idle`
//! → we show `Offline` / `Online` based on `last_success_age` vs.
//! `last_failure_age`, not a misleading "Uploading…".

use std::time::Duration;

use crate::server_sync::WorkerState;

/// Derived observable for the client's status line.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FooterStatus {
    /// ffmpeg (or platform-equivalent) is flushing the last recording.
    Finalizing,
    /// Worker is uploading. `count` = pending-dir depth.
    Uploading { count: usize },
    /// Worker is polling `/api/oneliners`.
    Synchronizing,
    /// Never got a successful request yet, and never saw a failure either —
    /// typical cold start with no network attempt so far.
    NeverConnected,
    /// Last event on record was a failure, or `last_success` is older
    /// than the offline threshold.
    Offline,
    /// `last_success` is fresh enough, and no failure has overtaken it.
    Online,
}

/// Decide which footer status to show. Pure function — no side effects,
/// fully table-testable.
///
/// Decision rules for the `Idle` branch:
/// - A failure younger than the last success (or a failure with no
///   prior success at all) wins → `Offline`.
/// - Otherwise fall back to success-age vs. threshold (classic staleness).
///
/// `last_*_age` arguments are `Option<Duration>` = "seconds since that
/// event, or `None` if it never happened". Smaller Duration = more recent.
pub fn pick_footer_status(
    finalizing: bool,
    worker_state: WorkerState,
    queue_len: usize,
    last_success_age: Option<Duration>,
    last_failure_age: Option<Duration>,
    offline_threshold: Duration,
) -> FooterStatus {
    if finalizing {
        return FooterStatus::Finalizing;
    }
    match worker_state {
        WorkerState::Uploading => FooterStatus::Uploading {
            count: queue_len.max(1),
        },
        WorkerState::Polling => FooterStatus::Synchronizing,
        WorkerState::Idle => {
            let failure_more_recent = match (last_success_age, last_failure_age) {
                (Some(s), Some(f)) => f < s, // smaller age = younger
                (None, Some(_)) => true,     // only a failure on record
                _ => false,
            };
            if failure_more_recent {
                return FooterStatus::Offline;
            }
            match last_success_age {
                None => FooterStatus::NeverConnected,
                Some(age) if age > offline_threshold => FooterStatus::Offline,
                Some(_) => FooterStatus::Online,
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    const THRESHOLD: Duration = Duration::from_secs(30);

    /// Convenience: most tests don't care about the failure field.
    fn status(
        finalizing: bool,
        worker_state: WorkerState,
        queue_len: usize,
        last_success_age: Option<Duration>,
    ) -> FooterStatus {
        pick_footer_status(
            finalizing,
            worker_state,
            queue_len,
            last_success_age,
            None,
            THRESHOLD,
        )
    }

    #[test]
    fn finalizing_wins_over_everything() {
        let s = pick_footer_status(
            true,
            WorkerState::Uploading,
            5,
            Some(Duration::from_millis(1)),
            Some(Duration::from_millis(1)),
            THRESHOLD,
        );
        assert_eq!(s, FooterStatus::Finalizing);
    }

    #[test]
    fn uploading_when_queue_non_empty() {
        let s = status(
            false,
            WorkerState::Uploading,
            3,
            Some(Duration::from_millis(1)),
        );
        assert_eq!(s, FooterStatus::Uploading { count: 3 });
    }

    #[test]
    fn uploading_reports_at_least_one_even_if_scan_raced() {
        let s = status(
            false,
            WorkerState::Uploading,
            0,
            Some(Duration::from_millis(1)),
        );
        assert_eq!(s, FooterStatus::Uploading { count: 1 });
    }

    #[test]
    fn synchronizing_when_polling_and_no_queue() {
        let s = status(
            false,
            WorkerState::Polling,
            0,
            Some(Duration::from_millis(1)),
        );
        assert_eq!(s, FooterStatus::Synchronizing);
    }

    #[test]
    fn never_connected_without_last_success() {
        let s = status(false, WorkerState::Idle, 0, None);
        assert_eq!(s, FooterStatus::NeverConnected);
    }

    #[test]
    fn offline_when_last_success_is_stale() {
        let s = status(false, WorkerState::Idle, 0, Some(Duration::from_secs(120)));
        assert_eq!(s, FooterStatus::Offline);
    }

    #[test]
    fn online_when_last_success_is_fresh() {
        let s = status(false, WorkerState::Idle, 0, Some(Duration::from_secs(5)));
        assert_eq!(s, FooterStatus::Online);
    }

    #[test]
    fn offline_threshold_is_inclusive_of_equal_age_as_online() {
        let s = status(false, WorkerState::Idle, 0, Some(THRESHOLD));
        assert_eq!(s, FooterStatus::Online);
    }

    #[test]
    fn offline_while_queue_has_items_but_worker_backing_off() {
        let s = status(false, WorkerState::Idle, 3, Some(Duration::from_secs(120)));
        assert_eq!(s, FooterStatus::Offline);
    }

    #[test]
    fn online_while_queue_has_items_but_worker_idle_and_recent_success() {
        let s = status(false, WorkerState::Idle, 3, Some(Duration::from_secs(2)));
        assert_eq!(s, FooterStatus::Online);
    }

    #[test]
    fn never_connected_while_queue_has_items_and_no_success_ever() {
        let s = status(false, WorkerState::Idle, 3, None);
        assert_eq!(s, FooterStatus::NeverConnected);
    }

    #[test]
    fn failure_younger_than_success_forces_offline() {
        let s = pick_footer_status(
            false,
            WorkerState::Idle,
            1,
            Some(Duration::from_secs(10)),
            Some(Duration::from_secs(1)),
            THRESHOLD,
        );
        assert_eq!(s, FooterStatus::Offline);
    }

    #[test]
    fn success_younger_than_failure_wins_back_online() {
        let s = pick_footer_status(
            false,
            WorkerState::Idle,
            0,
            Some(Duration::from_secs(1)),
            Some(Duration::from_secs(10)),
            THRESHOLD,
        );
        assert_eq!(s, FooterStatus::Online);
    }

    #[test]
    fn only_failure_no_success_is_offline() {
        let s = pick_footer_status(
            false,
            WorkerState::Idle,
            0,
            None,
            Some(Duration::from_secs(5)),
            THRESHOLD,
        );
        assert_eq!(s, FooterStatus::Offline);
    }
}
