use std::error::Error;

use common::net::tools::Packet;
use proto::{
    CmdID, GetPlayerTokenReq, GetPlayerTokenRsp, PacketHead, PlayerData, PlayerLoginReq,
    PlayerLoginRsp,
};

use prost::Message;

use crate::dbgate_service;

pub struct LoginHandler;

impl LoginHandler {
    pub async fn on_get_player_token_req(&self, packet: Packet) -> Result<(), Box<dyn Error>> {
        let Ok(req) = packet.get_proto::<GetPlayerTokenReq>() else {
            tracing::warn!("get proto failed");
            return Ok(());
        };

        let mut rsp = GetPlayerTokenRsp {
            retcode: -1,
            server_rand_key: req.client_rand_key.clone(),
            account_uid: req.account_uid.clone(),
            token: req.account_token.clone(),
            platform_type: req.platform_type,
            key_id: req.key_id,
            ..Default::default()
        };

        let retcode = self.check_login_account_condition(&req);
        if retcode != 0 {
            tracing::info!("[LOGIN] check_account_condition failed, account_uid:{} account_token: {} retcode:{}", &req.account_uid, "", retcode);
            rsp.retcode = retcode;
        } else {
            let sql_op_player_data = &dbgate_service().sql_op_player_data;
            let player_uid = match sql_op_player_data
                .select_player_uid_by_account(req.account_uid.clone())
                .await
            {
                Ok(Some(row)) => row.uid,
                Ok(None) => {
                    sql_op_player_data
                        .insert_player_uid_by_account(req.account_uid.clone(), req.platform_type)
                        .await?
                }
                Err(_) => {
                    tracing::warn!(
                        "[LOGIN] on_get_player_token failed, account_uid:{}",
                        &req.account_uid
                    );
                    rsp.retcode = -1;
                    return Ok(());
                }
            };

            rsp.uid = player_uid as u32;
            rsp.retcode = 0;
        }

        let packet = Packet {
            cmd_id: rsp.get_cmd_id(),
            head: PacketHead {
                rpc_id: packet.head.sender_app_id,
                ..packet.head
            },
            recv_vec: rsp.encode_to_vec(),
        };

        tracing::info!(
            "[LOGIN] OnGetPlayerToken account_uid: {} uid: {} ip: {} retcode: {}",
            &rsp.account_uid,
            rsp.uid,
            packet.get_user_ip(),
            rsp.retcode
        );

        dbgate_service().network_mgr.send_rsp(packet).await?;
        Ok(())
    }

    pub async fn on_player_login_req(&self, packet: Packet) -> Result<(), Box<dyn Error>> {
        let Ok(req) = packet.get_proto::<PlayerLoginReq>() else {
            tracing::error!("get proto failed");
            return Ok(());
        };

        let uid = packet.head.user_id;
        let Ok(result) = dbgate_service()
            .sql_op_player_data
            .select_player_data_by_uid(uid as i32)
            .await
        else {
            tracing::error!("select_player_data_by_uid failed, uid:{uid}");
            return Ok(());
        };

        let is_new_player = result.is_none();
        let player_data = match result {
            Some(data) => PlayerData {
                uid: data.uid as u32,
                nickname: data.nickname,
                bin: data.bin_data,
                extra_bin_data: data.extra_bin_data.unwrap_or_default(),
                json_data: data.json_data,
                ..Default::default()
            },
            None => PlayerData {
                uid,
                ..Default::default()
            },
        };

        if is_new_player {
            dbgate_service()
                .sql_op_player_data
                .insert_player_data_by_uid(uid as i32, &player_data)
                .await?;
        }

        let rsp = PlayerLoginRsp {
            is_new_player,
            player_data: player_data.encode_to_vec(),
            login_rand: req.login_rand,
            ..Default::default()
        };

        dbgate_service()
            .network_mgr
            .send_rsp(Packet {
                cmd_id: rsp.get_cmd_id(),
                head: PacketHead {
                    rpc_id: packet.head.sender_app_id,
                    ..packet.head
                },
                recv_vec: rsp.encode_to_vec(),
            })
            .await?;

        tracing::info!("[LOGIN] onPlayerLoginReq uid:{uid}");
        Ok(())
    }

    fn check_login_account_condition(&self, req: &GetPlayerTokenReq) -> i32 {
        if !req.account_uid.is_empty() {
            0
        } else {
            tracing::warn!("req.account_uid is empty() token: {}", "");
            -1
        }
    }
}
