use elseware::common::serverstate::{ClientId, ServerState};
use elseware::entity::gateway::{EntityGateway, InMemoryGateway};
use elseware::entity::item;
use elseware::ship::ship::{ShipServerState, RecvShipPacket};
use elseware::entity::character::TechLevel;
//use elseware::ship::items::{ClientItemId, ActiveItemEntityId, HeldItemType, FloorItemType};

use libpso::packet::ship::*;
use libpso::packet::messages::*;

#[path = "common.rs"]
mod common;
use common::*;


#[async_std::test]
async fn test_use_monomate() {
    let mut entity_gateway = InMemoryGateway::default();

    let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a", 1).await;

    let mut p1_items = Vec::new();
    for tool in vec![item::tool::ToolType::Monomate, item::tool::ToolType::Monofluid].into_iter() {
        let mut item = Vec::new();
        for _ in 0..2usize {
            item.push(entity_gateway.create_item(
                item::NewItemEntity {
                    item: item::ItemDetail::Tool(
                        item::tool::Tool {
                            tool: tool
                        }
                    ),
                }).await.unwrap());
        }
        p1_items.push(item::InventoryItemEntity::Stacked(item));
    }

    entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_items)).await.unwrap();

    let mut ship = Box::new(ShipServerState::builder()
        .gateway(entity_gateway.clone())
        .build());
    log_in_char(&mut ship, ClientId(1), "a1", "a").await;
    join_lobby(&mut ship, ClientId(1)).await;
    create_room(&mut ship, ClientId(1), "room", "").await;

    ship.handle(ClientId(1), RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem {
        client: 0,
        target: 0,
        item_id: 0x10000,
    })))).await.unwrap();

    let inventory_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap();
    assert_eq!(inventory_items.items.len(), 2);
    inventory_items.items[0].with_stacked(|items| {
        assert_eq!(items.len(), 1)
    }).unwrap();
    inventory_items.items[1].with_stacked(|items| {
        assert_eq!(items.len(), 2)
    }).unwrap();
}

#[async_std::test]
async fn test_use_monomate_twice() {
    let mut entity_gateway = InMemoryGateway::default();

    let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a", 1).await;

    let mut p1_items = Vec::new();
    for tool in vec![item::tool::ToolType::Monomate, item::tool::ToolType::Monofluid].into_iter() {
        let mut item = Vec::new();
        for _ in 0..3usize {
            item.push(entity_gateway.create_item(
                item::NewItemEntity {
                    item: item::ItemDetail::Tool(
                        item::tool::Tool {
                            tool: tool
                        }
                    ),
                }).await.unwrap());
        }
        p1_items.push(item::InventoryItemEntity::Stacked(item));
    }

    entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_items)).await.unwrap();

    let mut ship = Box::new(ShipServerState::builder()
        .gateway(entity_gateway.clone())
        .build());
    log_in_char(&mut ship, ClientId(1), "a1", "a").await;
    join_lobby(&mut ship, ClientId(1)).await;
    create_room(&mut ship, ClientId(1), "room", "").await;

    ship.handle(ClientId(1), RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem {
        client: 0,
        target: 0,
        item_id: 0x10000,
    })))).await.unwrap();
    ship.handle(ClientId(1), RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem {
        client: 0,
        target: 0,
        item_id: 0x10000,
    })))).await.unwrap();

    let inventory_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap();
    assert_eq!(inventory_items.items.len(), 2);
    inventory_items.items[0].with_stacked(|items| {
        assert_eq!(items.len(), 1)
    }).unwrap();
    inventory_items.items[1].with_stacked(|items| {
        assert_eq!(items.len(), 3)
    }).unwrap();
}

#[async_std::test]
async fn test_use_last_monomate() {
    let mut entity_gateway = InMemoryGateway::default();

    let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a", 1).await;

    let mut p1_inv = Vec::new();
    for tool in vec![item::tool::ToolType::Monomate, item::tool::ToolType::Monofluid].into_iter() {
        p1_inv.push(item::InventoryItemEntity::Stacked(vec![entity_gateway.create_item(
            item::NewItemEntity {
                item: item::ItemDetail::Tool(
                    item::tool::Tool {
                        tool: tool
                    }
                ),
            }).await.unwrap()]));
    }

    entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_inv)).await.unwrap();

    let mut ship = Box::new(ShipServerState::builder()
        .gateway(entity_gateway.clone())
        .build());
    log_in_char(&mut ship, ClientId(1), "a1", "a").await;
    join_lobby(&mut ship, ClientId(1)).await;
    create_room(&mut ship, ClientId(1), "room", "").await;

    ship.handle(ClientId(1), RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem {
        client: 0,
        target: 0,
        item_id: 0x10000,
    })))).await.unwrap();


    let inventory_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap();
    assert_eq!(inventory_items.items.len(), 1);
    inventory_items.items[0].with_stacked(|items| {
        assert_eq!(items.len(), 1);
        assert_eq!(items[0].item.item_type(), item::ItemType::Tool(item::tool::ToolType::Monofluid));
    }).unwrap();
}

