use common::util;
use data::tables::{
    self, ArchiveBattleQuestID, AvatarBaseID, BattleEventConfigID, BuddyBaseID, HollowQuestID,
    RobotBuddyConfigID, RobotConfigID, TrainingQuestID,
};
use proto::{DungeonInfo, DungeonItemData, FightSceneInfo, SceneInfo, WeatherPoolInfo};
use thiserror::Error;

use crate::logic::{
    battle::{
        drop::FightDropPool,
        unit::{AvatarUnit, AvatarUnitID, BuddyUnit, BuddyUnitID},
        BuddyParam, DungeonQuestManager, TeamDataItem,
    },
    BuddyTeamType, EHollowQuestType, ELocalPlayType, ESceneType, TimePeriodType, WeatherType,
};

use super::NapGameMode;

#[derive(Error, Debug)]
pub enum HollowGameError {
    #[error("Quest ({0}) type is not supported: {1:?}")]
    QuestTypeNotSupported(u32, EHollowQuestType),
    #[error("Battle group not found, quest id: {0}")]
    BattleGroupNotFound(u32),
    #[error("Invalid avatar id: {0}")]
    InvalidAvatarID(u32),
    #[error("Invalid robot id: {0}")]
    InvalidRobotID(u32),
}

pub struct HollowGame {
    pub quest_id: u32,
    pub battle_event_id: BattleEventConfigID,
    pub time_period: TimePeriodType,
    pub weather: WeatherType,
    pub play_type: ELocalPlayType,
    pub start_timestamp: i64,
    pub team_data: TeamDataItem,
    pub fight_drop_pool: FightDropPool,
    pub quest_manager: DungeonQuestManager,
}

impl HollowGame {
    pub fn create_training_game(
        training_quest_id: TrainingQuestID,
        play_type: ELocalPlayType,
        avatars: &[u32],
    ) -> Result<Self, HollowGameError> {
        let template = training_quest_id.template();

        let avatars = match avatars
            .iter()
            .map(|id| AvatarBaseID::new(*id).map(|id| AvatarUnitID::Base(id)))
            .collect::<Result<Vec<_>, _>>()
        {
            Ok(avatars) => avatars,
            Err(err) => return Err(HollowGameError::InvalidAvatarID(err.0)),
        };

        Ok(Self {
            quest_id: template.id.value(),
            battle_event_id: template.battle_event_id,
            time_period: TimePeriodType::Morning,
            weather: WeatherType::SunShine,
            start_timestamp: util::cur_timestamp() as i64,
            play_type,
            team_data: TeamDataItem::new(&avatars, &[]),
            fight_drop_pool: FightDropPool::new(template.battle_event_id),
            quest_manager: DungeonQuestManager::default(),
        })
    }

    pub fn create_archive_battle(
        archive_battle_quest_id: ArchiveBattleQuestID,
        play_type: ELocalPlayType,
        avatars: &[u32],
        buddy_id: u32,
    ) -> Result<Self, HollowGameError> {
        let template = archive_battle_quest_id.template();

        let avatars = match avatars
            .iter()
            .map(|id| RobotConfigID::new(*id).map(|id| AvatarUnitID::Robot(id)))
            .collect::<Result<Vec<_>, _>>()
        {
            Ok(avatars) => avatars,
            Err(err) => return Err(HollowGameError::InvalidAvatarID(err.0)),
        };

        let buddy_params = match RobotBuddyConfigID::new(buddy_id) {
            Ok(id) => vec![BuddyParam(BuddyUnitID::Robot(id), BuddyTeamType::Fighting)],
            Err(_) => Vec::with_capacity(0),
        };

        Ok(Self {
            quest_id: template.id.value(),
            battle_event_id: template.battle_event_id,
            time_period: TimePeriodType::Morning,
            weather: WeatherType::SunShine,
            start_timestamp: util::cur_timestamp() as i64,
            play_type,
            team_data: TeamDataItem::new(&avatars, &buddy_params),
            fight_drop_pool: FightDropPool::new(template.battle_event_id),
            quest_manager: DungeonQuestManager::default(),
        })
    }

