#![allow(dead_code)]

//! Sync screen state: progress tracking, coalescing, and summary.
//!
//! The sync screen shows real-time progress during data synchronization
//! and transitions to a summary view when complete. A progress coalescer
//! prevents render thrashing from rapid progress updates.

use std::time::Instant;

// ---------------------------------------------------------------------------
// Sync lanes (entity types being synced)
// ---------------------------------------------------------------------------

/// Sync entity types that progress is tracked for.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SyncLane {
    Issues,
    MergeRequests,
    Discussions,
    Notes,
    Events,
    Statuses,
}

impl SyncLane {
    /// Human-readable label for this lane.
    #[must_use]
    pub fn label(self) -> &'static str {
        match self {
            Self::Issues => "Issues",
            Self::MergeRequests => "MRs",
            Self::Discussions => "Discussions",
            Self::Notes => "Notes",
            Self::Events => "Events",
            Self::Statuses => "Statuses",
        }
    }

    /// All lanes in display order.
    pub const ALL: &'static [SyncLane] = &[
        Self::Issues,
        Self::MergeRequests,
        Self::Discussions,
        Self::Notes,
        Self::Events,
        Self::Statuses,
    ];
}

// ---------------------------------------------------------------------------
// Per-lane progress
// ---------------------------------------------------------------------------

/// Progress for a single sync lane.
#[derive(Debug, Clone, Default)]
pub struct LaneProgress {
    /// Current items processed.
    pub current: u64,
    /// Total items expected (0 = unknown).
    pub total: u64,
    /// Whether this lane has completed.
    pub done: bool,
}

impl LaneProgress {
    /// Fraction complete (0.0..=1.0). Returns 0.0 if total is unknown.
    #[must_use]
    pub fn fraction(&self) -> f64 {
        if self.total == 0 {
            return 0.0;
        }
        (self.current as f64 / self.total as f64).clamp(0.0, 1.0)
    }
}

// ---------------------------------------------------------------------------
// Sync summary
// ---------------------------------------------------------------------------

/// Per-entity-type change counts after sync completes.
#[derive(Debug, Clone, Default)]
pub struct EntityChangeCounts {
    pub new: u64,
    pub updated: u64,
}

/// Summary of a completed sync run.
#[derive(Debug, Clone, Default)]
pub struct SyncSummary {
    pub issues: EntityChangeCounts,
    pub merge_requests: EntityChangeCounts,
    pub discussions: EntityChangeCounts,
    pub notes: EntityChangeCounts,
    pub elapsed_ms: u64,
    /// Per-project errors (project path -> error message).
    pub project_errors: Vec<(String, String)>,
}

impl SyncSummary {
    /// Total number of changes across all entity types.
    #[must_use]
    pub fn total_changes(&self) -> u64 {
        self.issues.new
            + self.issues.updated
            + self.merge_requests.new
            + self.merge_requests.updated
            + self.discussions.new
            + self.discussions.updated
            + self.notes.new
            + self.notes.updated
    }

    /// Whether any errors occurred during sync.
    #[must_use]
    pub fn has_errors(&self) -> bool {
        !self.project_errors.is_empty()
    }
}

// ---------------------------------------------------------------------------
// Sync screen mode
// ---------------------------------------------------------------------------

/// Display mode for the sync screen.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum SyncScreenMode {
    /// Full-screen sync progress with per-lane bars.
    #[default]
    FullScreen,
    /// Compact single-line progress for embedding in Bootstrap screen.
    Inline,
}

// ---------------------------------------------------------------------------
// Sync phase
// ---------------------------------------------------------------------------

/// Current phase of the sync operation.
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub enum SyncPhase {
    /// Sync hasn't started yet.
    #[default]
    Idle,
    /// Sync is running.
    Running,
    /// Sync completed successfully.
    Complete,
    /// Sync was cancelled by user.
    Cancelled,
    /// Sync failed with an error.
    Failed(String),
}

// ---------------------------------------------------------------------------
// Progress coalescer
// ---------------------------------------------------------------------------

