use path_clean::PathClean;
use std::fs;
use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
use ckb_chain_spec::ChainSpec;
pub use ckb_logger_config::Config as LogConfig;
pub use ckb_metrics_config::Config as MetricsConfig;
use ckb_resource::Resource;
use super::configs::*;
#[cfg(feature = "with_sentry")]
use super::sentry_config::SentryConfig;
use super::{cli, legacy, ExitCode};
pub enum AppConfig {
    CKB(Box<CKBAppConfig>),
    Miner(Box<MinerAppConfig>),
}
#[derive(Clone, Debug, Serialize)]
#[serde(deny_unknown_fields)]
pub struct CKBAppConfig {
    #[serde(skip)]
    pub bin_name: String,
    #[serde(skip)]
    pub root_dir: PathBuf,
    pub data_dir: PathBuf,
    #[serde(default)]
    pub ancient: PathBuf,
    pub tmp_dir: Option<PathBuf>,
    pub logger: LogConfig,
    #[cfg(feature = "with_sentry")]
    #[serde(default)]
    pub sentry: SentryConfig,
    #[serde(default)]
    pub metrics: MetricsConfig,
    #[serde(default)]
    pub memory_tracker: MemoryTrackerConfig,
    pub chain: ChainConfig,
    pub block_assembler: Option<BlockAssemblerConfig>,
    #[serde(default)]
    pub db: DBConfig,
    pub network: NetworkConfig,
    pub rpc: RpcConfig,
    pub tx_pool: TxPoolConfig,
    #[serde(default)]
    pub store: StoreConfig,
    pub alert_signature: Option<NetworkAlertConfig>,
    #[serde(default)]
    pub notify: NotifyConfig,
    #[serde(default)]
    pub indexer: IndexerConfig,
}
#[derive(Clone, Debug, Serialize)]
#[serde(deny_unknown_fields)]
pub struct MinerAppConfig {
    #[serde(skip)]
    pub bin_name: String,
    #[serde(skip)]
    pub root_dir: PathBuf,
    pub data_dir: PathBuf,
    pub chain: ChainConfig,
    pub logger: LogConfig,
    #[cfg(feature = "with_sentry")]
    pub sentry: SentryConfig,
    #[serde(default)]
    pub metrics: MetricsConfig,
    #[serde(default)]
    pub memory_tracker: MemoryTrackerConfig,
    pub miner: MinerConfig,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ChainConfig {
    pub spec: Resource,
}
impl AppConfig {
    pub fn load_for_subcommand<P: AsRef<Path>>(
        root_dir: P,
        subcommand_name: &str,
    ) -> Result<AppConfig, ExitCode> {
        match subcommand_name {
            cli::CMD_MINER => {
                let resource = ensure_ckb_dir(Resource::miner_config(root_dir.as_ref()))?;
                let config = MinerAppConfig::load_from_slice(&resource.get()?)?;
                Ok(AppConfig::with_miner(
                    config.derive_options(root_dir.as_ref())?,
                ))
            }
            _ => {
                let resource = ensure_ckb_dir(Resource::ckb_config(root_dir.as_ref()))?;
                let config = CKBAppConfig::load_from_slice(&resource.get()?)?;
                Ok(AppConfig::with_ckb(
                    config.derive_options(root_dir.as_ref(), subcommand_name)?,
                ))
            }
        }
    }
    pub fn logger(&self) -> &LogConfig {
        match self {
            AppConfig::CKB(config) => &config.logger,
            AppConfig::Miner(config) => &config.logger,
        }
    }
    #[cfg(feature = "with_sentry")]
    pub fn sentry(&self) -> &SentryConfig {
        match self {
            AppConfig::CKB(config) => &config.sentry,
            AppConfig::Miner(config) => &config.sentry,
        }
    }
    pub fn metrics(&self) -> &MetricsConfig {
        match self {
            AppConfig::CKB(config) => &config.metrics,
            AppConfig::Miner(config) => &config.metrics,
        }
    }
    pub fn memory_tracker(&self) -> &MemoryTrackerConfig {
        match self {
            AppConfig::CKB(config) => &config.memory_tracker,
            AppConfig::Miner(config) => &config.memory_tracker,
        }
    }
    pub fn chain_spec(&self) -> Result<ChainSpec, ExitCode> {
        let spec_resource = match self {
            AppConfig::CKB(config) => &config.chain.spec,
            AppConfig::Miner(config) => &config.chain.spec,
        };
        ChainSpec::load_from(spec_resource).map_err(|err| {
            eprintln!("{err}");
            ExitCode::Config
        })
    }
    pub fn into_ckb(self) -> Result<Box<CKBAppConfig>, ExitCode> {
        match self {
            AppConfig::CKB(config) => Ok(config),
            _ => {
                eprintln!("unmatched config file");
                Err(ExitCode::Failure)
            }
        }
    }
    pub fn into_miner(self) -> Result<Box<MinerAppConfig>, ExitCode> {
        match self {
            AppConfig::Miner(config) => Ok(config),
            _ => {
                eprintln!("unmatched config file");
                Err(ExitCode::Failure)
            }
        }
    }
    pub fn set_bin_name(&mut self, bin_name: String) {
        match self {
            AppConfig::CKB(config) => config.bin_name = bin_name,
            AppConfig::Miner(config) => config.bin_name = bin_name,
        }
    }
}
impl AppConfig {
    fn with_ckb(config: CKBAppConfig) -> AppConfig {
        AppConfig::CKB(Box::new(config))
    }
    fn with_miner(config: MinerAppConfig) -> AppConfig {
        AppConfig::Miner(Box::new(config))
    }
}
impl CKBAppConfig {
    pub fn load_from_slice(slice: &[u8]) -> Result<Self, ExitCode> {
        let legacy_config: legacy::CKBAppConfig = toml::from_slice(slice)?;
        for field in legacy_config.deprecated_fields() {
            eprintln!(
                "WARN: the option \"{}\" in configuration files is deprecated since v{}.",
                field.path, field.since
            );
        }
        Ok(legacy_config.into())
    }
    fn derive_options(mut self, root_dir: &Path, subcommand_name: &str) -> Result<Self, ExitCode> {
        self.root_dir = root_dir.to_path_buf();
        self.data_dir = canonicalize_data_dir(self.data_dir, root_dir);
        self.db.adjust(root_dir, &self.data_dir, "db");
        self.ancient = mkdir(path_specified_or_else(&self.ancient, || {
            self.data_dir.join("ancient")
        }))?;
        self.network.path = self.data_dir.join("network");
        if self.tmp_dir.is_none() {
            self.tmp_dir = Some(self.data_dir.join("tmp"));
        }
        self.logger.log_dir = self.data_dir.join("logs");
        self.logger.file = Path::new(&(subcommand_name.to_string() + ".log")).to_path_buf();
        let tx_pool_path = mkdir(self.data_dir.join("tx_pool"))?;
        self.tx_pool.adjust(root_dir, tx_pool_path);
        let indexer_path = mkdir(self.data_dir.join("indexer"))?;
        self.indexer.adjust(root_dir, indexer_path);
        if subcommand_name == cli::CMD_RESET_DATA {
            return Ok(self);
        }
        self.data_dir = mkdir(self.data_dir)?;
        self.db.path = mkdir(self.db.path)?;
        self.network.path = mkdir(self.network.path)?;
        if let Some(tmp_dir) = self.tmp_dir {
            self.tmp_dir = Some(mkdir(tmp_dir)?);
        }
        if self.logger.log_to_file {
            mkdir(self.logger.log_dir.clone())?;
            touch(self.logger.log_dir.join(&self.logger.file))?;
        }
        self.chain.spec.absolutize(root_dir);
        Ok(self)
    }
}
impl MinerAppConfig {
    pub fn load_from_slice(slice: &[u8]) -> Result<Self, ExitCode> {
        let legacy_config: legacy::MinerAppConfig = toml::from_slice(slice)?;
        for field in legacy_config.deprecated_fields() {
            eprintln!(
                "WARN: the option \"{}\" in configuration files is deprecated since v{}.",
                field.path, field.since
            );
        }
        Ok(legacy_config.into())
    }
    fn derive_options(mut self, root_dir: &Path) -> Result<Self, ExitCode> {
        self.root_dir = root_dir.to_path_buf();
        self.data_dir = mkdir(canonicalize_data_dir(self.data_dir, root_dir))?;
        self.logger.log_dir = self.data_dir.join("logs");
        self.logger.file = Path::new("miner.log").to_path_buf();
        if self.logger.log_to_file {
            mkdir(self.logger.log_dir.clone())?;
            touch(self.logger.log_dir.join(&self.logger.file))?;
        }
        self.chain.spec.absolutize(root_dir);
        Ok(self)
    }
}
fn canonicalize_data_dir(data_dir: PathBuf, root_dir: &Path) -> PathBuf {
    if data_dir.is_absolute() {
        data_dir
    } else {
        root_dir.join(data_dir)
    }
}
fn mkdir(dir: PathBuf) -> Result<PathBuf, ExitCode> {
    fs::create_dir_all(dir.clean())?;
    Ok(dir)
}
fn touch(path: PathBuf) -> Result<PathBuf, ExitCode> {
    fs::OpenOptions::new()
        .create(true)
        .append(true)
        .open(&path)?;
    Ok(path)
}
fn ensure_ckb_dir(r: Resource) -> Result<Resource, ExitCode> {
    if r.exists() {
        Ok(r)
    } else {
        eprintln!("Not a CKB directory, initialize one with `ckb init`.");
        Err(ExitCode::Config)
    }
}
fn path_specified_or_else<P: AsRef<Path>, F: FnOnce() -> PathBuf>(
    path: P,
    default_path: F,
) -> PathBuf {
    let path_ref = path.as_ref();
    if path_ref.to_str().is_none() || path_ref.to_str() == Some("") {
        default_path()
    } else {
        path_ref.to_path_buf()
    }
}