use std::convert::TryInto;
use log::warn;
use rand::Rng;
use rand::seq::SliceRandom;
use libpso::packet::ship::*;
use libpso::packet::messages::*;
use crate::common::leveltable::CharacterLevelTable;
use crate::common::serverstate::ClientId;
use crate::ship::ship::{SendShipPacket, ShipError, Clients, Rooms, ItemShops, TradeItem, TradeState, TradeStatus};
use crate::ship::location::{ClientLocation, ClientLocationError};
use crate::ship::drops::ItemDrop;
use crate::ship::items::{ItemManager, ItemManagerError, ClientItemId, TriggerCreateItem, FloorItem, FloorType, ItemToTradeDetail};
use crate::ship::items::inventory::InventoryItem;
use crate::entity::gateway::EntityGateway;
use crate::entity::item;
use libpso::utf8_to_utf16_array;
use crate::ship::packet::builder;
use crate::ship::shops::{ShopItem, ToolShopItem, ArmorShopItem};

#[derive(thiserror::Error, Debug)]
#[error("")]
pub enum TradeError {
    CouldNotFindTradePartner,
    ClientItemIdDidNotMatchItem(ClientItemId, [u8; 16]),
    InvalidStackAmount(ClientItemId, usize),
    NotInTradeMenu,
}



pub async fn inner_items_to_trade<EG>(id: ClientId,
                                items_to_trade: &ItemsToTrade,
                                entity_gateway: &mut EG,
                                client_location: &ClientLocation,
                                clients: &mut Clients,
                                item_manager: &mut ItemManager)
                                -> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, anyhow::Error>
where
    EG: EntityGateway
{
    let client = clients.get_mut(&id).ok_or(ShipError::ClientNotFound(id))?;
    let inventory = item_manager.get_character_inventory_mut(&client.character)?;
    let trade_partner = client_location.get_client_neighbors(id)?
        .into_iter()
        .filter(|ac| {
            ac.local_client.id() == items_to_trade.trade_target
        })
        .next()
        .ok_or(TradeError::CouldNotFindTradePartner)?;

    let item_blobs = items_to_trade.items.iter().take(items_to_trade.count as usize);
    let trade_items = item_blobs
        .map(|item| {
            // TOOD: meseta?
            let real_item = inventory.get_item_handle_by_id(ClientItemId(item.item_id))
                .ok_or(ItemManagerError::NoSuchItemId(ClientItemId(item.item_id)))?;
            let real_item = real_item
                .item()
                .ok_or(ItemManagerError::NoSuchItemId(ClientItemId(item.item_id)))?;
            let trade_item_bytes: [u8; 16] = item.item_data.iter()
                .chain(item.item_data2.iter())
                .cloned().collect::<Vec<u8>>()
                .try_into()
                .unwrap();
            if real_item.as_client_bytes() == trade_item_bytes {
                match real_item {
                    InventoryItem::Individual(individual_inventory_item) => {
                        Ok(TradeItem::Individual(individual_inventory_item.item_id))
                    },
                    InventoryItem::Stacked(stacked_inventory_item) => {
                        let amount = trade_item_bytes[5] as usize;
                        if amount > stacked_inventory_item.entity_ids.len() {
                            Ok(TradeItem::Stacked(stacked_inventory_item.item_id, amount))
                        }
                        else {
                            Err(TradeError::InvalidStackAmount(stacked_inventory_item.item_id, amount).into())
                        }
                    }
                }
            }
            else {
                Err(TradeError::ClientItemIdDidNotMatchItem(ClientItemId(item.item_id), trade_item_bytes).into())
            }
        })
        .collect::<Result<Vec<_>, anyhow::Error>>()?;

    // TODO: check room in inventory for items
    client.trade = Some(TradeState {
        other_client: trade_partner.client,
        items: trade_items,
        status: TradeStatus::Unconfirmed
    });

    Ok(Box::new(vec![(trade_partner.client, SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {}))].into_iter()))
}

pub async fn items_to_trade<EG>(id: ClientId,
                                items_to_trade_pkt: &ItemsToTrade,
                                entity_gateway: &mut EG,
                                client_location: &ClientLocation,
                                clients: &mut Clients,
                                item_manager: &mut ItemManager)
                                -> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, anyhow::Error>
