use data::{
    event,
    tables::{HollowQuestID, ProcedureConfigID, SectionConfigID, TrainingQuestID},
};

use super::core::NetError;

use crate::{
    logic::{
        game::*, procedure::ProcedureAction, EHollowQuestType, ELocalPlayType, ENPCInteraction,
    },
    net::NetSessionState,
};

use super::*;

pub async fn on_enter_world(
    session: &NetSession,
    player: &mut Player,
    _req: EnterWorldCsReq,
) -> NetResult<EnterWorldScRsp> {
    session.set_state(NetSessionState::EndBasicsReq);

    if let Some(procedure_id) = player.basic_data_model.beginner_procedure_id {
        player.game_instance = GameInstance::Fresh(FreshGame::new(procedure_id))
    } else {
        player.init_frontend_game()?;
    }

    let world_init_notify = player.game_instance.create_world_init_notify()?;
    session.notify(world_init_notify).await?;

    session.set_state(NetSessionState::EnterWorldScRsp);
    Ok(EnterWorldScRsp {
        retcode: Retcode::RetSucc.into(),
    })
}

pub async fn on_advance_beginner_procedure(
    session: &NetSession,
    player: &mut Player,
    req: AdvanceBeginnerProcedureCsReq,
) -> NetResult<AdvanceBeginnerProcedureScRsp> {
    let is_end = {
        let GameInstance::Fresh(fresh_game) = &mut player.game_instance else {
            return Err(NetError::from(Retcode::RetFail));
        };

        let procedure_id =
            ProcedureConfigID::new(req.procedure_id as u32).ok_or(Retcode::RetFail)?;

        fresh_game
            .procedure_mgr
            .try_complete_procedure(procedure_id)
            .map_err(LogicError::from)?;

        player.basic_data_model.beginner_procedure_id = fresh_game.procedure_mgr.procedure_id();
        fresh_game.procedure_mgr.is_end()
    };

    if is_end {
        player.init_frontend_game()?;

        let world_init_notify = player.game_instance.create_world_init_notify()?;
        session.notify(world_init_notify).await?;
    }

    Ok(AdvanceBeginnerProcedureScRsp {
        retcode: Retcode::RetSucc.into(),
        next_procedure_id: req.procedure_id,
    })
}

pub async fn on_beginner_battle_begin(
    _session: &NetSession,
    player: &mut Player,
    req: BeginnerBattleBeginCsReq,
) -> NetResult<BeginnerBattleBeginScRsp> {
    let GameInstance::Fresh(fresh_game) = &mut player.game_instance else {
        return Err(NetError::from(Retcode::RetFail));
    };

    fresh_game
        .procedure_mgr
        .on_action(ProcedureAction::BeginnerBattleBegin)
        .map_err(LogicError::from)?;

    Ok(BeginnerBattleBeginScRsp {
        retcode: Retcode::RetSucc.into(),
        battle_uid: req.battle_id as i64,
    })
}

pub async fn on_beginner_battle_end(
    _session: &NetSession,
    player: &mut Player,
    req: BeginnerBattleEndCsReq,
) -> NetResult<BeginnerBattleEndScRsp> {
    let GameInstance::Fresh(fresh_game) = &mut player.game_instance else {
        return Err(NetError::from(Retcode::RetFail));
    };

    tracing::info!(
        "beginner battle end, id: {}, uid: {}, statistics: {:?}",
        req.battle_id,
        req.battle_uid,
        req.battle_statistics
    );

    fresh_game
        .procedure_mgr
        .on_action(ProcedureAction::BeginnerBattleEnd)
        .map_err(LogicError::from)?;

    Ok(BeginnerBattleEndScRsp {
        retcode: Retcode::RetSucc.into(),
    })
}

pub async fn on_beginner_battle_rebegin(
    _session: &NetSession,
    _player: &mut Player,
    _req: BeginnerBattleRebeginCsReq,
) -> NetResult<BeginnerBattleRebeginScRsp> {
    Ok(BeginnerBattleRebeginScRsp {
        retcode: Retcode::RetSucc.into(),
    })
}

