use common::{
    crypto::{rsa_utils, MhyXorpad},
    net::{kcp_server::KcpSession, tools::Packet, NetError, ServiceType},
    tools::time_utils,
    ServiceQualifier,
};
use proto::{
    GetPlayerTokenReq, GetPlayerTokenRsp, PacketHead, PlayerLoginReq, PlayerStatusRedisData,
    PlayerStatusType,
};
use rand::RngCore;
use thiserror::Error;

use crate::{gateserver_service, network::PlayerSession};

pub struct LoginHandler;

#[derive(Eq, PartialEq)]
pub enum SdkRetcode {
    Fail = -1,
    #[allow(dead_code)]
    Succ = 0,
}

#[derive(Error, Debug)]
enum GenSecretSeedError {
    #[error("client_rand_key is too long:{0} uid:{1}")]
    TooLongClientRandKey(usize, u32),
    #[error("this region doesn't allow key_id:{0}")]
    KeyNotAllowed(u32),
    #[error("key_id:{0} config not found or key is empty")]
    KeyNotFound(u32),
    #[error("client_rand_key decrypt failed, uid:{0}")]
    ClientRandKeyDecryptFail(u32),
    #[error("client_rand_key is not uint64, uid:{0}")]
    ClientRandKeySizeMismatch(u32),
    #[error("client public encrypt failed, server_key:{0}, uid:{1}")]
    ClientPublicEncryptFail(String, u32),
    #[error("server private sign failed, server_key:{0}, uid:{1}")]
    ServerPrivateSignFail(String, u32),
}

impl LoginHandler {
    pub async fn on_get_player_token_req(&self, packet: Packet) {
        let user_session_id = packet.head.user_session_id;
        let Some(session) = gateserver_service().find_player_session(user_session_id) else {
            tracing::warn!("find_player_session: {user_session_id} failed");
            return;
        };

        if self.get_player_token(&session, packet).await.is_none() {
            let rsp = GetPlayerTokenRsp {
                retcode: 1,
                ..Default::default()
            };
            let _ = session
                .send_packet(Packet::new(rsp, PacketHead::default()))
                .await;
        };
    }

    pub async fn on_player_login_req(&self, packet: Packet) {
        let user_session_id = packet.head.user_session_id;
        let Some(session) = gateserver_service().find_player_session(user_session_id) else {
            tracing::warn!("find_player_session: {user_session_id} failed");
            return;
        };

        session.set_is_recv_login();
        let _ = self.player_login(&session, packet).await;
    }

    pub async fn on_player_login_rsp(&self, packet: Packet) {
        let user_session_id = packet.head.user_session_id;
        let Some(session) = gateserver_service().find_player_session(user_session_id) else {
            tracing::warn!("find_player_session: {user_session_id} failed");
            return;
        };

        session.set_is_finish_login();
        if session.send_packet(packet).await.is_err() {
            tracing::error!("send_packet failed uid: {}", session.get_uid());
        } else {
            tracing::info!(
                "[LOGIN] send LoginRsp to client session: {} uid: {}",
                user_session_id,
                session.get_uid()
            );
        }
    }

    async fn get_player_token(&self, session: &PlayerSession, packet: Packet) -> Option<()> {
        let Ok(proto) = packet.get_proto::<GetPlayerTokenReq>() else {
            tracing::warn!("get GetPlayerTokenReq failed");
            return None;
        };

        if !gateserver_service().config.region.dev_sdk_account {
            if self
                .check_sdk_account(&proto.account_uid, &proto.account_token)
                .await
                == SdkRetcode::Fail
            {
                tracing::warn!("account_uid: {} sdk_retcode is FAIL", &proto.account_uid);
                return None;
            }
        }

        self.forward_get_player_token_req(session.get_user_session_id(), packet)
            .await
            .ok()
    }

    async fn player_login(&self, session: &PlayerSession, packet: Packet) -> Option<()> {
        let uid = session.get_uid();
        if uid == 0 {
            tracing::error!("get_uid is 0, GetPlayerToken first");
            return None;
        }

        let Ok(mut req) = packet.get_proto::<PlayerLoginReq>() else {
            tracing::error!("get PlayerLoginReq failed");
            return None;
        };

        if req.token != session.get_account_token() {
            tracing::error!(
                "uid: {} token not fit: {} {}",
                uid,
                req.token,
                session.get_account_token()
            );
            return None;
        }

        let platform_type = session.get_platform_type();
        if req.platform_type != platform_type {
            tracing::warn!("different platform_type in TokenReq and LoginReq! in login_req:{} going to change it to:{}", req.platform_type, platform_type);
            req.platform_type = platform_type;
        }

        let Some(status) = self.async_query_player_status(uid).await else {
            return None;
        };

        let gameserver_id = match status.status() {
            PlayerStatusType::PlayerStatusOnline => {
                tracing::info!(
                    "[LOGIN] online, game: {}, login_rand: {}",
                    status.gameserver_id,
                    status.login_rand
                );
                status.gameserver_id
            }
            PlayerStatusType::PlayerStatusOffline => {
                let id = gateserver_service().choose_gameserver_id();
                tracing::info!("[LOGIN] offline, and choose game: {id}");
                id
            }
        };

        session.set_gameserver_id(gameserver_id);

        let mut packet = packet.clone();
        packet.head.user_session_id = session.get_user_session_id();
        packet.head.user_id = uid;
        packet.head.user_ip = session.get_remote_ip();
        gateserver_service()
            .service_box
            .push_packet_to_service(ServiceQualifier::AppId(gameserver_id), packet)
            .await
            .ok()
    }

