use std::path::PathBuf;

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum TuiTaskProgressEvent {
    RootStarted {
        root_index: usize,
        root_count: usize,
        root: PathBuf,
        backend: String,
    },
    RootFinished {
        root_index: usize,
        root_count: usize,
        root: PathBuf,
        status: String,
        logical_bytes: u64,
        files: u64,
        directories: u64,
    },
    Traversal {
        root: PathBuf,
        counter: String,
        value: u64,
        logical_bytes: u64,
        files: u64,
        directories: u64,
    },
    FileMeasured {
        target_path: PathBuf,
        path: PathBuf,
        file_size: u64,
        files_scanned: u64,
        bytes_scanned: u64,
    },
    BackendFallback {
        root: PathBuf,
        backend: String,
        reason: String,
    },
    BackendStage {
        root: PathBuf,
        backend: String,
        stage: &'static str,
        finished: bool,
    },
    BackendMetric {
        metric: &'static str,
        value: u64,
    },
    Finalizing {
        roots: usize,
        logical_bytes: u64,
        files: u64,
        directories: u64,
    },
    CleanupTargetScanning {
        rule_id: String,
        path: PathBuf,
    },
    CleanupTargetFinished {
        rule_id: String,
        path: PathBuf,
        status: String,
        estimated_bytes: u64,
    },
    CleanupFileMeasured {
        rule_id: String,
        target_path: PathBuf,
        path: PathBuf,
        file_size: u64,
        files_scanned: u64,
        bytes_scanned: u64,
    },
    CleanupCacheHit {
        rule_id: String,
        path: PathBuf,
        estimated_bytes: u64,
    },
    CleanupCacheMiss {
        rule_id: String,
        path: PathBuf,
        reason: String,
        pruned: bool,
    },
    CleanupCacheWriteSkipped {
        rule_id: String,
        path: PathBuf,
    },
    CleanupCachePruned {
        inspected: usize,
        pruned: usize,
        retained: usize,
    },
    ExecutionFinished {
        completed_targets: u64,
        freed_bytes: u64,
        pending_reclaim_bytes: u64,
    },
}

