use data::{
    gacha::{
        gacha_config::{CharacterGachaPool, GachaAddedItemType},
        global_gacha_config,
    },
    tables::{AvatarBaseID, ItemID, WeaponID},
};
use proto::{GainItemInfo, GetGachaDataScRsp};

use super::*;
use crate::{
    handlers::core::NetError,
    logic::{item::ItemModel, role::RoleModel},
};
use chrono::{DateTime, Local};

pub async fn on_get_gacha_data(
    _session: &NetSession,
    _player: &mut Player,
    req: GetGachaDataCsReq,
) -> NetResult<GetGachaDataScRsp> {
    if req.gacha_type != 3 {
        // tracing::info!("non-supported gacha type {}", body.gacha_type);
        Ok(GetGachaDataScRsp {
            retcode: Retcode::RetSucc.into(),
            gacha_type: req.gacha_type,
            gacha_data: Some(GachaData::default()),
        })
    } else {
        // tracing::info!("construct gacha info");
        Ok(GetGachaDataScRsp {
            retcode: Retcode::RetSucc.into(),
            gacha_type: req.gacha_type,
            gacha_data: Some(_player.gacha_model.to_client(&Local::now())),
        })
    }
}

pub async fn on_do_gacha(
    _session: &NetSession,
    _player: &mut Player,
    req: DoGachaCsReq,
) -> NetResult<DoGachaScRsp> {
    let gachaconf = global_gacha_config();
    let gacha_model = &mut _player.gacha_model;
    let item_model = &mut _player.item_model;
    let role_model = &mut _player.role_model;
    let pull_time = Local::now();
    let target_pool = get_gacha_pool(
        &gachaconf.character_gacha_pool_list,
        &req.gacha_parent_schedule_id,
        &pull_time,
    )?;

    // tracing::info!("cost_item_count: {}", req.cost_item_count);
    let pull_count = if req.cost_item_count > 1 { 10 } else { 1 };
    let mut cost_count = gacha_model.get_actual_cost_count(target_pool, &pull_count);
    if pull_count != req.cost_item_count {
        tracing::info!(
            "refuse gacha because: expected cost item {cost_count}, found {}",
            req.cost_item_count
        );
        return Err(NetError::from(Retcode::RetFail));
    } else {
        // TODO: cost resource
    }

    let mut gain_item_list: Vec<GainItemInfo> = vec![];
    while cost_count > 0 {
        let pull_result = gacha_model.perform_pull_pool(&pull_time, target_pool);

        let uid = add_item(
            role_model,
            item_model,
            &pull_result.obtained_item_id,
            &pull_result.item_type,
        )?;
        let (mut extra_item_id, mut extra_item_count) = (0, 0);
        if let Some(extra_resources) = &pull_result.extra_resources {
            (extra_item_id, extra_item_count) = (
                extra_resources.extra_item_id.value(),
                extra_resources.extra_item_count.clone(),
            );
            item_model.add_resource(extra_item_id, extra_item_count);
        }
        gain_item_list.push(GainItemInfo {
            item_id: pull_result.obtained_item_id.value(),
            extra_item_id,
            extra_item_count,
            uid,
            num: 1,
            ..GainItemInfo::default()
        });
        cost_count -= 1;
        gacha_model.gacha_records.push(pull_result);
    }

    _session
        .notify(construct_sync(role_model, item_model))
        .await?;
    Ok(DoGachaScRsp {
        retcode: Retcode::RetSucc.into(),
        gain_item_list,
        gacha_data: Some(gacha_model.to_client(&pull_time)),
        cost_item_count: req.cost_item_count,
    })
}

