use std::collections::HashMap;
use std::fs;
use std::io::Write;
use std::path::{Path, PathBuf};

use lazy_static::lazy_static;
use regex::Regex;
use tracing::{debug, error, info, trace};
use tracing_subscriber::{fmt, EnvFilter};

use config::{AutoConfig, RepairConfig};
use cvrf_xmlparser::{
    CVRF,
    // SaInfo 即为 CUSA
    // SaInfo,
};

pub mod cli;

mod config;

/// 定义 crate::Error
/// 大部分函数返回的错误
pub type Error = Box<dyn std::error::Error + Send + Sync>;

/// 定义 crate::Result
pub type Result<T> = std::result::Result<T, Error>;

lazy_static! {
    pub static ref NVR_RE: Regex = Regex::new(
        // %{name}-%{version}-%{release}.[oexxxx.xxx.rpm]
        r"^([a-zA-Z0-9\-_+]+)-([0-9a-zA-Z\._+]+)-([0-9a-zA-Z\._-]+).(oe[0-9a-z]+.[0-9a-z]+.rpm)"
    )
    .unwrap();
}

pub fn cumain() -> Result<()> {
    set_up_logging()?;
    let cli = cli::parse();

    match cli.subcommand {
        cli::CliSub::Convert(cli) => covert(&cli),
        cli::CliSub::Db(cli) => sadb(&cli),
        cli::CliSub::Auto(cli) => auto(&cli),
    }
}

fn set_up_logging() -> crate::Result<()> {
    fmt()
        .with_ansi(false)
        .with_timer(fmt::time::OffsetTime::local_rfc_3339().unwrap())
        .with_line_number(true)
        .with_env_filter(EnvFilter::from_default_env())
        .try_init()
}

/// 可使用 convert 函数将 cvrf 格式文件转换并输出至指定的 cusa 文件。
///
/// 例：
///
/// ```no_run
/// use cvrf2cusa::cli::ConvertCli
///
/// let cli = ConvertCli::new(
///     // input
///     "xxx-cvrf.xml".to_string,
///     // output
///     Some("pkg_version_said.json".to_string),
///     // print to screen
///     false,
/// );
///
/// cvrf2cusa::Convert(cli)?;
/// ```
pub fn covert(cli: &cli::ConvertCli) -> Result<()> {
    // 检查 input，此为必须项
    let input = Path::new(&cli.input);
    if !input.is_file() {
        return Err("输入的文件不是有效的文件路径！".into());
    }

    // 从 input 读取 cvrf，并转换为 cusa
    let mut cvrf = CVRF::new();
    cvrf.load_xml(&cli.input)?;
    let cusa = cvrf.sainfo();

    let data = serde_json::to_string_pretty(&cusa)?;

    // 是否将 cusa 输出至指定文件
    if let Some(output) = &cli.output {
        let output = Path::new(output);

        // output 所在父路径须提前创建
        let parent = output.parent().unwrap();
        if !parent.exists() {
            return Err("无法访问输出文件所在路径，请确认！".into());
        }

        // 写入文件
        let mut json_file = fs::OpenOptions::new()
            .read(true)
            .write(true)
            .create(true)
            .open(output)?;
        json_file.write(data.as_bytes())?;
        json_file.flush()?;

        // 在输出到文件模式下，默认不再标准输出打印 CUSA
        if !cli.print {
            return Ok(());
        }
    }

    // 在标准输出打印 CUSA
    println!("{}", data);
    Ok(())
}

pub fn sadb(cli: &cli::SaDbCli) -> Result<()> {
    let source = &cli.from;
    // 只要文件
    let files = walk_dir(source, true);

    let mut sadb = HashMap::new();
    let mut cvedb = HashMap::new();

    for _file in files {
        match _file.extension() {
            Some(tail) => {
                if tail != "xml" {
                    continue;
                }

                // 从 xml 中读取 cvrf 并转换为 cusa
                let file = if let Some(file) = _file.to_str() {
                    file
                } else {
                    continue;
                };
                let mut cvrf = CVRF::new();
                let _ = cvrf.load_xml(file);
                let sa = cvrf.sainfo();

                // cvedb
                sa.cves.clone().into_iter().for_each(|cve| {
                    cvedb.insert(cve.id.clone(), cve);
                });

                // sadb
                // 很烦，华为干的这事儿
                // 2022/cvrf-openEuler-SA-2022-2063.xml 里的 ID 为 openEuler-SA-2022-2046
                // 暂不处理
                // TODO
                if let Some(_) = sadb.insert(sa.id.clone(), sa) {
                    // unimplemented!();
                }
            }
            _ => {}
        }
    }

    // 写入 sadb 文件
    let data = serde_json::to_string_pretty(&sadb)?;
    let mut sadb_file = fs::OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .open(&cli.sa)?;
    sadb_file.write(data.as_bytes())?;
    sadb_file.flush()?;

    // 写入 cvedb 文件
    let data = serde_json::to_string_pretty(&cvedb)?;
    let mut cvedb_file = fs::OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .open(&cli.cve)?;
    cvedb_file.write(data.as_bytes())?;
    cvedb_file.flush()?;

    Ok(())
}