/// Batches rapid progress updates to prevent render thrashing.
///
/// At most one update is emitted per `floor_ms`. Updates arriving faster
/// are coalesced — only the latest value survives.
#[derive(Debug)]
pub struct ProgressCoalescer {
    /// Minimum interval between emitted updates.
    floor_ms: u64,
    /// Timestamp of the last emitted update.
    last_emit: Option<Instant>,
    /// Number of updates coalesced (dropped) since last emit.
    coalesced_count: u64,
}

impl ProgressCoalescer {
    /// Create a new coalescer with the given floor interval in milliseconds.
    #[must_use]
    pub fn new(floor_ms: u64) -> Self {
        Self {
            floor_ms,
            last_emit: None,
            coalesced_count: 0,
        }
    }

    /// Default coalescer with 100ms floor (10 updates/second max).
    #[must_use]
    pub fn default_floor() -> Self {
        Self::new(100)
    }

    /// Should this update be emitted?
    ///
    /// Returns `true` if enough time has elapsed since the last emit.
    /// The caller should only render/process the update when this returns true.
    pub fn should_emit(&mut self) -> bool {
        let now = Instant::now();
        match self.last_emit {
            None => {
                self.last_emit = Some(now);
                self.coalesced_count = 0;
                true
            }
            Some(last) => {
                let elapsed_ms = now.duration_since(last).as_millis() as u64;
                if elapsed_ms >= self.floor_ms {
                    self.last_emit = Some(now);
                    self.coalesced_count = 0;
                    true
                } else {
                    self.coalesced_count += 1;
                    false
                }
            }
        }
    }

    /// Number of updates that have been coalesced since the last emit.
    #[must_use]
    pub fn coalesced_count(&self) -> u64 {
        self.coalesced_count
    }

    /// Reset the coalescer (e.g., when sync restarts).
    pub fn reset(&mut self) {
        self.last_emit = None;
        self.coalesced_count = 0;
    }
}

// ---------------------------------------------------------------------------
// SyncState
// ---------------------------------------------------------------------------

/// State for the sync progress/summary screen.
#[derive(Debug)]
pub struct SyncState {
    /// Current sync phase.
    pub phase: SyncPhase,
    /// Display mode (full screen vs inline).
    pub mode: SyncScreenMode,
    /// Per-lane progress (updated during Running phase).
    pub lanes: [LaneProgress; 6],
    /// Current stage label (e.g., "Fetching issues...").
    pub stage: String,
    /// Log lines from the sync process.
    pub log_lines: Vec<String>,
    /// Stream throughput stats (items per second).
    pub items_per_sec: f64,
    /// Bytes synced.
    pub bytes_synced: u64,
    /// Total items synced.
    pub items_synced: u64,
    /// When the current sync run started (for throughput calculation).
    pub started_at: Option<Instant>,
    /// Progress coalescer for render throttling.
    pub coalescer: ProgressCoalescer,
    /// Summary (populated after sync completes).
    pub summary: Option<SyncSummary>,
    /// Scroll offset for log lines view.
    pub log_scroll_offset: usize,
}

impl Default for SyncState {
    fn default() -> Self {
        Self {
            phase: SyncPhase::Idle,
            mode: SyncScreenMode::FullScreen,
            lanes: Default::default(),
            stage: String::new(),
            log_lines: Vec::new(),
            items_per_sec: 0.0,
            bytes_synced: 0,
            items_synced: 0,
            started_at: None,
            coalescer: ProgressCoalescer::default_floor(),
            summary: None,
            log_scroll_offset: 0,
        }
    }
}

impl SyncState {
    /// Reset state for a new sync run.
    pub fn start(&mut self) {
        self.phase = SyncPhase::Running;
        self.lanes = Default::default();
        self.stage.clear();
        self.log_lines.clear();
        self.items_per_sec = 0.0;
        self.bytes_synced = 0;
        self.items_synced = 0;
        self.started_at = Some(Instant::now());
        self.coalescer.reset();
        self.summary = None;
        self.log_scroll_offset = 0;
    }

    /// Apply a progress update for a specific lane.
    pub fn update_progress(&mut self, stage: &str, current: u64, total: u64) {
        self.stage = stage.to_string();

        // Map stage name to lane index.
        if let Some(lane) = self.lane_for_stage(stage) {
            lane.current = current;
            lane.total = total;
        }
    }

