// This code is automatically generated by Senax and is always overwritten.

use anyhow::{Result, bail};
use regex::Regex;
use senax_common::types::blob::FILES;
use std::collections::{BTreeMap, HashMap};
use std::fs;
use std::path::PathBuf;
use std::sync::RwLock;

use crate::{DbConn, connection, models::exec_ddl};

// SEEDS
include!(concat!(env!("OUT_DIR"), "/seeds.rs"));

#[cfg_attr(feature = "seed_schema", derive(::schemars::JsonSchema))]
#[cfg_attr(feature = "seed_schema", schemars(deny_unknown_fields))]
#[allow(dead_code)]
#[allow(non_snake_case)]
pub struct SeedSchema {
@%- for (name, defs) in groups %@@% if !defs.is_empty() %@
    @{ name|ident }@: Option<_base::models::@{ name|snake|ident }@::@{ name|pascal }@>,
@%- endif %@@% endfor %@
}
impl SeedSchema {
    #[allow(clippy::single_match)]
    #[rustfmt::skip]
    pub async fn seed(data: &str) -> Result<()> {
        let seeds: serde_yaml::Value = serde_yaml::from_str(data)?;
        let mut conns: Vec<_> = DbConn::shard_num_range().map(DbConn::_new).collect();
        for conn in conns.iter_mut() {
            conn.begin().await?;
        }
        @%- for unified in unified_joinable %@
        _base_repo_@{ unified }@::repositories::seed(&seeds, &mut conns).await?;
        @%- endfor %@
        for mut conn in conns {
            conn.commit().await?;
        }
        Ok(())
    }
}

pub fn gen_seed_schema() -> Result<()> {
    #[cfg(feature="seed_schema")]
    {
        use schemars::r#gen::SchemaSettings;
        let settings = SchemaSettings::draft07().with(|s| {
            s.option_nullable = false;
            s.option_add_null_type = true;
        });
        let generator = settings.into_generator();
        let schema = generator.into_root_schema_for::<SeedSchema>();
        let schema = serde_json::to_string_pretty(&schema)?;
        let path = std::path::Path::new(file!())
            .parent()
            .unwrap()
            .parent()
            .unwrap()
            .join("seed-schema.json");
        fs::write(path, schema)?;
    }
    Ok(())
}

pub async fn seed(use_test: bool, file_path: Option<PathBuf>) -> Result<()> {
    let mut yml_files = BTreeMap::new();
    if FILES.get().is_none() {
        let _ = FILES.set(RwLock::new(HashMap::new()));
    }
    let re1 = Regex::new(r"(\d+)(_([^/]+)\.yml)?$")?;
    let file_name = file_path
        .as_ref()
        .and_then(|x| x.file_name().map(|y| y.to_string_lossy()));
    let file_num = file_name
        .as_ref()
        .and_then(|x| re1.captures(x.as_ref()).map(|y| y.get(1).unwrap().as_str()));
    let re2 = Regex::new(r"^seeds/(\d+)_([^/]+)\.yml$")?;
    {
        let mut files = FILES.get().unwrap().write().unwrap();
        for name in SEEDS.file_names() {
            if let Some(caps) = re2.captures(name) {
                let content = SEEDS.get(name)?;
                let data = std::str::from_utf8(content.as_ref())?.to_string();
                let name = name.trim_start_matches("seeds/").to_string();
                if let Some(file_num) = file_num {
                    if file_num == caps.get(1).unwrap().as_str() {
                        yml_files.insert(name, data);
                    }
                } else {
                    yml_files.insert(name, data);
                }
            } else {
                files.insert(
                    name.trim_start_matches("seeds/").to_string(),
                    SEEDS.get(name)?,
                );
            }
        }
    }
    if let Some(ref path) = file_path && path.is_file() {
        yml_files.clear();
        let data = fs::read_to_string(path).unwrap();
        yml_files.insert(
            path.file_name().unwrap().to_str().unwrap().to_string(),
            data,
        );
    }
    if file_num.is_some() && yml_files.is_empty() {
        FILES.get().unwrap().write().unwrap().clear();
        bail!("seed file not found!");
    }
    if use_test {
        connection::init_test().await?;
    } else {
        connection::init().await?;
    }
    for (name, data) in yml_files {
        let caps = re1.captures(&name).unwrap();
        let version: i64 = caps.get(1).unwrap().as_str().parse()?;
        let description = caps.get(3).unwrap().as_str().to_string();
        let mut writer = DbConn::_new(0).acquire_writer().await?;
        exec_ddl(
            r#"
                CREATE TABLE IF NOT EXISTS _seeds (
                    version BIGINT PRIMARY KEY,
                    description TEXT NOT NULL,
                    installed_on DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
                );
            "#,
            writer.as_mut(),
        )
        .await?;
        let result = sqlx::query("SELECT version FROM _seeds WHERE version=@{ config.db_type_switch("?", "$1") }@")
            .bind(version)
            .fetch_optional(writer.as_mut())
            .await?;
        if result.is_some() {
            continue;
        }
        SeedSchema::seed(&data).await?;
        sqlx::query("INSERT INTO _seeds (version, description) VALUES @{ config.db_type_switch("(?,?)", "($1,$2)") }@")
            .bind(version)
            .bind(description)
            .execute(writer.as_mut())
            .await?;
    }
    FILES.get().unwrap().write().unwrap().clear();
    Ok(())
}
@{-"\n"}@