pub async fn on_sync_hall_event(
    _session: &NetSession,
    _player: &mut Player,
    _req: SyncHallEventCsReq,
) -> NetResult<SyncHallEventScRsp> {
    Ok(SyncHallEventScRsp {
        retcode: Retcode::RetSucc.into(),
    })
}

pub async fn on_save_pos_in_main_city(
    _session: &NetSession,
    player: &mut Player,
    req: SavePosInMainCityCsReq,
) -> NetResult<SavePosInMainCityScRsp> {
    if let Some(transform) = req.position {
        player
            .main_city_model
            .update_position(transform.position, transform.rotation);
    }

    Ok(SavePosInMainCityScRsp {
        retcode: Retcode::RetSucc.into(),
    })
}

pub async fn on_world_init_finish(
    _session: &NetSession,
    _player: &mut Player,
    _req: WorldInitFinishCsReq,
) -> NetResult<WorldInitFinishScRsp> {
    Ok(WorldInitFinishScRsp {
        retcode: Retcode::RetSucc.into(),
    })
}

pub async fn on_start_trial_fighting_mission(
    session: &NetSession,
    player: &mut Player,
    req: StartTrialFightingMissionCsReq,
) -> NetResult<StartTrialFightingMissionScRsp> {
    let quest_id = TrainingQuestID::new(req.quest_id).ok_or(Retcode::RetFail)?;

    player.game_instance = GameInstance::Hollow(
        HollowGame::create_training_game(quest_id, ELocalPlayType::TrainingRoomFight, &req.avatars)
            .map_err(LogicError::from)?,
    );

    let world_init_notify = player.game_instance.create_world_init_notify()?;
    session.notify(world_init_notify).await?;

    Ok(StartTrialFightingMissionScRsp {
        retcode: Retcode::RetSucc.into(),
    })
}

pub async fn on_end_battle(
    session: &NetSession,
    player: &mut Player,
    req: EndBattleCsReq,
) -> NetResult<EndBattleScRsp> {
    match &mut player.game_instance {
        GameInstance::Hollow(hollow) if hollow.quest_manager.has_active_quests() => {
            hollow
                .quest_manager
                .finish_quest(hollow.battle_event_id.value())
                .map_err(LogicError::from)?;

            session
                .notify(DungeonQuestFinishedScNotify {
                    result: req.battle_result.unwrap().result as u32,
                    quest_id: hollow.quest_id,
                    ..Default::default()
                })
                .await?;
        }
        GameInstance::LongFight(fight) => {
            fight
                .quest_manager
                .finish_quest(fight.battle_event_id.value())
                .map_err(LogicError::from)?;

            session
                .notify(DungeonQuestFinishedScNotify {
                    result: req.battle_result.unwrap().result as u32,
                    quest_id: fight.quest_id,
                    ..Default::default()
                })
                .await?;
        }
        _ => (),
    };

    Ok(EndBattleScRsp {
        battle_reward: Some(BattleRewardInfo::default()),
        retcode: Retcode::RetSucc.into(),
    })
}

pub async fn on_leave_cur_dungeon(
    session: &NetSession,
    player: &mut Player,
    _req: LeaveCurDungeonCsReq,
) -> NetResult<LeaveCurDungeonScRsp> {
    player.init_frontend_game()?;

    let world_init_notify = player.game_instance.create_world_init_notify()?;
    session.notify(world_init_notify).await?;

    Ok(LeaveCurDungeonScRsp {
        retcode: Retcode::RetSucc.into(),
    })
}