    /// Apply a batch progress increment.
    pub fn update_batch(&mut self, stage: &str, batch_size: u64) {
        self.stage = stage.to_string();

        if let Some(lane) = self.lane_for_stage(stage) {
            lane.current += batch_size;
        }
    }

    /// Mark sync as completed with summary.
    pub fn complete(&mut self, elapsed_ms: u64) {
        self.phase = SyncPhase::Complete;
        // Mark all lanes as done.
        for lane in &mut self.lanes {
            lane.done = true;
        }
        // Build summary from lane data if not already set.
        if self.summary.is_none() {
            self.summary = Some(SyncSummary {
                elapsed_ms,
                ..Default::default()
            });
        } else if let Some(ref mut summary) = self.summary {
            summary.elapsed_ms = elapsed_ms;
        }
    }

    /// Mark sync as cancelled.
    pub fn cancel(&mut self) {
        self.phase = SyncPhase::Cancelled;
    }

    /// Mark sync as failed.
    pub fn fail(&mut self, error: String) {
        self.phase = SyncPhase::Failed(error);
    }

    /// Add a log line.
    pub fn add_log_line(&mut self, line: String) {
        self.log_lines.push(line);
        // Auto-scroll to bottom.
        if self.log_lines.len() > 1 {
            self.log_scroll_offset = self.log_lines.len().saturating_sub(20);
        }
    }

    /// Update stream stats.
    pub fn update_stream_stats(&mut self, bytes: u64, items: u64) {
        self.bytes_synced = bytes;
        self.items_synced = items;
        // Compute actual throughput from elapsed time since sync start.
        if items > 0
            && let Some(started) = self.started_at
        {
            let elapsed_secs = started.elapsed().as_secs_f64();
            if elapsed_secs > 0.0 {
                self.items_per_sec = items as f64 / elapsed_secs;
            }
        }
    }

    /// Whether sync is currently running.
    #[must_use]
    pub fn is_running(&self) -> bool {
        self.phase == SyncPhase::Running
    }

    /// Overall progress fraction (average of all lanes).
    #[must_use]
    pub fn overall_progress(&self) -> f64 {
        let active_lanes: Vec<&LaneProgress> = self.lanes.iter().filter(|l| l.total > 0).collect();
        if active_lanes.is_empty() {
            return 0.0;
        }
        let sum: f64 = active_lanes.iter().map(|l| l.fraction()).sum();
        sum / active_lanes.len() as f64
    }

    /// Map a stage name to the corresponding lane.
    fn lane_for_stage(&mut self, stage: &str) -> Option<&mut LaneProgress> {
        let lower = stage.to_lowercase();
        let idx = if lower.contains("issue") {
            Some(0)
        } else if lower.contains("merge") || lower.contains("mr") {
            Some(1)
        } else if lower.contains("discussion") {
            Some(2)
        } else if lower.contains("note") {
            Some(3)
        } else if lower.contains("event") {
            Some(4)
        } else if lower.contains("status") {
            Some(5)
        } else {
            None
        };
        idx.map(|i| &mut self.lanes[i])
    }
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

#[cfg(test)]
mod tests {
    use super::*;
    use std::thread;
    use std::time::Duration;

    #[test]
    fn test_lane_progress_fraction() {
        let lane = LaneProgress {
            current: 50,
            total: 100,
            done: false,
        };
        assert!((lane.fraction() - 0.5).abs() < f64::EPSILON);
    }

    #[test]
    fn test_lane_progress_fraction_zero_total() {
        let lane = LaneProgress::default();
        assert!((lane.fraction()).abs() < f64::EPSILON);
    }

    #[test]
    fn test_sync_summary_total_changes() {
        let summary = SyncSummary {
            issues: EntityChangeCounts { new: 5, updated: 3 },
            merge_requests: EntityChangeCounts { new: 2, updated: 1 },
            ..Default::default()
        };
        assert_eq!(summary.total_changes(), 11);
    }

    #[test]
    fn test_sync_summary_has_errors() {
        let mut summary = SyncSummary::default();
        assert!(!summary.has_errors());

        summary
            .project_errors
            .push(("grp/repo".into(), "timeout".into()));
        assert!(summary.has_errors());
    }