impl TuiTaskProgressEvent {
    pub(crate) fn is_coalescible(&self) -> bool {
        matches!(
            self,
            Self::Traversal { .. } | Self::FileMeasured { .. } | Self::CleanupFileMeasured { .. }
        )
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct TuiTaskStatus {
    pub(crate) label: String,
    pub(crate) phase: String,
    pub(crate) backend: Option<String>,
    pub(crate) current_root: Option<PathBuf>,
    pub(crate) current_path: Option<PathBuf>,
    pub(crate) current_rule_id: Option<String>,
    pub(crate) roots_finished: usize,
    pub(crate) root_count: usize,
    pub(crate) logical_bytes: u64,
    pub(crate) files: u64,
    pub(crate) directories: u64,
    pub(crate) targets_started: u64,
    pub(crate) targets_finished: u64,
    pub(crate) estimated_bytes: u64,
    pub(crate) cache_hits: u64,
    pub(crate) cache_misses: u64,
    pub(crate) cache_write_skipped: u64,
    pub(crate) cache_pruned: usize,
    pub(crate) last_event: String,
    pub(crate) cancel_requested: bool,
}

impl TuiTaskStatus {
    pub(crate) fn started(label: impl Into<String>) -> Self {
        let label = label.into();
        Self {
            phase: label.clone(),
            label,
            backend: None,
            current_root: None,
            current_path: None,
            current_rule_id: None,
            roots_finished: 0,
            root_count: 0,
            logical_bytes: 0,
            files: 0,
            directories: 0,
            targets_started: 0,
            targets_finished: 0,
            estimated_bytes: 0,
            cache_hits: 0,
            cache_misses: 0,
            cache_write_skipped: 0,
            cache_pruned: 0,
            last_event: String::new(),
            cancel_requested: false,
        }
    }

    pub(crate) fn apply_event(&mut self, event: TuiTaskProgressEvent) {
        match event {
            TuiTaskProgressEvent::RootStarted {
                root_index,
                root_count,
                root,
                backend,
            } => {
                self.phase = format!("Scanning root {}/{}", root_index + 1, root_count);
                self.backend = Some(backend);
                self.current_root = Some(root.clone());
                self.current_path = Some(root.clone());
                self.root_count = root_count;
                self.last_event = format!("Started {}", root.display());
            }
            TuiTaskProgressEvent::RootFinished {
                root_index,
                root_count,
                root,
                status: root_status,
                logical_bytes,
                files,
                directories,
            } => {
                self.phase = format!("Finished root {}/{}", root_index + 1, root_count);
                self.current_root = Some(root.clone());
                self.current_path = Some(root.clone());
                self.roots_finished = self.roots_finished.max(root_index + 1);
                self.root_count = root_count;
                self.logical_bytes = logical_bytes;
                self.files = files;
                self.directories = directories;
                self.last_event = format!("{root_status}: {}", root.display());
            }
            TuiTaskProgressEvent::Traversal {
                root,
                counter,
                value,
                logical_bytes,
                files,
                directories,
            } => {
                self.phase = format!("Walking {counter} {value}");
                self.current_root = Some(root);
                self.logical_bytes = logical_bytes;
                self.files = files;
                self.directories = directories;
                self.last_event = format!("{counter}: {value}");
            }
            TuiTaskProgressEvent::FileMeasured {
                target_path,
                path,
                file_size,
                files_scanned,
                bytes_scanned,
            } => {
                self.phase = "Measuring files".to_string();
                self.current_root = Some(target_path.clone());
                self.current_path = Some(path.clone());
                self.files = files_scanned;
                self.logical_bytes = bytes_scanned;
                self.last_event = format!("{} ({file_size} bytes)", path.display());
            }
            TuiTaskProgressEvent::BackendFallback {
                root,
                backend,
                reason,
            } => {
                self.phase = "Backend fallback".to_string();
                self.backend = Some(backend.clone());
                self.current_root = Some(root.clone());
                self.current_path = Some(root.clone());
                self.last_event = format!("{backend}: {reason}");
            }
            TuiTaskProgressEvent::BackendStage {
                root,
                backend,
                stage,
                finished,
            } => {
                self.phase = if finished {
                    format!("{stage} finished")
                } else {
                    format!("{stage} started")
                };
                self.backend = Some(backend);
                self.current_root = Some(root.clone());
                self.current_path = Some(root);
                self.last_event = self.phase.clone();
            }
            TuiTaskProgressEvent::BackendMetric { metric, value } => {
                self.phase = "Reading backend metrics".to_string();
                self.last_event = format!("{metric}: {value}");
            }
            TuiTaskProgressEvent::Finalizing {
                roots,
                logical_bytes,
                files,
                directories,
            } => {
                self.phase = "Finalizing map".to_string();
                self.root_count = roots;
                self.roots_finished = roots;
                self.logical_bytes = logical_bytes;
                self.files = files;
                self.directories = directories;
                self.last_event = "Building ranked tree".to_string();
            }
            TuiTaskProgressEvent::CleanupTargetScanning { rule_id, path } => {
                self.phase = "Scanning cleanup target".to_string();
                self.current_rule_id = Some(rule_id.clone());
                self.current_path = Some(path.clone());
                self.targets_started = self.targets_started.saturating_add(1);
                self.last_event = format!("{rule_id}: {}", path.display());
            }
            TuiTaskProgressEvent::CleanupTargetFinished {
                rule_id,
                path,
                status: target_status,
                estimated_bytes,
            } => {
                self.phase = "Measured cleanup target".to_string();
                self.current_rule_id = Some(rule_id.clone());
                self.current_path = Some(path.clone());
                self.targets_finished = self.targets_finished.saturating_add(1);
                self.estimated_bytes = self.estimated_bytes.saturating_add(estimated_bytes);
                self.last_event = format!("{rule_id} {target_status}: {}", path.display());
            }
            TuiTaskProgressEvent::CleanupFileMeasured {
                rule_id,
                target_path,
                path,
                file_size,
                files_scanned,
                bytes_scanned,
            } => {
                self.phase = "Measuring cleanup files".to_string();
                self.current_rule_id = Some(rule_id);
                self.current_root = Some(target_path);
                self.current_path = Some(path.clone());
                self.files = files_scanned;
                self.logical_bytes = bytes_scanned;
                self.last_event = format!("{} ({file_size} bytes)", path.display());
            }
            TuiTaskProgressEvent::CleanupCacheHit {
                rule_id,
                path,
                estimated_bytes,
            } => {
                self.phase = "Using scan cache".to_string();
                self.current_rule_id = Some(rule_id.clone());
                self.current_path = Some(path.clone());
                self.cache_hits = self.cache_hits.saturating_add(1);
                self.estimated_bytes = self.estimated_bytes.saturating_add(estimated_bytes);
                self.last_event = format!("{rule_id} cache hit: {}", path.display());
            }
            TuiTaskProgressEvent::CleanupCacheMiss {
                rule_id,
                path,
                reason,
                pruned,
            } => {
                self.phase = "Refreshing scan cache".to_string();
                self.current_rule_id = Some(rule_id.clone());
                self.current_path = Some(path.clone());
                self.cache_misses = self.cache_misses.saturating_add(1);
                self.cache_pruned += usize::from(pruned);
                self.last_event = format!("{rule_id} cache miss {reason}: {}", path.display());
            }
            TuiTaskProgressEvent::CleanupCacheWriteSkipped { rule_id, path } => {
                self.phase = "Scan cache write skipped".to_string();
                self.current_rule_id = Some(rule_id.clone());
                self.current_path = Some(path.clone());
                self.cache_write_skipped = self.cache_write_skipped.saturating_add(1);
                self.last_event = format!("{rule_id}: {}", path.display());
            }
            TuiTaskProgressEvent::CleanupCachePruned {
                inspected,
                pruned,
                retained,
            } => {
                self.phase = "Pruning scan cache".to_string();
                self.cache_pruned = self.cache_pruned.saturating_add(pruned);
                self.last_event =
                    format!("cache inspected {inspected}, pruned {pruned}, retained {retained}");
            }
            TuiTaskProgressEvent::ExecutionFinished {
                completed_targets,
                freed_bytes,
                pending_reclaim_bytes,
            } => {
                self.phase = "Cleanup execution finished".to_string();
                self.targets_finished = completed_targets;
                self.logical_bytes = freed_bytes.saturating_add(pending_reclaim_bytes);
                self.last_event = format!("{completed_targets} target(s) completed");
            }
        }
    }

    pub(crate) fn mark_cancel_requested(&mut self) {
        self.cancel_requested = true;
        self.phase = "Cancel requested".to_string();
    }

    pub(crate) fn cancel_wait_message(&self) -> &'static str {
        if self.label.contains("recoverable trash") || self.targets_started > 0 {
            "Cancel requested; cleanup will stop before the next target or after the current trash operation returns."
        } else {
            "Cancel requested; waiting for cooperative checkpoint."
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct TuiTaskId(pub(crate) u64);

#[cfg(test)]
mod tests {
    use std::path::Path;

    use super::*;

    #[test]
    fn traversal_updates_structured_status() {
        let mut status = TuiTaskStatus::started("Scanning fixture...");

        status.apply_event(TuiTaskProgressEvent::Traversal {
            root: PathBuf::from("/tmp"),
            counter: "files".to_string(),
            value: 8,
            logical_bytes: 42,
            files: 8,
            directories: 2,
        });

        assert_eq!(status.phase, "Walking files 8");
        assert_eq!(status.current_root, Some(PathBuf::from("/tmp")));
        assert_eq!(status.files, 8);
        assert_eq!(status.directories, 2);
        assert_eq!(status.logical_bytes, 42);
        assert_eq!(status.last_event, "files: 8");
    }

    #[test]
    fn cleanup_cache_events_accumulate_counts_and_bytes() {
        let mut status = TuiTaskStatus::started("Preview cleanup...");

        status.apply_event(TuiTaskProgressEvent::CleanupCacheHit {
            rule_id: "linux.user-cache".to_string(),
            path: PathBuf::from("/home/me/.cache/app"),
            estimated_bytes: 1024,
        });
        status.apply_event(TuiTaskProgressEvent::CleanupCacheMiss {
            rule_id: "linux.user-cache".to_string(),
            path: PathBuf::from("/home/me/.cache/other"),
            reason: "stale".to_string(),
            pruned: true,
        });
        status.apply_event(TuiTaskProgressEvent::CleanupCacheWriteSkipped {
            rule_id: "linux.user-cache".to_string(),
            path: PathBuf::from("/home/me/.cache/readonly"),
        });

        assert_eq!(status.cache_hits, 1);
        assert_eq!(status.cache_misses, 1);
        assert_eq!(status.cache_write_skipped, 1);
        assert_eq!(status.cache_pruned, 1);
        assert_eq!(status.estimated_bytes, 1024);
        assert_eq!(status.current_rule_id.as_deref(), Some("linux.user-cache"));
        assert_eq!(
            status.current_path,
            Some(PathBuf::from("/home/me/.cache/readonly"))
        );
    }

    #[test]
    fn execution_event_tracks_completed_reclaim_bytes() {
        let mut status = TuiTaskStatus::started("Executing cleanup...");

        status.apply_event(TuiTaskProgressEvent::ExecutionFinished {
            completed_targets: 2,
            freed_bytes: 128,
            pending_reclaim_bytes: 384,
        });

        assert_eq!(status.phase, "Cleanup execution finished");
        assert_eq!(status.targets_finished, 2);
        assert_eq!(status.logical_bytes, 512);
        assert_eq!(status.last_event, "2 target(s) completed");
    }

    #[test]
    fn backend_fallback_and_cancel_request_update_status() {
        let mut status = TuiTaskStatus::started("Scanning fixture...");

        status.apply_event(TuiTaskProgressEvent::BackendFallback {
            root: PathBuf::from("/tmp"),
            backend: "portable".to_string(),
            reason: "MFT unavailable".to_string(),
        });
        status.mark_cancel_requested();

        assert_eq!(status.backend.as_deref(), Some("portable"));
        assert_eq!(status.current_path.as_deref(), Some(Path::new("/tmp")));
        assert_eq!(status.last_event, "portable: MFT unavailable");
        assert!(status.cancel_requested);
        assert_eq!(status.phase, "Cancel requested");
        assert_eq!(
            status.cancel_wait_message(),
            "Cancel requested; waiting for cooperative checkpoint."
        );
    }

    #[test]
    fn execution_cancel_request_reports_target_boundary() {
        let mut status = TuiTaskStatus::started("Moving allowed targets to recoverable trash...");

        status.mark_cancel_requested();

        assert_eq!(
            status.cancel_wait_message(),
            "Cancel requested; cleanup will stop before the next target or after the current trash operation returns."
        );
    }

    #[test]
    fn only_high_volume_progress_events_are_coalescible() {
        assert!(
            TuiTaskProgressEvent::Traversal {
                root: PathBuf::from("/tmp"),
                counter: "files".to_string(),
                value: 10,
                logical_bytes: 128,
                files: 10,
                directories: 2,
            }
            .is_coalescible()
        );
        assert!(
            TuiTaskProgressEvent::FileMeasured {
                target_path: PathBuf::from("/tmp"),
                path: PathBuf::from("/tmp/a.bin"),
                file_size: 128,
                files_scanned: 1,
                bytes_scanned: 128,
            }
            .is_coalescible()
        );
        assert!(
            !TuiTaskProgressEvent::BackendFallback {
                root: PathBuf::from("/tmp"),
                backend: "portable".to_string(),
                reason: "fallback".to_string(),
            }
            .is_coalescible()
        );
        assert!(
            !TuiTaskProgressEvent::ExecutionFinished {
                completed_targets: 1,
                freed_bytes: 128,
                pending_reclaim_bytes: 0,
            }
            .is_coalescible()
        );
    }
}
