use std::sync::Arc;

use meilisearch_types::milli::progress::{AtomicSubStep, NamedStep, Progress, ProgressView};
use meilisearch_types::milli::{make_atomic_progress, make_enum_progress};
use roaring::RoaringBitmap;

use crate::utils::ProcessingBatch;

#[derive(Clone, Default)]
pub struct ProcessingTasks {
    pub batch: Option<Arc<ProcessingBatch>>,
    /// The list of tasks ids that are currently running.
    pub processing: Arc<RoaringBitmap>,
    /// The progress on processing tasks
    pub progress: Option<Progress>,
}

impl ProcessingTasks {
    /// Creates an empty `ProcessingAt` struct.
    pub fn new() -> ProcessingTasks {
        ProcessingTasks::default()
    }

    pub fn get_progress_view(&self) -> Option<ProgressView> {
        self.progress.as_ref()?.as_progress_view()
    }

    /// Stores the currently processing tasks, and the date time at which it started.
    pub fn start_processing(
        &mut self,
        processing_batch: ProcessingBatch,
        processing: RoaringBitmap,
    ) -> Progress {
        self.batch = Some(Arc::new(processing_batch));
        self.processing = Arc::new(processing);
        let progress = Progress::default();
        progress.update_progress(BatchProgress::ProcessingTasks);
        self.progress = Some(progress.clone());

        progress
    }

    /// Set the processing tasks to an empty list
    pub fn stop_processing(&mut self) -> Self {
        Self {
            batch: std::mem::take(&mut self.batch),
            processing: std::mem::take(&mut self.processing),
            progress: std::mem::take(&mut self.progress),
        }
    }

    /// Returns `true` if there, at least, is one task that is currently processing that we must stop.
    pub fn must_cancel_processing_tasks(&self, canceled_tasks: &RoaringBitmap) -> bool {
        !self.processing.is_disjoint(canceled_tasks)
    }
}

make_enum_progress! {
    pub enum BatchProgress {
        ProcessingTasks,
        WritingTasksToDisk,
    }
}

make_enum_progress! {
    pub enum FinalizingIndexStep {
        Committing,
        ComputingStats,
    }
}

make_enum_progress! {
    pub enum TaskCancelationProgress {
        RetrievingTasks,
        CancelingUpgrade,
        CleaningCompactionLeftover,
        UpdatingTasks,
    }
}

make_enum_progress! {
    pub enum TaskDeletionProgress {
        RetrievingTasks,
        RetrievingBatchTasks,
        DeletingTasksDateTime,
        DeletingBatchesDateTime,
        DeletingTasksMetadata,
        DeletingBatchesMetadata,
        DeletingTasks,
        DeletingBatches,
    }
}

make_enum_progress! {
    pub enum SnapshotCreationProgress {
        StartTheSnapshotCreation,
        SnapshotTheIndexScheduler,
        SnapshotTheUpdateFiles,
        SnapshotTheIndexes,
        SnapshotTheApiKeys,
        CreateTheTarball,
    }
}

make_enum_progress! {
    pub enum DumpCreationProgress {
        StartTheDumpCreation,
        DumpTheApiKeys,
        DumpTheChatCompletionSettings,
        DumpTheTasks,
        DumpTheBatches,
        DumpTheIndexes,
        DumpTheExperimentalFeatures,
        DumpTheWebhooks,
        DumpDynamicSearchRules,
        CompressTheDump,
    }
}

make_enum_progress! {
    pub enum CreateIndexProgress {
        CreatingTheIndex,
    }
}

make_enum_progress! {
    pub enum UpdateIndexProgress {
        UpdatingTheIndex,
    }
}

make_enum_progress! {
    pub enum DeleteIndexProgress {
        DeletingTheIndex,
    }
}

make_enum_progress! {
    pub enum SwappingTheIndexes {
        EnsuringCorrectnessOfTheSwap,
        SwappingTheIndexes,
    }
}

make_enum_progress! {
    pub enum IndexCompaction {
        RetrieveTheIndex,
        CreateTemporaryFile,
        CopyAndCompactTheIndex,
        PersistTheCompactedIndex,
        CloseTheIndex,
        ReopenTheIndex,
    }
}

make_enum_progress! {
    pub enum InnerSwappingTwoIndexes {
        RetrieveTheTasks,
        UpdateTheTasks,
        UpdateTheIndexesMetadata,
    }
}

make_enum_progress! {
    pub enum DocumentOperationProgress {
        RetrievingConfig,
        ComputingDocumentChanges,
        ReadingPayloadStats,
        Indexing,
    }
}

make_enum_progress! {
    pub enum DocumentEditionProgress {
        RetrievingConfig,
        ComputingDocumentChanges,
        Indexing,
    }
}

make_enum_progress! {
    pub enum DocumentDeletionProgress {
        RetrievingConfig,
        DeleteDocuments,
        Indexing,
    }
}

make_enum_progress! {
    pub enum SettingsProgress {
        RetrievingAndMergingTheSettings,
        ApplyTheSettings,
    }
}