pub async fn on_interact_with_unit(
    session: &NetSession,
    _player: &mut Player,
    req: InteractWithUnitCsReq,
) -> NetResult<InteractWithUnitScRsp> {
    tracing::info!("interact: {req:?}");

    if let Some(graph) = event::interacts().find(|e| e.event_id == req.interaction as u32) {
        session
            .notify(SyncEventInfoScNotify {
                owner_id: req.interaction as u32,
                npc_interaction: ENPCInteraction::OnInteract.to_string(),
                tag: req.unit_tag as u32,
                owner_type: EventGraphOwnerType::SceneUnit.into(),
                action_list: graph.actions.iter().map(|a| a.to_protocol()).collect(),
                ..Default::default()
            })
            .await?;
    } else {
        tracing::warn!("no event graph for interaction: {}", req.interaction);
    }

    Ok(InteractWithUnitScRsp {
        retcode: Retcode::RetSucc.into(),
    })
}

pub async fn on_enter_section(
    session: &NetSession,
    player: &mut Player,
    req: EnterSectionCsReq,
) -> NetResult<EnterSectionScRsp> {
    let section_id = SectionConfigID::new(req.section_id).ok_or(Retcode::RetFail)?;

    player.main_city_model.switch_section(section_id);
    player.init_frontend_game()?;

    let GameInstance::Frontend(frontend_game) = &mut player.game_instance else {
        unreachable!()
    };

    frontend_game.set_entry_transform(req.transform);

    session
        .notify(player.game_instance.create_world_init_notify()?)
        .await?;

    Ok(EnterSectionScRsp {
        retcode: Retcode::RetSucc.into(),
    })
}

pub async fn on_jump_page_system(
    _session: &NetSession,
    _player: &mut Player,
    _req: JumpPageSystemCsReq,
) -> NetResult<JumpPageSystemScRsp> {
    Ok(JumpPageSystemScRsp {
        retcode: Retcode::RetSucc.into(),
        ..Default::default()
    })
}

pub async fn on_start_hollow_quest(
    session: &NetSession,
    player: &mut Player,
    req: StartHollowQuestCsReq,
) -> NetResult<StartHollowQuestScRsp> {
    use crate::logic::{TimePeriodType, WeatherType};

    let quest_id = HollowQuestID::new(req.quest_id).ok_or(Retcode::RetFail)?;
    let quest_type = EHollowQuestType::from(quest_id.template().hollow_quest_type);

    match quest_type {
        EHollowQuestType::RallyBattle => {
            player.game_instance = GameInstance::LongFight(
                LongFightGame::create_rally_game(
                    quest_id,
                    &req.avatars,
                    req.buddy_id,
                    TimePeriodType::from_str(&req.quest_time_period),
                    WeatherType::from_str(&req.quest_weather),
                )
                .map_err(LogicError::from)?,
            )
        }
        _ => {
            player.game_instance = GameInstance::Hollow(
                HollowGame::create_pure_hollow_battle(
                    quest_id,
                    &req.avatars,
                    req.buddy_id,
                    TimePeriodType::from_str(&req.quest_time_period),
                    WeatherType::from_str(&req.quest_weather),
                )
                .map_err(LogicError::from)?,
            )
        }
    }

    let world_init_notify = player.game_instance.create_world_init_notify()?;
    session.notify(world_init_notify).await?;

    Ok(StartHollowQuestScRsp {
        retcode: Retcode::RetSucc.into(),
        quest_id: 0,
    })
}

pub async fn on_finish_hollow_battle_event(
    _session: &NetSession,
    _player: &mut Player,
    _req: FinishHollowBattleEventCsReq,
) -> NetResult<FinishHollowBattleEventScRsp> {
    Ok(FinishHollowBattleEventScRsp {
        retcode: Retcode::RetSucc.into(),
    })
}

pub async fn on_long_fight_progress_update(
    _session: &NetSession,
    _player: &mut Player,
    _req: LongFightProgressUpdateCsReq,
) -> NetResult<LongFightProgressUpdateScRsp> {
    Ok(LongFightProgressUpdateScRsp {
        retcode: Retcode::RetSucc.into(),
    })
}
