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

use anyhow::{bail, Result};
use regex::Regex;
use schemars::gen::SchemaSettings;
use schemars::schema::{InstanceType, Schema, SchemaObject, SingleOrVec};
use schemars::JsonSchema;
use senax_common::types::blob::FILES;
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap};
use std::fs;
use std::path::PathBuf;
use std::sync::RwLock;

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

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

#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema)]
#[serde(deny_unknown_fields)]
#[allow(non_snake_case)]
pub struct SeedSchema {
@%- for (name, defs) in groups %@@% if !defs.is_empty() %@
    @{ name|to_var_name }@: Option<crate::models::@{ name|snake|to_var_name }@::@{ name|pascal }@>,
@%- endif %@@% endfor %@
}
impl SeedSchema {
    #[allow(clippy::single_match)]
    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?;
        }
        if let Some(mapping) = seeds.as_mapping() {
            for (name, value) in mapping {
                match name.as_str() {
                @%- for (name, defs) in groups %@@% if !defs.is_empty() %@
                    Some("@{ name }@") => crate::models::@{ name|snake|to_var_name }@::seed(value, &mut conns).await?,
                @%- endif %@@% endfor %@
                    _ => {}
                }
            }
        }
        for mut conn in conns {
            conn.commit().await?;
        }
        Ok(())
    }
}

pub fn gen_seed_schema() -> Result<()> {
    let settings = SchemaSettings::draft07().with(|s| {
        s.option_nullable = false;
        s.option_add_null_type = true;
    });
    let gen = settings.into_generator();
    let schema = gen.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 {
        if 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=?")
            .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 (?,?)")
            .bind(version)
            .bind(description)
            .execute(writer.as_mut())
            .await?;
    }
    FILES.get().unwrap().write().unwrap().clear();
    Ok(())
}

#[allow(dead_code)]
pub(crate) fn id_schema(_: &mut schemars::gen::SchemaGenerator) -> Schema {
    let schema = SchemaObject {
        instance_type: Some(SingleOrVec::Vec(vec![
            InstanceType::String,
            InstanceType::Integer,
        ])),
        ..Default::default()
    };
    Schema::Object(schema)
}
@{-"\n"}@