use std::{cell::RefCell, error::Error, rc::Rc};

use common::net::tools::Packet;
use data::math_def::Vector3;
use proto::{
    EnterSceneDoneRsp, EnterSceneReadyRsp, EntityMoveInfo, PostEnterSceneRsp, Retcode,
    SceneInitFinishRsp,
};

use crate::{
    creature::{Creature, LifeState},
    entity::{Entity, MotionContext},
    player::{avatar::Avatar, Player},
    scene::Scene,
};

use super::get_proto;

pub fn on_enter_scene_ready_req(player: &Rc<Player>, packet: Packet) -> Result<(), Box<dyn Error>> {
    let req = get_proto!(packet, EnterSceneReadyReq);

    let result = player.enter_scene_ready(&req);

    player.send_proto(EnterSceneReadyRsp {
        enter_scene_token: req.enter_scene_token,
        retcode: result.err().unwrap_or_default().into(),
    });

    Ok(())
}

pub fn on_scene_init_finish_req(player: &Rc<Player>, packet: Packet) -> Result<(), Box<dyn Error>> {
    let req = get_proto!(packet, SceneInitFinishReq);

    let mut rsp = SceneInitFinishRsp {
        retcode: Retcode::RetFail.into(),
        enter_scene_token: req.enter_scene_token,
    };

    player.player_basic_comp.borrow().notify_server_time();
    let ret = player
        .scene_init_finish(&req)
        .err()
        .unwrap_or(Retcode::RetSucc);

    rsp.retcode = ret.into();
    player.send_proto(rsp);
    Ok(())
}

pub fn on_enter_scene_done_req(player: &Rc<Player>, packet: Packet) -> Result<(), Box<dyn Error>> {
    let req = get_proto!(packet, EnterSceneDoneReq);

    if let Err(ret) = player.enter_scene_done(&req) {
        tracing::info!("end_enter_scene failed, uid:{}", player.uid);
        player.send_proto(EnterSceneDoneRsp {
            retcode: ret.into(),
            enter_scene_token: req.enter_scene_token,
        });
    }

    Ok(())
}

pub fn on_post_enter_scene_req(player: &Rc<Player>, packet: Packet) -> Result<(), Box<dyn Error>> {
    let req = get_proto!(packet, PostEnterSceneReq);

    if let Err(ret) = player.post_enter_scene(&req) {
        tracing::info!("post_enter_scene failed, uid:{}", player.uid);
        player.send_proto(PostEnterSceneRsp {
            retcode: ret.into(),
            enter_scene_token: req.enter_scene_token,
        });
    }

    Ok(())
}

pub fn process_entity_move_info(
    scene: Rc<RefCell<Scene>>,
    player: &Rc<Player>,
    cur_avatar: Rc<RefCell<Avatar>>,
    move_info: &EntityMoveInfo,
    _sync_uid_vec: &mut Vec<u32>,
) -> Result<(), Retcode> {
    if move_info.entity_id == cur_avatar.borrow().entity_id() {
        if cur_avatar.borrow().get_life_state() != LifeState::LifeAlive {
            tracing::info!(
                "avatar is not alive:{}",
                cur_avatar.borrow() as std::cell::Ref<dyn Entity>
            );
            return Err(Retcode::RetFail);
        }
    } else {
        return Err(Retcode::RetFail); // TODO: non cur avatar also has some handling
    }

    let motion_info = move_info.motion_info.as_ref().unwrap();
    let (Some(pos), Some(rot)) = (motion_info.pos.as_ref(), motion_info.rot.as_ref()) else {
        return Ok(());
    };

    let context = MotionContext {
        scene_time_ms: move_info.scene_time as u64,
        reliable_seq: move_info.reliable_seq,
        is_reliable: move_info.is_reliable,
        exclude_uid: player.uid,
        is_notify: false,
        is_do_move: true,
        interval_velocity: 0,
        sync_uid_vec: Vec::new(),
    };

    scene.borrow_mut().entity_move_to(
        cur_avatar.clone() as Rc<RefCell<dyn Entity>>,
        Vector3::from_client(pos),
        Vector3::from_client(rot),
    );

    let _ = cur_avatar
        .borrow_mut()
        .set_motion_info(motion_info, &context);

    Ok(())
}