    async fn async_query_player_status(&self, uid: u32) -> Option<PlayerStatusRedisData> {
        let ret = gateserver_service().redis_mgr.get_player_status(uid).await;
        match ret {
            Some(_) => tracing::info!("query_player_status succ uid: {uid}"),
            None => tracing::error!("get_player_status failed, uid: {uid}"),
        }

        ret
    }

    async fn forward_get_player_token_req(
        &self,
        session_id: u32,
        mut packet: Packet,
    ) -> Result<(), NetError> {
        let app_id = gateserver_service().config.app_id;

        packet.head.source_service = ServiceType::Gateserver as u32;
        packet.head.sender_app_id = app_id;
        packet.head.user_session_id = session_id;

        gateserver_service()
            .service_box
            .push_packet_to_service(ServiceQualifier::Type(ServiceType::Dbgate), packet)
            .await
    }

    pub async fn check_sdk_account(&self, _account_uid: &str, _account_token: &str) -> SdkRetcode {
        tracing::error!("LoginHandler::check_sdk_account not implemented");
        SdkRetcode::Fail
    }

    pub async fn on_get_player_token_rsp(&self, packet: &Packet) -> GetPlayerTokenRsp {
        let session_id = packet.head.user_session_id;
        let Ok(mut rsp) = packet.get_proto::<GetPlayerTokenRsp>() else {
            tracing::error!("get GetPlayerTokenRsp failed");
            return GetPlayerTokenRsp {
                retcode: -1,
                ..Default::default()
            };
        };

        tracing::info!(
            "on_get_player_token_rsp session_id:{} uid:{} account_uid:{} ret:{} platform_type:{}",
            session_id,
            rsp.uid,
            &rsp.account_uid,
            rsp.retcode,
            rsp.platform_type
        );

        let Some(session) = gateserver_service().find_player_session(session_id) else {
            tracing::warn!("find_player_session: {session_id} failed");
            return rsp;
        };

        if rsp.retcode == 0 {
            match self.gen_secret_seed(&mut rsp).await {
                Ok(seed) => {
                    let secret_key = MhyXorpad::from_seed(seed);
                    session.set_secret_key(secret_key);
                    session.set_uid(rsp.uid);
                }
                Err(err) => tracing::warn!("gen SecretSeed failed, uid: {}, {err}", rsp.uid),
            }
        }

        rsp
    }

    async fn gen_secret_seed(
        &self,
        rsp: &mut GetPlayerTokenRsp,
    ) -> Result<u64, GenSecretSeedError> {
        let service = gateserver_service();

        let uid = rsp.uid;
        let rand_key = rbase64::decode(&rsp.server_rand_key).unwrap_or_default();
        rsp.server_rand_key.clear();
        rsp.retcode = -1;

        let server_rand_key =
            ((rand::thread_rng().next_u32() as u64) << 32) | (time_utils::get_now() & 0xFFFFFFFF);

        if rand_key.len() <= 0x200 {
            let region_config = service
                .deploy_config_mgr
                .find_db_region_config(service.config.region.name.clone())
                .await
                .unwrap();
            let key_id = rsp.key_id;

            if !region_config.is_key_allowed(key_id) {
                return Err(GenSecretSeedError::KeyNotAllowed(key_id));
            }

            let rsa_config = service
                .deploy_config_mgr
                .find_db_rsakey_config(key_id.into())
                .await
                .ok_or(GenSecretSeedError::KeyNotFound(key_id))?;

            let client_rand_key =
                rsa_utils::private_decrypt(&rsa_config.server_private_key, &rand_key)
                    .ok_or(GenSecretSeedError::ClientRandKeyDecryptFail(uid))?;

            let client_rand_key = u64::from_be_bytes(
                client_rand_key
                    .try_into()
                    .map_err(|_| GenSecretSeedError::ClientRandKeySizeMismatch(uid))?,
            );

            let server_rand_key_b = server_rand_key.to_be_bytes();
            let server_seed_encrypted =
                rsa_utils::public_encrypt(&rsa_config.client_public_key, &server_rand_key_b)
                    .ok_or(GenSecretSeedError::ClientPublicEncryptFail(
                        hex::encode(&server_rand_key_b),
                        uid,
                    ))?;

            let server_sign =
                rsa_utils::private_sign(&rsa_config.server_private_key, &server_rand_key_b).ok_or(
                    GenSecretSeedError::ServerPrivateSignFail(hex::encode(&server_rand_key_b), uid),
                )?;

            rsp.retcode = 0;
            rsp.server_rand_key = rbase64::encode(&server_seed_encrypted);
            rsp.sign = rbase64::encode(&server_sign);

            Ok(client_rand_key ^ server_rand_key)
        } else {
            Err(GenSecretSeedError::TooLongClientRandKey(
                rand_key.len(),
                uid,
            ))
        }
    }
}