make_enum_progress! {
    pub enum Export {
        EnsuringCorrectnessOfTheTarget,
        ExportingTheSettings,
        ExportingTheDocuments,
    }
}

pub mod network {
    use meilisearch_types::milli::make_enum_progress;
    use meilisearch_types::tasks::network::NetworkTopologyState as DbNetworkTopologyState;

    make_enum_progress! {
            pub enum NetworkTopologyState {
                WaitingForOlderTasks,
                ExportingDocuments,
                ImportingDocuments,
                WaitingForOthers,
                DeletingDocuments,
                Finished,
        }
    }

    impl From<DbNetworkTopologyState> for self::NetworkTopologyState {
        fn from(value: DbNetworkTopologyState) -> Self {
            match value {
                DbNetworkTopologyState::WaitingForOlderTasks => Self::WaitingForOlderTasks,
                DbNetworkTopologyState::ExportingDocuments => Self::ExportingDocuments,
                DbNetworkTopologyState::ImportingDocuments => Self::ImportingDocuments,
                DbNetworkTopologyState::WaitingForOthers => Self::WaitingForOthers,
                DbNetworkTopologyState::DeletingDocuments => Self::DeletingDocuments,
                DbNetworkTopologyState::Finished => Self::Finished,
            }
        }
    }

    #[cfg(feature = "enterprise")] // only used in enterprise edition for now
    /// used in VariableNamedStep while ExportingDocuments
    pub enum ExportIndex {}

    #[cfg(feature = "enterprise")] // only used in enterprise edition for now
    /// used in VariableNamedStep while deleting documents
    pub enum DeleteDocumentsFromIndex {}

    #[cfg(feature = "enterprise")] // only used in enterprise edition for now
    /// used in VariableNamedStep while waiting for other remotes to finish importing
    pub enum ImportRemotes {}
}

make_atomic_progress!(Task alias AtomicTaskStep => "task" );
make_atomic_progress!(Document alias AtomicDocumentStep => "document" );
make_atomic_progress!(Index alias AtomicIndexStep => "index" );
make_atomic_progress!(Batch alias AtomicBatchStep => "batch" );
make_atomic_progress!(UpdateFile alias AtomicUpdateFileStep => "update file" );

#[cfg(test)]
mod test {
    use std::sync::atomic::Ordering;

    use meili_snap::{json_string, snapshot};

    use super::*;

    #[test]
    fn one_level() {
        let mut processing = ProcessingTasks::new();
        processing.start_processing(ProcessingBatch::new(0), RoaringBitmap::new());
        snapshot!(json_string!(processing.get_progress_view()), @r#"
        {
          "steps": [
            {
              "currentStep": "processing tasks",
              "finished": 0,
              "total": 2
            }
          ],
          "percentage": 0.0
        }
        "#);
        processing.progress.as_ref().unwrap().update_progress(BatchProgress::WritingTasksToDisk);
        snapshot!(json_string!(processing.get_progress_view()), @r#"
        {
          "steps": [
            {
              "currentStep": "writing tasks to disk",
              "finished": 1,
              "total": 2
            }
          ],
          "percentage": 50.0
        }
        "#);
    }

    #[test]
    fn task_progress() {
        let mut processing = ProcessingTasks::new();
        processing.start_processing(ProcessingBatch::new(0), RoaringBitmap::new());
        let (atomic, tasks) = AtomicTaskStep::new(10);
        processing.progress.as_ref().unwrap().update_progress(tasks);
        snapshot!(json_string!(processing.get_progress_view()), @r#"
        {
          "steps": [
            {
              "currentStep": "processing tasks",
              "finished": 0,
              "total": 2
            },
            {
              "currentStep": "task",
              "finished": 0,
              "total": 10
            }
          ],
          "percentage": 0.0
        }
        "#);
        atomic.fetch_add(6, Ordering::Relaxed);
        snapshot!(json_string!(processing.get_progress_view()), @r#"
        {
          "steps": [
            {
              "currentStep": "processing tasks",
              "finished": 0,
              "total": 2
            },
            {
              "currentStep": "task",
              "finished": 6,
              "total": 10
            }
          ],
          "percentage": 30.000002
        }
        "#);
        processing.progress.as_ref().unwrap().update_progress(BatchProgress::WritingTasksToDisk);
        snapshot!(json_string!(processing.get_progress_view()), @r#"
        {
          "steps": [
            {
              "currentStep": "writing tasks to disk",
              "finished": 1,
              "total": 2
            }
          ],
          "percentage": 50.0
        }
        "#);
        let (atomic, tasks) = AtomicTaskStep::new(5);
        processing.progress.as_ref().unwrap().update_progress(tasks);
        atomic.fetch_add(4, Ordering::Relaxed);
        snapshot!(json_string!(processing.get_progress_view()), @r#"
        {
          "steps": [
            {
              "currentStep": "writing tasks to disk",
              "finished": 1,
              "total": 2
            },
            {
              "currentStep": "task",
              "finished": 4,
              "total": 5
            }
          ],
          "percentage": 90.0
        }
        "#);
    }
}
