use derive_builder::Builder;
use once_cell::sync::Lazy;
use semver::Version;
/// A simple file based database for storing the migration status.
///
use serde::{Deserialize, Serialize};
use std::{borrow::Cow, collections::HashMap, io::Write, path::PathBuf};

use super::MigrationState;

/// A lockfile store the migrated version and the state.
/// The lower version of the migration will be ignored.
static MIGRATION_LOCK_FILE: Lazy<PathBuf> = Lazy::new(|| {
    let mut path = crate::utils::dirs::app_config_dir().unwrap();
    path.push("migration.lock");
    path
});

#[derive(Debug, Clone, Serialize, Deserialize, Builder)]
#[builder(default)]
pub struct MigrationFile<'a> {
    pub version: Cow<'a, Version>,
    pub states: HashMap<Cow<'a, str>, MigrationState>,
}

impl MigrationFileBuilder<'_> {
    pub fn read_file(mut self) -> Self {
        let content = std::fs::read_to_string(&*MIGRATION_LOCK_FILE).ok();
        if let Some(content) = content {
            let file: Option<MigrationFile> = serde_yaml::from_str(&content).ok();
            if let Some(file) = file {
                self.version = Some(file.version);
                self.states = Some(file.states);
            }
        }
        self
    }
}

impl Default for MigrationFile<'_> {
    fn default() -> Self {
        // since 1.6.0, we have introduced the migration system. so the last version of 1.5.x is 1.5.1.
        let ver = Version::parse("1.5.1").unwrap();
        Self {
            version: Cow::Owned(ver),
            states: HashMap::new(),
        }
    }
}

impl<'a> MigrationFile<'a> {
    /// Create or Truncate the lock file and write the content.
    pub fn write_file(&self) -> Result<(), std::io::Error> {
        let content = serde_yaml::to_string(self).map_err(|e| {
            log::error!("Failed to serialize the migration file: {}", e);
            std::io::Error::new(std::io::ErrorKind::Other, e)
        })?;
        let mut file = std::fs::OpenOptions::new()
            .write(true)
            .create(true)
            .truncate(true)
            .open(&*MIGRATION_LOCK_FILE)?;
        file.write(
            "# This file is generated by the migration system, do not edit it manually.\n"
                .as_bytes(),
        )
        .map_err(|e| {
            log::error!("Failed to write the migration file: {}", e);
            e
        })?;
        file.write_all(content.as_bytes())
    }

    pub fn get_state(&self, name: &str) -> Option<MigrationState> {
        self.states.get(name).copied()
    }

    pub fn set_state(&mut self, name: Cow<'a, str>, state: MigrationState) {
        self.states.insert(name, state);
    }
}