    #[test]
    fn test_sync_state_start_resets() {
        let mut state = SyncState {
            stage: "old".into(),
            phase: SyncPhase::Complete,
            ..SyncState::default()
        };
        state.log_lines.push("old log".into());

        state.start();

        assert_eq!(state.phase, SyncPhase::Running);
        assert!(state.stage.is_empty());
        assert!(state.log_lines.is_empty());
    }

    #[test]
    fn test_sync_state_update_progress() {
        let mut state = SyncState::default();
        state.start();

        state.update_progress("Fetching issues", 10, 50);
        assert_eq!(state.lanes[0].current, 10);
        assert_eq!(state.lanes[0].total, 50);
        assert_eq!(state.stage, "Fetching issues");
    }

    #[test]
    fn test_sync_state_update_batch() {
        let mut state = SyncState::default();
        state.start();

        state.update_batch("MR processing", 5);
        state.update_batch("MR processing", 3);
        assert_eq!(state.lanes[1].current, 8); // MR lane
    }

    #[test]
    fn test_sync_state_complete() {
        let mut state = SyncState::default();
        state.start();

        state.complete(5000);
        assert_eq!(state.phase, SyncPhase::Complete);
        assert!(state.summary.is_some());
        assert_eq!(state.summary.as_ref().unwrap().elapsed_ms, 5000);
    }

    #[test]
    fn test_sync_state_overall_progress() {
        let mut state = SyncState::default();
        state.start();

        state.update_progress("issues", 50, 100);
        state.update_progress("merge requests", 25, 100);
        // Two active lanes: 0.5 and 0.25, average = 0.375
        assert!((state.overall_progress() - 0.375).abs() < 0.01);
    }

    #[test]
    fn test_sync_state_overall_progress_no_active_lanes() {
        let state = SyncState::default();
        assert!((state.overall_progress()).abs() < f64::EPSILON);
    }

    #[test]
    fn test_progress_coalescer_first_always_emits() {
        let mut coalescer = ProgressCoalescer::new(100);
        assert!(coalescer.should_emit());
    }

    #[test]
    fn test_progress_coalescer_rapid_updates_coalesced() {
        let mut coalescer = ProgressCoalescer::new(100);
        assert!(coalescer.should_emit()); // First always emits.

        // Rapid-fire updates within 100ms should be coalesced.
        let mut emitted = 0;
        for _ in 0..50 {
            if coalescer.should_emit() {
                emitted += 1;
            }
        }
        // With ~0ms between calls, at most 0-1 additional emits expected.
        assert!(emitted <= 1, "Expected at most 1 emit, got {emitted}");
    }

    #[test]
    fn test_progress_coalescer_emits_after_floor() {
        let mut coalescer = ProgressCoalescer::new(50);
        assert!(coalescer.should_emit());

        // Wait longer than floor.
        thread::sleep(Duration::from_millis(60));
        assert!(coalescer.should_emit());
    }

    #[test]
    fn test_progress_coalescer_reset() {
        let mut coalescer = ProgressCoalescer::new(100);
        coalescer.should_emit();
        coalescer.should_emit(); // Coalesced.

        coalescer.reset();
        assert!(coalescer.should_emit()); // Fresh start.
    }

    #[test]
    fn test_sync_lane_labels() {
        assert_eq!(SyncLane::Issues.label(), "Issues");
        assert_eq!(SyncLane::MergeRequests.label(), "MRs");
        assert_eq!(SyncLane::Notes.label(), "Notes");
    }

    #[test]
    fn test_sync_state_add_log_line() {
        let mut state = SyncState::default();
        state.add_log_line("line 1".into());
        state.add_log_line("line 2".into());
        assert_eq!(state.log_lines.len(), 2);
        assert_eq!(state.log_lines[0], "line 1");
    }

    #[test]
    fn test_sync_state_cancel() {
        let mut state = SyncState::default();
        state.start();
        state.cancel();
        assert_eq!(state.phase, SyncPhase::Cancelled);
    }

    #[test]
    fn test_sync_state_fail() {
        let mut state = SyncState::default();
        state.start();
        state.fail("network timeout".into());
        assert!(matches!(state.phase, SyncPhase::Failed(_)));
    }
}