#[async_std::test]
async fn test_use_nonstackable_tool() {
    let mut entity_gateway = InMemoryGateway::default();

    let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a", 1).await;

    let mut p1_items = Vec::new();
    p1_items.push(entity_gateway.create_item(
        item::NewItemEntity {
            item: item::ItemDetail::Tool(
                item::tool::Tool {
                    tool: item::tool::ToolType::HuntersReport,
                }
            ),
        }).await.unwrap());

    entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_items)).await.unwrap();

    let mut ship = Box::new(ShipServerState::builder()
        .gateway(entity_gateway.clone())
        .build());
    log_in_char(&mut ship, ClientId(1), "a1", "a").await;
    join_lobby(&mut ship, ClientId(1)).await;
    create_room(&mut ship, ClientId(1), "room", "").await;

    ship.handle(ClientId(1), RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem {
        client: 0,
        target: 0,
        item_id: 0x10000,
    })))).await.unwrap();

    let inventory_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap();
    assert_eq!(inventory_items.items.len(), 0);
}

#[async_std::test]
async fn test_use_materials() {
    let mut entity_gateway = InMemoryGateway::default();

    let (user1, char1) = new_user_character(&mut entity_gateway, "a1", "a", 1).await;

    let mut p1_inv = Vec::new();
    for tool in vec![item::tool::ToolType::PowerMaterial, item::tool::ToolType::LuckMaterial].into_iter() {
        let mut item = Vec::new();
        for _ in 0..5usize {
            item.push(entity_gateway.create_item(
                item::NewItemEntity {
                    item: item::ItemDetail::Tool(
                        item::tool::Tool {
                            tool: tool
                        }
                    ),
                }).await.unwrap());
        }
        p1_inv.push(item::InventoryItemEntity::Stacked(item));
    }

    entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_inv)).await.unwrap();

    let mut ship = Box::new(ShipServerState::builder()
        .gateway(entity_gateway.clone())
        .build());
    log_in_char(&mut ship, ClientId(1), "a1", "a").await;
    join_lobby(&mut ship, ClientId(1)).await;
    create_room(&mut ship, ClientId(1), "room", "").await;

    ship.handle(ClientId(1), RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem {
        client: 0,
        target: 0,
        item_id: 0x10000,
    })))).await.unwrap();
    ship.handle(ClientId(1), RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem {
        client: 0,
        target: 0,
        item_id: 0x10001,
    })))).await.unwrap();
    ship.handle(ClientId(1), RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem {
        client: 0,
        target: 0,
        item_id: 0x10001,
    })))).await.unwrap();

    let inventory_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap();
    assert_eq!(inventory_items.items.len(), 2);
    inventory_items.items[0].with_stacked(|items| {
        assert_eq!(items.len(), 4)
    }).unwrap();
    inventory_items.items[1].with_stacked(|items| {
        assert_eq!(items.len(), 3)
    }).unwrap();

    let characters = entity_gateway.get_characters_by_user(&user1).await.unwrap();
    let char = characters[0].as_ref().unwrap();

    assert!(char.materials.power == 1);
    assert!(char.materials.luck == 2);
}

#[async_std::test]
async fn test_jackolantern() {
    let mut entity_gateway = InMemoryGateway::default();
    let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a", 1).await;

    let p1_inv = vec![
        item::InventoryItemEntity::Stacked(
            vec![
                entity_gateway.create_item(
                    item::NewItemEntity {
                        item: item::ItemDetail::Tool(
                            item::tool::Tool {
                                tool: item::tool::ToolType::JackOLantern,
                            }
                        ),
                    }).await.unwrap(),
                entity_gateway.create_item(item::NewItemEntity {
                    item: item::ItemDetail::Tool(
                        item::tool::Tool {
                            tool: item::tool::ToolType::JackOLantern,
                        }
                    ),
                }).await.unwrap(),
            ])];
    entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_inv)).await.unwrap();

    let mut ship = Box::new(ShipServerState::builder()
        .gateway(entity_gateway.clone())
        .build());
    log_in_char(&mut ship, ClientId(1), "a1", "a").await;
    join_lobby(&mut ship, ClientId(1)).await;
    create_room(&mut ship, ClientId(1), "room", "").await;

    ship.handle(ClientId(1), RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem {
        client: 0,
        target: 0,
        item_id: 0x10000,
    })))).await.unwrap();

    ship.handle(ClientId(1), RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem {
        client: 0,
        target: 0,
        item_id: 0x10000,
    })))).await.unwrap();

    let inventory_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap();
    for item in inventory_items.items {
        for sitem in item.stacked().unwrap() {
            assert!(sitem.item.clone().as_tool().unwrap().tool.is_mag_cell());
        }
    }
}


