use crate::entity::gateway::EntityGateway;
use thiserror::Error;
use std::borrow::Cow;
use crate::ship::items::manager::{ItemManager, ItemManagerError};
use std::collections::HashMap;
use crate::entity::character::{CharacterEntity, CharacterEntityId, TechLevel};
use crate::ship::items::bank::*;
use crate::ship::items::floor::*;
use crate::ship::items::inventory::*;
use crate::ship::items::ClientItemId;
use crate::entity::gateway::GatewayError;

use crate::ship::location::{AreaClient, RoomId};

#[derive(Error, Debug)]
#[error("")]
pub enum TransactionCommitError {
    Gateway(#[from] GatewayError),
    ItemManager(#[from] ItemManagerError),
}

#[async_trait::async_trait]
pub trait ItemAction<EG: EntityGateway>: std::marker::Send + std::marker::Sync {
    async fn commit(&self, manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError>;
}

pub struct ItemTransactionActions<'a, EG: EntityGateway> {
    action_queue: Vec<Box<dyn ItemAction<EG>>>,
    pub manager: &'a ItemManager,
}

    
impl<'a, EG: EntityGateway> ItemTransactionActions<'a, EG> {
    fn new(manager: &'a ItemManager) -> ItemTransactionActions<'a, EG> {
        ItemTransactionActions {
            action_queue: Vec::new(),
            manager
        }
    }

    pub fn action<A: ItemAction<EG> + 'static>(&mut self, action: A) {
    //pub fn action<A: Into<Box<dyn ItemAction<EG>>>>(&mut self, action: A) {
    //pub fn action(&mut self, action: impl ItemAction<EG>) {
        self.action_queue.push(Box::new(action))
        //self.action_queue.push(action.into())
    }
}


pub struct ItemTransaction<'a, T, EG: EntityGateway> {
    data: T,
    actions: ItemTransactionActions<'a, EG>,
}

impl<'a, T, EG: EntityGateway> ItemTransaction<'a, T, EG> {
    pub fn new(manager: &'a ItemManager, arg: T) -> ItemTransaction<'a, T, EG> {
        ItemTransaction {
            data: arg,
            actions: ItemTransactionActions::new(manager),
        }
    }

    /*
    pub fn act<U>(mut self, action: fn(&mut ItemTransactionActions<EG>, &T) -> Result<U, E>) -> ItemTransaction<'a, U, E, EG> {
        if self.error.is_none() {
            let k = action(&mut self.actions, &self.prev.unwrap());
            match k {
                Ok(k) => {
                    ItemTransaction {
                        error: None,
                        prev: Some(k),
                        actions: self.actions,
                    }
                },
                Err(err) => {
                    ItemTransaction {
                        error: Some(err),
                        prev: None,
                        actions: self.actions,
                    }
                }
            }
        }
        else {
            ItemTransaction {
                error: self.error,
                prev: None,
                actions: self.actions,
            }
        }
    }
*/

    pub fn act<E: std::fmt::Debug, U>(mut self, action: fn(&mut ItemTransactionActions<EG>, &T) -> Result<U, E>) -> FinalizedItemTransaction<U, E, EG> {
        match action(&mut self.actions, &self.data) {
            Ok(k) => {
                FinalizedItemTransaction {
                    value: Ok(k),
                    action_queue: self.actions.action_queue,
                }
            },
            Err(err) => {
                FinalizedItemTransaction {
                    value: Err(err),
                    action_queue: Vec::new(),
                }
            }
        }
    }

    /*
    pub fn finalize(self) -> FinalizedItemTransaction<T, E, EG> {
        FinalizedItemTransaction {
            error: self.error,
            prev: self.prev,
            action_queue: self.actions.action_queue,
        }
    }
*/
}


#[derive(Error, Debug)]
#[error("")]
pub enum TransactionError<E: std::fmt::Debug> {
    Action(E),
    Commit(#[from] TransactionCommitError),
    
}

// this only exists to drop the ItemManager borrow of ItemTransaction so a mutable ItemTransaction can be passed in later
pub struct FinalizedItemTransaction<T, E: std::fmt::Debug, EG: EntityGateway> {
    value: Result<T, E>,
    action_queue: Vec<Box<dyn ItemAction<EG>>>,
}

impl<T, E: std::fmt::Debug, EG: EntityGateway> FinalizedItemTransaction<T, E, EG> {
    pub async fn commit(self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<T, TransactionError<E>> {
        match self.value {
            Ok(value) => {
                for action in self.action_queue.into_iter() {
                    // TODO: better handle rolling back if this ever errors out
                    //let result = action.item_action(item_manager).await.map_err(|err| TransactionError::Commit(err.into()))?;
                    //action.gateway_action(entity_gateway, result).await.map_err(|err| TransactionError::Commit(err.into()))?;
                    action.commit(item_manager, entity_gateway).await.map_err(|err| TransactionError::Commit(err))?;
                }
                Ok(value)
            },
            Err(err) => Err(TransactionError::Action(err)),
        }
    }
}


#[cfg(test)]
mod test {
    use super::*;
    use crate::entity::account::{UserAccountId, NewUserAccountEntity, UserAccountEntity};
    use crate::entity::character::{NewCharacterEntity, CharacterEntity};
    use crate::entity::gateway::GatewayError;
    use thiserror::Error;
    
