use axum::{extract::Query, routing::get, Router};
use prost::Message;
use proto::{QueryCurrRegionHttpRsp, QueryRegionListHttpRsp, RegionInfo, RegionSimpleInfo};
use serde::Deserialize;
use serde_json::json;

use crate::{config::DispatchConfig, dispatch_service, gateserver_mgr::GateserverMgr};
use common::{
    crypto::{rsa_utils, MhyXorpad},
    db::DbDeployConfigMgr,
};

const ALIVE_RSP: &str = "{\"retcode\":\"0\", \"msg\":\"alive ok\"}";
const FALLBACK_RSP: &str = "{\"retcode\":\"-1\", \"msg\":\"system error\"}";

pub fn make_routes() -> Router {
    Router::new()
        .route("/alive", get(|| async { ALIVE_RSP }))
        .route("/query_region_list", get(query_region_list))
        .route("/query_cur_region", get(query_curr_region))
        .route("/query_gateserver", get(query_curr_region))
        .fallback(|| async { FALLBACK_RSP })
}

#[derive(Deserialize)]
#[allow(unused)]
struct QueryRegionListParams {
    pub channel_id: i32,
    pub version: String,
    pub lang: i32,
    pub platform: i32,
    pub binary: u8,
    pub time: u32,
}

#[derive(Deserialize)]
#[allow(unused)]
struct QueryCurrRegionParams {
    pub channel_id: i32,
    pub version: String,
    pub lang: i32,
    pub platform: i32,
    pub binary: u8,
    pub time: u32,
    #[serde(rename = "dispatchSeed")]
    pub dispatch_seed: String,
    pub aid: u32,
    pub key_id: Option<i64>,
}

async fn query_region_list(params: Query<QueryRegionListParams>) -> String {
    let service = dispatch_service();
    let rsp = if service.config.forbid_first_dispatch {
        tracing::warn!("query_region_list is forbidden in this dispatch");

        QueryRegionListHttpRsp {
            retcode: 8,
            ..Default::default()
        }
    } else {
        match build_query_region_list_rsp(&service.db_deploy_config_mgr, &params.0).await {
            Some(rsp) => rsp,
            None => QueryRegionListHttpRsp {
                retcode: -1,
                ..Default::default()
            },
        }
    };

    rbase64::encode(&rsp.encode_to_vec())
}

async fn query_curr_region(params: Query<QueryCurrRegionParams>) -> String {
    let service = dispatch_service();
    let rsp = match get_curr_region_info(
        &service.gateserver_mgr,
        &service.db_deploy_config_mgr,
        &service.config,
        &params.0,
    )
    .await
    {
        Some(rsp) => rsp,
        None => QueryCurrRegionHttpRsp {
            retcode: -1,
            ..Default::default()
        },
    };

    if params.key_id.is_some() {
        encrypt_region_info(
            &service.db_deploy_config_mgr,
            &service.config,
            &params.0,
            rsp.encode_to_vec(),
        )
        .await
        .unwrap()
        .to_string()
    } else {
        rbase64::encode(&rsp.encode_to_vec())
    }
}

async fn encrypt_region_info(
    deploy_config_mgr: &DbDeployConfigMgr,
    config: &DispatchConfig,
    params: &QueryCurrRegionParams,
    rsp_vec: Vec<u8>,
) -> Option<serde_json::Value> {
    let key_id = params.key_id.unwrap();

    let region = deploy_config_mgr
        .find_db_region_config(config.region.name.clone())
        .await?;

    if !region.is_key_allowed(key_id as u32) {
        tracing::warn!("this region doesn't allow key_id: {}", key_id);
        return None;
    }

    let rsakey = deploy_config_mgr.find_db_rsakey_config(key_id).await?;

    let sign = rsa_utils::private_sign(&rsakey.server_private_key, &rsp_vec)?;
    let cipher = rsa_utils::public_encrypt(&rsakey.client_public_key, &rsp_vec)?;

    Some(json! ({
        "content": rbase64::encode(&cipher),
        "sign": rbase64::encode(&sign)
    }))
}