// 递归去读一个路径，返回一个文件列表
#[allow(dead_code)]
fn walk_dir<P: AsRef<Path>>(path: P, nodir: bool) -> Vec<PathBuf> {
    let mut res = vec![];
    for entry in std::fs::read_dir(path).expect("read_dir call failed!") {
        let entry = entry.unwrap().path();
        if entry.is_dir() {
            if !nodir {
                res.push(entry.clone());
            }
            res.append(&mut walk_dir(entry, nodir));
        } else {
            // 暂不处理链接等情况
            res.push(entry);
        }
    }

    res
}

/// 从配置文件中读取 cvrf 和 cusa 数据库的路径，并执行自动转换操作
pub fn auto(cli: &cli::AutoCli) -> Result<()> {
    let auto = if cli.source.is_some() && cli.target.is_some() {
        let auto = AutoConfig::new(
            cli.source.as_ref().unwrap().to_owned(),
            cli.target.as_ref().unwrap().to_owned(),
        );
        info!("从参数中读取 AutoConfig: {:#?}", auto);
        auto
    } else {
        // 默认配置为，但也可读取执行命令路径下的配置
        let config = {
            let default = Path::new("/etc/cuvars/cvrf2cusa.json");
            if default.is_file() {
                default.to_str().unwrap()
            } else {
                "cvrf2cusa.json"
            }
        };
        info!("Config file is: {}", config);

        let auto = config::AutoConfig::from(config)?;
        info!("Load AutoConfig: {:#?}", auto);
        auto
    };

    let files = walk_dir(auto.source(), true);

    for _file in files {
        match _file.extension() {
            Some(tail) => {
                if tail != "xml" {
                    continue;
                }

                // 从 xml 中读取 cvrf 并转换为 cusa
                let file = if let Some(file) = _file.to_str() {
                    file
                } else {
                    continue;
                };
                trace!("parsing {}", file);
                let mut cvrf = CVRF::new();
                let _ = cvrf.load_xml(file);

                _save_2_cusa_db(auto.target(), &cvrf)?;
            }
            _ => {}
        }
    }
    Ok(())
}

fn _save_2_cusa_db(dbpath: &str, cvrf: &CVRF) -> Result<()> {
    let mut db = PathBuf::from(dbpath);

    let component = cvrf.affected_component().unwrap();
    trace!("Get affected_component: {}", component);
    // 这里随便取一个 src 包名
    let _src = cvrf.producttree.packages["src"][0].content.as_str();

    /*
    // TODO: may empty
    if _src == "" {
        error!(
            "{}: ProductTree->packages[\"src\"] have empty productid, ignore it!",
            cvrf.id()
        );
        return Ok(());
    }
    */
    db.push(_src.chars().next().unwrap().to_lowercase().to_string());
    // 不理会此问题
    // db: "cusas/l/log4j,mybatis,netty,springframework,wildfly-security-manager,wildfly-elytron,wildfly-build-tools,wildfly-common,wildfly-core,thrift,json-lib,datanucleus-core,jgroups,mx4j,jboss-logging,infinispan,datanucleus-rdbms,avalon-logkit,datanucleus-api-jdo,avalon-framework,HikariCP,metrics"
    // Error: Os { code: 63, kind: InvalidFilename, message: "File name too long" }
    db.push(component);

    // 读取修复配置
    let mut repairconf = db.clone();
    repairconf.push("config.json");
    let repairconfig = match config::RepairConfig::from(&repairconf) {
        Ok(repairconfig) => repairconfig,
        _ => {
            // 这里临时处理下，不应该创建
            // TO BE DELETE
            let parent = repairconf.parent().unwrap();
            match fs::create_dir_all(parent) {
                Ok(_) => {}
                Err(e) => {
                    error!(
                        "sa_id: {}, create_dir_all({:?}): {}",
                        cvrf.id(),
                        parent,
                        e.to_string()
                    );
                    return Ok(());
                }
            }
            let repairconfig = RepairConfig::new("22.03-LTS".to_string(), true, "".to_string());
            // 写入默认配置
            let data = serde_json::to_string_pretty(&repairconfig)?;
            let mut repair = fs::OpenOptions::new()
                .read(true)
                .write(true)
                .create(true)
                .open(repairconf)?;
            repair.write(data.as_bytes())?;
            repair.flush()?;

            repairconfig
        }
    };

    // 是否影响当前软件包，即作上游判断
    let mut not_affected = true;
    for product in cvrf.affected_products() {
        if product.productid.ends_with(repairconfig.upstream()) {
            not_affected = false;
        }
    }
    if not_affected {
        debug!(
            "{} not effected {}'s {}, skip...",
            cvrf.id(),
            repairconfig.upstream(),
            component
        );
        return Ok(());
    }

    // 增加 sa 信息
    let mut nvr = String::new();
    for product in &cvrf.producttree.packages["src"] {
        if product.cpe.ends_with(repairconfig.upstream()) {
            nvr = if let Some(caps) = NVR_RE.captures(&product.content) {
                format!("{}-{}-{}", &caps[1], &caps[2], &caps[3])
            } else {
                product.content.clone()
            };
            nvr.push_str(&format!("_{}.json", cvrf.id()));
            break;
        }
    }
    db.push(&nvr);

    if db.exists() {
        debug!("{} is already converted!", cvrf.id());
        return Ok(());
    }

    let data = serde_json::to_string_pretty(&cvrf.sainfo())?;
    let mut sa_file = fs::OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .open(db)?;
    sa_file.write(data.as_bytes())?;
    sa_file.flush()?;

    Ok(())
}