    pub fn create_pure_hollow_battle(
        quest_id: HollowQuestID,
        avatars: &[u32],
        buddy_id: u32,
        time_period: TimePeriodType,
        weather: WeatherType,
    ) -> Result<Self, HollowGameError> {
        let template = quest_id.template();
        if template.chess_board_id != 0 {
            return Err(HollowGameError::QuestTypeNotSupported(
                template.id.value(),
                EHollowQuestType::from(template.hollow_quest_type),
            ));
        }

        let Some(battle_group) = tables::battle_group_config_template_tb::iter()
            .find(|tmpl| tmpl.quest_id == template.id.value())
        else {
            return Err(HollowGameError::BattleGroupNotFound(template.id.value()));
        };

        let avatars = match avatars
            .iter()
            .map(|id| AvatarBaseID::new(*id).map(|id| AvatarUnitID::Base(id)))
            .collect::<Result<Vec<_>, _>>()
        {
            Ok(avatars) => avatars,
            Err(err) => return Err(HollowGameError::InvalidAvatarID(err.0)),
        };

        let buddy_params = match BuddyBaseID::new(buddy_id) {
            Ok(id) => vec![BuddyParam(BuddyUnitID::Base(id), BuddyTeamType::Fighting)],
            Err(_) => Vec::with_capacity(0),
        };

        Ok(Self {
            quest_id: template.id.value(),
            battle_event_id: battle_group.battle_event_id,
            time_period,
            weather,
            start_timestamp: util::cur_timestamp() as i64,
            play_type: Self::get_play_type_by_quest_type(EHollowQuestType::from(
                template.hollow_quest_type,
            )),
            team_data: TeamDataItem::new(&avatars, &buddy_params),
            fight_drop_pool: FightDropPool::new(battle_group.battle_event_id),
            quest_manager: DungeonQuestManager::new_for_battle_group(battle_group.id),
        })
    }

    pub fn get_play_type_by_quest_type(quest_type: EHollowQuestType) -> ELocalPlayType {
        match quest_type {
            EHollowQuestType::NormalBattle => ELocalPlayType::PureHollowBattle,
            EHollowQuestType::BossRushBattle => ELocalPlayType::BossRushBattle,
            EHollowQuestType::DifficutyBattle => ELocalPlayType::PureHollowBattleHardmode,
            _ => ELocalPlayType::PureHollowBattle,
        }
    }
}

impl NapGameMode for HollowGame {
    fn scene_type(&self) -> ESceneType {
        ESceneType::Fight
    }

    fn scene_info(&self) -> Option<SceneInfo> {
        Some(SceneInfo {
            scene_type: self.scene_type() as u32,
            battle_event_id: self.battle_event_id.value(),
            play_type: self.play_type as u32,
            fight_scene_info: Some(FightSceneInfo {
                nmhdkmcabjg: true,
                weather_pool: Some(WeatherPoolInfo {
                    time_period: self.time_period.to_string(),
                    weather: self.weather.to_string(),
                    ..Default::default()
                }),
                fight_drop_info: Some(self.fight_drop_pool.to_client()),
                ..Default::default()
            }),
            ..Default::default()
        })
    }

    fn dungeon_info(&self) -> Option<DungeonInfo> {
        Some(DungeonInfo {
            quest_id: self.quest_id,
            start_timestamp: self.start_timestamp,
            dungeon_item_data: Some(DungeonItemData::default()),
            avatar_list: self
                .team_data
                .avatar_member_list
                .iter()
                .map(AvatarUnit::to_client)
                .collect(),
            buddy_list: self
                .team_data
                .equipped_buddy_list
                .iter()
                .map(BuddyUnit::to_client)
                .collect(),
            dungeon_quest_info: Some(self.quest_manager.to_client()),
            ..Default::default()
        })
    }
}