pub async fn on_gacha_free_agent(
    _session: &NetSession,
    _player: &mut Player,
    req: GachaFreeAgentCsReq,
) -> NetResult<GachaFreeAgentScRsp> {
    let gachaconf = global_gacha_config();
    let gacha_model = &mut _player.gacha_model;
    let role_model = &mut _player.role_model;
    let item_model = &mut _player.item_model;
    let pull_time = Local::now();
    let target_pool = get_gacha_pool(
        &gachaconf.character_gacha_pool_list,
        &req.gacha_parent_schedule_id,
        &pull_time,
    )?;

    let item_id = ItemID::new(req.avatar_id);
    if let None = item_id {
        return Err(NetError::from(Retcode::RetFail));
    }
    let item_id = item_id.unwrap();

    let item_type = gacha_model.request_free_agent(target_pool, &item_id);
    if item_type == GachaAddedItemType::None {
        return Err(NetError::from(Retcode::RetFail));
    }
    let _ = add_item(role_model, item_model, &item_id, &item_type);

    _session
        .notify(construct_sync(role_model, item_model))
        .await?;
    Ok(GachaFreeAgentScRsp {
        retcode: Retcode::RetSucc.into(),
    })
}

pub async fn on_choose_gacha_up(
    _session: &NetSession,
    _player: &mut Player,
    req: ChooseGachaUpCsReq,
) -> NetResult<ChooseGachaUpScRsp> {
    let gachaconf = global_gacha_config();
    let gacha_model = &mut _player.gacha_model;
    let pull_time = Local::now();
    let target_pool = get_gacha_pool(
        &gachaconf.character_gacha_pool_list,
        &req.gacha_parent_schedule_id,
        &pull_time,
    )?;

    let item_id = ItemID::new(req.item_id);
    if let None = item_id {
        return Err(NetError::from(Retcode::RetFail));
    }
    let item_id = item_id.unwrap();

    Ok(ChooseGachaUpScRsp {
        retcode: if gacha_model.choose_gacha_up(target_pool, &item_id) {
            Retcode::RetSucc.into()
        } else {
            Retcode::RetFail.into()
        },
        ..Default::default()
    })
}

fn get_gacha_pool<'conf>(
    character_gacha_pool_list: &'conf Vec<CharacterGachaPool>,
    gacha_parent_schedule_id: &u32,
    pull_time: &DateTime<Local>,
) -> NetResult<&'conf CharacterGachaPool> {
    for target_pool in character_gacha_pool_list.iter() {
        if &target_pool.gacha_parent_schedule_id == gacha_parent_schedule_id
            && target_pool.is_still_open(pull_time)
        {
            return Ok(target_pool);
        }
    }
    tracing::info!(
        "refuse gacha op because: pool of parent_schedule_id {} not found or isn't in open time",
        gacha_parent_schedule_id
    );
    Err(NetError::from(Retcode::RetFail))
}

/// Return is item UID (weapon specific)
fn add_item(
    role_model: &mut RoleModel,
    item_model: &mut ItemModel,
    item_id: &ItemID,
    item_type: &GachaAddedItemType,
) -> NetResult<u32> {
    match item_type {
        GachaAddedItemType::Character => match AvatarBaseID::new(item_id.value()) {
            Some(avatar_id) => {
                role_model.add_avatar(avatar_id);
                Ok(0)
            }
            None => {
                tracing::info!("add item failed for avatar id {item_id}");
                Err(NetError::from(Retcode::RetFail))
            }
        },
        GachaAddedItemType::Weapon => match WeaponID::new(item_id.value()) {
            Some(weapon_id) => Ok(item_model.add_weapon(weapon_id).value()),
            None => {
                tracing::info!("add item failed for weapon id {item_id}");
                Err(NetError::from(Retcode::RetFail))
            }
        },
        GachaAddedItemType::Bangboo => Ok(0),
        _ => {
            tracing::info!(
                "add item failed due to undefined item type (from {item_id}) in configuration"
            );
            Err(NetError::from(Retcode::RetFail))
        }
    }
}

fn construct_sync(role_model: &RoleModel, item_model: &ItemModel) -> PlayerSyncScNotify {
    PlayerSyncScNotify {
        avatar: Some(role_model.avatar_sync()),
        item_sync: Some(item_model.item_sync()),
        ..Default::default()
    }
}