    #[async_std::test]
    async fn test_item_transaction() {
        struct DummyAction1 {
            name: String,
        }
        struct DummyAction2 {
            value: u32,
        }

        #[derive(Error, Debug)]
        #[error("")]
        enum DummyError {
            Error
        }

        #[derive(Default, Clone)]
        struct DummyGateway {
            d1_set: String,
            d2_inc: u32,
        }

        #[async_trait::async_trait]
        impl EntityGateway for DummyGateway {
            async fn create_user(&mut self, user: NewUserAccountEntity) -> Result<UserAccountEntity, GatewayError> {
                self.d1_set = user.username;
                Ok(UserAccountEntity::default())
            }

            async fn create_character(&mut self, char: NewCharacterEntity) -> Result<CharacterEntity, GatewayError> {
                self.d2_inc += char.slot;
                Ok(CharacterEntity::default())
            }
        }
        

        #[async_trait::async_trait]
        impl<EG: EntityGateway> ItemAction<EG> for DummyAction1 {
            async fn commit(&self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> {
                item_manager.id_counter = 55555;
                entity_gateway.create_user(NewUserAccountEntity {
                    username: self.name.clone(),
                    ..NewUserAccountEntity::default()
                })
                    .await?;
                Ok(())
            }
        }

        #[async_trait::async_trait]
        impl<EG: EntityGateway> ItemAction<EG> for DummyAction2 {
            async fn commit(&self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> {
                item_manager.id_counter += self.value;
                entity_gateway.create_character(NewCharacterEntity {
                    slot: self.value,
                    ..NewCharacterEntity::new(UserAccountId(0))
                })
                    .await?;
                Ok(())
            }
        }

        let mut item_manager = ItemManager::default();
        let mut entity_gateway = DummyGateway::default();

        let result = ItemTransaction::new(&item_manager, 12)
            .act(|it, k| {
                it.action(DummyAction1 {name: "asdf".into()});
                it.action(DummyAction2 {value: 11});
                it.action(DummyAction2 {value: *k});
                if *k == 99 {
                    return Err(DummyError::Error)
                }
                Ok(String::from("hello"))
            })
            .commit(&mut item_manager, &mut entity_gateway)
            .await;

        assert!(entity_gateway.d1_set == "asdf");
        assert!(entity_gateway.d2_inc == 23);
        assert!(item_manager.id_counter == 55578);
        assert!(result.unwrap() == "hello");
    }

    #[async_std::test]
    async fn test_item_transaction_with_action_error() {
        struct DummyAction1 {
        }
        struct DummyAction2 {
        }

        #[derive(Error, Debug, PartialEq, Eq)]
        #[error("")]
        enum DummyError {
            Error
        }

        #[derive(Default, Clone)]
        struct DummyGateway {
            d1_set: String,
            d2_inc: u32,
        }

        #[async_trait::async_trait]
        impl EntityGateway for DummyGateway {
            async fn create_character(&mut self, char: NewCharacterEntity) -> Result<CharacterEntity, GatewayError> {
                self.d2_inc += char.slot;
                Ok(CharacterEntity::default())
            }
        }
        

        #[async_trait::async_trait]
        impl<EG: EntityGateway> ItemAction<EG> for DummyAction1 {
            async fn commit(&self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> {
                entity_gateway.create_character(NewCharacterEntity {
                    slot: 1,
                    ..NewCharacterEntity::new(UserAccountId(0))
                })
                    .await?;
                Ok(())
            }
        }

        #[async_trait::async_trait]
        impl<EG: EntityGateway> ItemAction<EG> for DummyAction2 {
            async fn commit(&self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> {
                entity_gateway.create_character(NewCharacterEntity {
                    slot: 1,
                    ..NewCharacterEntity::new(UserAccountId(0))
                })
                    .await?;
                Ok(())
            }
        }

        let mut item_manager = ItemManager::default();
        let mut entity_gateway = DummyGateway::default();

        let result = ItemTransaction::new(&item_manager, 12)
            .act(|it, _| -> Result<(), _> {
                it.action(DummyAction1 {});
                it.action(DummyAction2 {});
                it.action(DummyAction2 {});
                Err(DummyError::Error)
            })
            .commit(&mut item_manager, &mut entity_gateway)
            .await;

        assert!(entity_gateway.d2_inc == 0);
        assert!(matches!(result, Err(TransactionError::Action(DummyError::Error))));
    }

    #[async_std::test]
    async fn test_item_transaction_with_commit_error() {
        struct DummyAction1 {
        }
        struct DummyAction2 {
        }

        #[derive(Error, Debug, PartialEq, Eq)]
        #[error("")]
        enum DummyError {
        }

        #[derive(Default, Clone)]
        struct DummyGateway {
            d1_set: String,
            d2_inc: u32,
        }

        #[async_trait::async_trait]
        impl EntityGateway for DummyGateway {
            async fn create_character(&mut self, char: NewCharacterEntity) -> Result<CharacterEntity, GatewayError> {
                self.d2_inc += char.slot;
                Ok(CharacterEntity::default())
            }
        }
        

        #[async_trait::async_trait]
        impl<EG: EntityGateway> ItemAction<EG> for DummyAction1 {
            async fn commit(&self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> {
                entity_gateway.create_character(NewCharacterEntity {
                    slot: 1,
                    ..NewCharacterEntity::new(UserAccountId(0))
                })
                    .await?;
                Err(GatewayError::Error.into())
            }
        }

        #[async_trait::async_trait]
        impl<EG: EntityGateway> ItemAction<EG> for DummyAction2 {
            async fn commit(&self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> {
                entity_gateway.create_character(NewCharacterEntity {
                    slot: 1,
                    ..NewCharacterEntity::new(UserAccountId(0))
                })
                    .await?;
                Ok(())
            }
        }

        let mut item_manager = ItemManager::default();
        let mut entity_gateway = DummyGateway::default();

        let result = ItemTransaction::new(&item_manager, 12)
            .act(|it, _| -> Result<_, DummyError> {
                it.action(DummyAction1 {});
                it.action(DummyAction2 {});
                it.action(DummyAction2 {});
                Ok(())
            })
            .commit(&mut item_manager, &mut entity_gateway)
            .await;

        // in an ideal world this would be 0 as rollbacks would occur
        assert!(entity_gateway.d2_inc == 1);
        assert!(matches!(result, Err(TransactionError::Commit(TransactionCommitError::Gateway(GatewayError::Error)))));
    }
}