where
    EG: EntityGateway
{
    let t = inner_items_to_trade(id, items_to_trade_pkt, entity_gateway, client_location, clients, item_manager).await;
    match t {
        Ok(p) => Ok(p),
        Err(err) => {
            let client = clients.get(&id).ok_or(ShipError::ClientNotFound(id))?;
            let trade_partner = client.trade.as_ref()
                .and_then(|trade_state| {
                    client_location.get_local_client(trade_state.other_client).ok()
                })
                .map(|trade_partner| {
                    (trade_partner.client, SendShipPacket::CancelTrade(CancelTrade {}))
                });

            log::warn!("error in trading: {:?}", err);
            Ok(Box::new(vec![(id, SendShipPacket::CancelTrade(CancelTrade {}))]
                        .into_iter()
                        .chain(trade_partner.into_iter())))
        }
    }
}

pub async fn trade_confirmed<EG>(id: ClientId,
                                 entity_gateway: &mut EG,
                                 client_location: &ClientLocation,
                                 clients: &mut Clients,
                                 item_manager: &mut ItemManager)
                                 -> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, anyhow::Error>
where
    EG: EntityGateway
{
    {
        let this_client = clients.get_mut(&id).ok_or(ShipError::ClientNotFound(id))?;
        this_client.trade.as_mut().ok_or(TradeError::NotInTradeMenu)?.status = TradeStatus::Confirmed;
    }

    let both_confirmed = {
        let this_client = clients.get(&id).ok_or(ShipError::ClientNotFound(id))?;
        let other_client_id = this_client.trade.as_ref().ok_or(TradeError::NotInTradeMenu)?.other_client;
        let other_client = clients.get(&other_client_id).ok_or(ShipError::ClientNotFound(other_client_id))?;

        let this_client_trade = this_client.trade.as_ref().ok_or(TradeError::NotInTradeMenu)?;
        let other_client_trade = other_client.trade.as_ref().ok_or(TradeError::NotInTradeMenu)?;
        this_client_trade.status == TradeStatus::Confirmed && other_client_trade.status == TradeStatus::Confirmed
    };

    if both_confirmed {
        let this_client_trade = {
            let this_client = clients.get_mut(&id).ok_or(ShipError::ClientNotFound(id))?;
            let this_client_trade = this_client.trade.as_ref().ok_or(TradeError::NotInTradeMenu)?.clone();
            this_client.trade = None;
            this_client_trade
        };
        let other_client_trade = {
            let other_client = clients.get_mut(&this_client_trade.other_client).ok_or(ShipError::ClientNotFound(this_client_trade.other_client))?;
            let other_client_trade = other_client.trade.as_ref().ok_or(TradeError::NotInTradeMenu)?.clone();
            other_client.trade = None;
            other_client_trade
        };
        let this_client = clients.get(&id).ok_or(ShipError::ClientNotFound(id))?;
        let other_client = clients.get(&this_client_trade.other_client).ok_or(ShipError::ClientNotFound(this_client_trade.other_client))?;

        let this_local_client = client_location.get_local_client(id)?;
        let other_local_client = client_location.get_local_client(this_client_trade.other_client)?;

        let traded_items = item_manager.trade_items(
            entity_gateway,
            (&this_local_client, &this_client.character, &this_client_trade.items),
            (&other_local_client, &other_client.character, &other_client_trade.items)
        ).await?;

        let clients_in_room = client_location.get_all_clients_by_client(id)?;
        let traded_item_packets = traded_items
            .into_iter()
            .map(|item| {
                match item.item_detail {
                    ItemToTradeDetail::Individual(item_detail) => {
                        [
                            GameMessage::CreateItem(builder::message::create_individual_item(item.add_to, item.item_id, &item_detail).unwrap()),
                            GameMessage::PlayerNoLongerHasItem(builder::message::player_no_longer_has_item(item.remove_from, item.item_id, 1)) // TODO: amount = ?
                        ]
                    },
                    ItemToTradeDetail::Stacked(tool, amount) => {
                        [
                            GameMessage::CreateItem(builder::message::create_stacked_item(item.add_to, item.item_id, &tool, amount).unwrap()),
                            GameMessage::PlayerNoLongerHasItem(builder::message::player_no_longer_has_item(item.remove_from, item.item_id, amount as u32))
                        ]
                    },
                }
            })
            .flatten()
            .map(move |packet| {
                clients_in_room
                    .clone()
                    .into_iter()
                    .map(move |client| {
                        (client.client, SendShipPacket::Message(Message::new(packet.clone())))
                    })
            })
            .flatten();
        Ok(Box::new(traded_item_packets))
    }
    else {
        Ok(Box::new(None.into_iter()))
    }
}