#[tracing::instrument(skip_all)]
async fn get_curr_region_info(
    gateserver_mgr: &GateserverMgr,
    deploy_config_mgr: &DbDeployConfigMgr,
    config: &DispatchConfig,
    params: &QueryCurrRegionParams,
) -> Option<QueryCurrRegionHttpRsp> {
    tracing::info!(
        "request version: {} lang: {} platform: {} channel_id: {} dispatch_seed: {}",
        params.version,
        params.lang,
        params.platform,
        params.channel_id,
        params.dispatch_seed
    );

    let region = deploy_config_mgr
        .find_db_region_config(config.region.name.clone())
        .await?;

    let client_config = deploy_config_mgr
        .find_db_client_config(params.version.clone())
        .await?;

    if client_config.dispatch_seed.len() > 0 {
        if params.dispatch_seed != client_config.dispatch_seed {
            tracing::warn!(
                "expected dispatch seed: {} get: {}",
                client_config.dispatch_seed,
                params.dispatch_seed
            );

            return Some(QueryCurrRegionHttpRsp {
                retcode: 1,
                ..Default::default()
            });
        }
    }

    let Some(gateserver) = gateserver_mgr.get_gateserver(params.aid) else {
        tracing::warn!("get_gateserver failed for uid: {}", params.aid);

        return Some(QueryCurrRegionHttpRsp {
            retcode: -1,
            msg: String::from("Not found Gateserver"),
            ..Default::default()
        });
    };

    let mut rsp = QueryCurrRegionHttpRsp {
        client_secret_key: region.client_secret_key.clone(),
        region_info: Some(RegionInfo {
            gateserver_ip: gateserver.outer_ip,
            gateserver_port: gateserver.listen_port as u32,
            use_gateserver_domain_name: gateserver.use_domain_name,
            gateserver_domain_name: gateserver.domain_name,
            area_type: region.area_type.clone(),
            ..Default::default()
        }),
        ..Default::default()
    };

    if let Some(client_region_config) = deploy_config_mgr
        .find_db_client_region_config(params.version.clone(), region.name.clone())
        .await
    {
        if client_region_config.client_region_custom_config.len() > 0 {
            let mut client_region_custom_config = client_region_config
                .client_region_custom_config
                .into_bytes();
            let xorpad = MhyXorpad::from_ec2b(&region.client_secret_key);
            xorpad.xor(&mut client_region_custom_config);
            rsp.client_region_custom_config_encrypted = client_region_custom_config;
        }
    }

    Some(rsp)
}

#[tracing::instrument(skip_all)]
async fn build_query_region_list_rsp(
    deploy_config_mgr: &DbDeployConfigMgr,
    params: &QueryRegionListParams,
) -> Option<QueryRegionListHttpRsp> {
    let binds = deploy_config_mgr
        .find_bind_region_config_vec(params.version.clone())
        .await;

    let regions = deploy_config_mgr.fetch_region_list(&binds).await;
    let enable_login_pc = deploy_config_mgr.enable_login_pc(params.channel_id).await?;

    let client_secret_key = regions.first()?.client_secret_key.clone();
    let client_config = deploy_config_mgr
        .find_db_client_config(params.version.clone())
        .await?;

    let mut client_custom_config = client_config.client_custom_config_str.into_bytes();
    let xorpad = MhyXorpad::from_ec2b(&client_secret_key);
    xorpad.xor(&mut client_custom_config);

    Some(QueryRegionListHttpRsp {
        retcode: 0,
        enable_login_pc,
        client_secret_key,
        client_custom_config_encrypted: client_custom_config,
        region_list: regions
            .iter()
            .map(|r| RegionSimpleInfo {
                name: r.name.clone(),
                title: r.title.clone(),
                r#type: r.region_type.clone(),
                dispatch_url: r.dispatch_url.clone(),
            })
            .collect(),
    })
}
