use std::{fs::read_to_string, io, sync::Arc, time::Duration};

use axum::{error_handling::HandleErrorLayer, BoxError};
use config::Config;
use hyper::StatusCode;
use time::UtcOffset;
use tower::ServiceBuilder;
// use tracing::log::LevelFilter;
use tracing_subscriber::{filter::LevelFilter, fmt::time::OffsetTime, prelude::*};

pub mod config;
pub mod gmssl;
pub mod router;

pub mod backend;
pub mod extract;
pub mod sd_model;
static APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);

pub mod packageid_map;

pub async fn init_router(config: Config) -> axum::Router {
    let req = reqwest::Client::builder()
        .user_agent(APP_USER_AGENT)
        .deflate(true)
        .gzip(true)
        .brotli(true)
        .timeout(Duration::from_millis(config.timeout))
        .no_proxy()
        // .trust_dns(true)
        .build()
        .unwrap();
    let layer = ServiceBuilder::new()
        .layer(HandleErrorLayer::new(handle_timeout_error))
        .timeout(Duration::from_millis(config.timeout));

    let pool = sqlx::any::AnyPoolOptions::new()
        .connect(&config.date_source)
        .await
        .unwrap();

    crate::router::init_route()
        .await
        .layer(axum::Extension(req))
        .layer(axum::Extension(Arc::new(config)))
        .layer(axum::Extension(pool))
        .layer(layer)
}

async fn handle_timeout_error(err: BoxError) -> (StatusCode, String) {
    if err.is::<tower::timeout::error::Elapsed>() {
        (StatusCode::REQUEST_TIMEOUT, "timeout".to_string())
    } else {
        (StatusCode::INTERNAL_SERVER_ERROR, format!("error: {err:?}"))
    }
}

pub fn load_config() -> config::Config {
    let path = if cfg!(not(debug_assertions)) {
        "config.toml"
    } else {
        "config-test.toml"
    };
    let config = read_to_string(path).expect("读取配置文件错误");
    toml::from_str(&config).expect("解析配置文件错误")
}

pub fn init_tracing() {
    let time_format = time::macros::format_description!(
        "[year]-[month padding:zero]-[day padding:zero] [hour padding:zero]:[minute padding:zero]:[second padding:zero]"
    );
    let offset = UtcOffset::from_hms(8, 0, 0).unwrap();
    let local_time = OffsetTime::new(offset, time_format);

    let writer = tracing_appender::rolling::daily("logs", "sd-proxy.log");

    let fmt = tracing_subscriber::fmt::format()
        .with_timer(local_time)
        .with_level(true)
        .with_file(true)
        .with_line_number(true)
        .with_target(true)
        // .with_thread_ids(true)
        .with_thread_names(true)
        .compact();

    let log_file = tracing_subscriber::fmt::layer()
        .event_format(fmt.clone())
        .with_writer(writer)
        .with_ansi(false)
        .with_filter(LevelFilter::INFO);

    let log_stdout = tracing_subscriber::fmt::layer()
        .event_format(fmt)
        .with_ansi(true)
        .with_writer(io::stdout)
        .with_filter(LevelFilter::DEBUG);

    tracing_subscriber::registry()
        .with(log_file)
        .with(log_stdout)
        .init();
}

pub trait Sign {
    fn sign(&mut self, config: &Config);
}

impl<T> Sign for Option<T>
where
    T: Sign,
{
    fn sign(&mut self, config: &Config) {
        if let Some(s) = self {
            s.sign(config)
        }
    }
}

#[allow(unused)]
pub trait Verify {
    fn verify(&self, config: &Config) -> anyhow::Result<()> {
        Ok(())
    }
}

impl<T> Verify for Option<T>
where
    T: Verify,
{
    fn verify(&self, config: &Config) -> anyhow::Result<()> {
        if let Some(v) = self {
            v.verify(config)
        } else {
            Ok(())
        }
    }
}

#[cfg(test)]
#[allow(unused)]
mod tests {
    use tracing_subscriber::{filter::LevelFilter, Registry};

    use super::*;

    #[test]
    fn test_name() {
        load_config();
    }

    #[derive(Debug)]
    struct TestStruct {
        fild: String,
        fild2: String,
    }

    #[test]
    fn test_log() {
        init_tracing();
        // let arg = TestStruct {
        //     fild: "fild".to_string(),
        //     fild2: "fild2".to_string(),
        // };
        // let span = tracing::span!(tracing::Level::WARN, "HandlerPay", arg=%arg.fild);
        // let _g = span.enter();
        tracing::trace!("this is a trace message");
        tracing::debug!("this is a debug message");
        tracing::info!("this is an info message");
        tracing::warn!("this is a warning message");
        tracing::error!("this is an error message");
        let span = tracing::span!(tracing::Level::ERROR, "example_span", "asdwa");
        let guard = span.enter();

        tracing::trace!("this is a trace message");
        tracing::debug!("this is a debug message");
        tracing::info!("this is an info message");
        tracing::warn!("this is a warning message");
        tracing::error!("this is an error message");
        drop(guard)
    }
}