#[async_std::test]
async fn test_use_barta_1() {
    let mut entity_gateway = InMemoryGateway::default();

    let (user1, char1) = new_user_character(&mut entity_gateway, "a1", "a", 1).await;

    let inv = vec![
        entity_gateway.create_item(
            item::NewItemEntity {
                item: item::ItemDetail::TechniqueDisk(
                    item::tech::TechniqueDisk {
                        tech: item::tech::Technique::Foie,
                        level: 3,
                    }
                )
            }
        ).await.unwrap(),
        entity_gateway.create_item(
            item::NewItemEntity {
                item: item::ItemDetail::TechniqueDisk(
                    item::tech::TechniqueDisk {
                        tech: item::tech::Technique::Barta,
                        level: 4,
                    }
                )
            }
        ).await.unwrap(),
        entity_gateway.create_item(
            item::NewItemEntity {
                item: item::ItemDetail::TechniqueDisk(
                    item::tech::TechniqueDisk {
                        tech: item::tech::Technique::Zonde,
                        level: 5,
                    }
                )
            }
        ).await.unwrap()
    ];

    entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(inv)).await.unwrap();

    let mut ship = Box::new(ShipServerState::builder()
        .gateway(entity_gateway.clone())
        .build());
    log_in_char(&mut ship, ClientId(1), "a1", "a").await;
    join_lobby(&mut ship, ClientId(1)).await;
    create_room(&mut ship, ClientId(1), "room", "").await;

    ship.handle(ClientId(1), RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem {
        client: 0,
        target: 0,
        item_id: 0x10000,
    })))).await.unwrap();

    ship.handle(ClientId(1), RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem {
        client: 0,
        target: 0,
        item_id: 0x10002,
    })))).await.unwrap();

    let characters = entity_gateway.get_characters_by_user(&user1).await.unwrap();
    let char = characters[0].as_ref().unwrap();

    assert_eq!(char.techs.techs.len(), 2);
    assert_eq!(char.techs.techs.get(&item::tech::Technique::Foie).unwrap(), &TechLevel(3));
    assert_eq!(char.techs.techs.get(&item::tech::Technique::Zonde).unwrap(), &TechLevel(5));
    assert!(char.techs.techs.get(&item::tech::Technique::Barta).is_none());
}


#[async_std::test]
async fn test_use_monogrinder() {
    let mut entity_gateway = InMemoryGateway::default();

    let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a", 1).await;

    let saber = entity_gateway.create_item(
        item::NewItemEntity {
            item: item::ItemDetail::Weapon(
                item::weapon::Weapon {
                    weapon: item::weapon::WeaponType::Saber,
                    grind: 0,
                    special: None,
                    attrs: [None, None, None],
                    tekked: true,
                }
            ),
        }).await.unwrap();

    let mut grinders = Vec::new();
    for _ in 0..3usize {
        grinders.push(entity_gateway.create_item(
            item::NewItemEntity {
                item: item::ItemDetail::Tool(
                    item::tool::Tool {
                        tool: item::tool::ToolType::Monogrinder,
                    }
                ),
            }).await.unwrap());
    }

    let equipped = item::EquippedEntity {
        weapon: Some(saber.id),
        armor: None,
        shield: None,
        unit: [None; 4],
        mag: None,
    };
    entity_gateway.set_character_equips(&char1.id, &equipped).await.unwrap();
    entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(vec![item::InventoryItemEntity::Individual(saber),
                                                                                       item::InventoryItemEntity::Stacked(grinders)])).await.unwrap();

    let mut ship = Box::new(ShipServerState::builder()
        .gateway(entity_gateway.clone())
        .build());
    log_in_char(&mut ship, ClientId(1), "a1", "a").await;
    join_lobby(&mut ship, ClientId(1)).await;
    create_room(&mut ship, ClientId(1), "room", "").await;

    ship.handle(ClientId(1), RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem {
        client: 0,
        target: 0,
        item_id: 0x10001,
    })))).await.unwrap();

    ship.handle(ClientId(1), RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem {
        client: 0,
        target: 0,
        item_id: 0x10001,
    })))).await.unwrap();

    let inventory_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap();
    assert_eq!(inventory_items.items.len(), 2);

    assert!(matches!(inventory_items.items[0], item::InventoryItemEntity::Individual(item::ItemEntity{ item: item::ItemDetail::Weapon(item::weapon::Weapon {grind: 2, ..}), ..})));
}




// TODO: tests for ALL ITEMS WOW

/*
#[async_std::test]
pub async fn test_learn_new_tech() {}

#[async_std::test]
pub async fn test_new_fo_has_foie_1() {}

#[async_std::test]
pub async fn test_char_cannot_use_lower_level_tech() {}

#[async_std::test]
pub async fn test_char_cannot_learn_wrong_tech() {}

#[async_std::test]
pub async fn test_char_cannot_learn_high_level_tech() {}

#[async_std::test]
pub async fn test_android_cannot_learn_tech() {}
*/