using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Timers;
using GTANetworkAPI;
using Newtonsoft.Json;
using ReallifeGamemode.Database.Entities;
using ReallifeGamemode.Database.Models;
using ReallifeGamemode.Server.Extensions;
using ReallifeGamemode.Server.Inventory;
using ReallifeGamemode.Server.Inventory.Interfaces;
using ReallifeGamemode.Server.Services;
using ReallifeGamemode.Server.Util;
using ReallifeGamemode.Services;
using ReallifeGamemode.Server.Types;


/**
* @overview Life of German Reallife - Managers InventoryManager (InventoryManager.cs)
* @author hydrant, VegaZ
* @copyright (c) 2008 - 2018 Life of German
*/

namespace ReallifeGamemode.Server.Managers
{
  public class InventoryManager : Script
  {
    public static List<IItem> itemList;

    private static Dictionary<int, (int[], int[])> TradeItems { get; set; } = new Dictionary<int, (int[], int[])>();
    private static Dictionary<int, Timer> TradeTimer { get; set; } = new Dictionary<int, Timer>();
    private static Dictionary<int, int> TradeValue { get; set; } = new Dictionary<int, int>();

    public static Dictionary<Player, List<InventoryItem>> backpackItems { get; set; } = new Dictionary<Player, List<InventoryItem>>();
    public static Dictionary<Player, List<InventoryItem>> vehicleItems { get; set; } = new Dictionary<Player, List<InventoryItem>>();
    private static Timer aTimer;
    public class InventoryItem
    {
      public string Name;
      public int Amount;
      public int ItemId;
      public int Weight;
      public Vehicle vehicle;
    }

    public static void SetBackpackItems(Player player)
    {
      List<UserItem> itemList = GetUserItems(player);
      if (itemList == null) return;

      if (!backpackItems.ContainsKey(player))
        backpackItems.Add(player, new List<InventoryItem>());

      backpackItems[player] = new List<InventoryItem>();

      foreach (var i in itemList)
      {
        var newItem = new InventoryItem()
        {
          Name = GetItemById(i.ItemId).Name,
          Amount = i.Amount,
          ItemId = i.ItemId,
          Weight = GetUserInventoryWeight(player),
        };
        backpackItems[player].Add(newItem);
      }

      player.SetSharedData("backpackItems", JsonConvert.SerializeObject(backpackItems[player].ToArray()));
    }

    [RemoteEvent("CLIENT:getVehicleInventory")]
    public static void SetVehicleItems(Player player)
    {
      if (player.IsInVehicle)
      {
        player.TriggerEvent("Error", "Du kannst dein Kofferraum nicht betätigen.");
      }
      Vehicle veh = NAPI.Pools.GetAllVehicles().ToList().Where(v => v.Position.DistanceTo(player.Position) <= 2).FirstOrDefault();
      if (veh == null)
      {
        player.TriggerEvent("Error", "Kein Fahrzeug in der Nähe !");
        return;
      }
      List<VehicleItem> itemList = GetVehicleItems(veh);

      if (itemList == null)
      {
        player.TriggerEvent("Error", "Kofferraum ist leer !");
        return;
      }

      if (!vehicleItems.ContainsKey(player))
        vehicleItems.Add(player, new List<InventoryItem>());
      //VehicleStreaming.SetDoorState(veh, DoorID.DoorTrunk, DoorState.DoorOpen);

      vehicleItems[player] = new List<InventoryItem>();

      foreach (var i in itemList)
      {
        var newItem = new InventoryItem
        {
          Name = GetItemById(i.ItemId).Name,
          Amount = i.Amount,
          ItemId = i.ItemId,
          Weight = GetVehicleInventoryWeight(veh),
          vehicle = veh,
        };
        vehicleItems[player].Add(newItem);
      }

      player.SetSharedData("vehicleItems", JsonConvert.SerializeObject(vehicleItems[player].ToArray()));
    }

    public static void LoadItems()
    {
      itemList = new List<IItem>();

      Type[] allTypes = Assembly.GetExecutingAssembly().GetTypes();
      foreach (Type item in allTypes)
      {
        if (item.GetInterfaces().Contains((typeof(IItem))) && !item.IsAbstract)
        {
          NAPI.Util.ConsoleOutput($"Loading Item {item.Name}");
          if (Activator.CreateInstance(item) is IItem o)
          {
            if (GetItemById(o.Id) != null)
            {
              throw new InvalidOperationException($"Double ItemID found: {o.Id} | {o.Name}");
            }
            itemList.Add(o);
          }
        }
      }
    }

    public static IItem GetItemById(int id)
    {
      return itemList.Find(i => i.Id == id);
    }

    public static IItem GetItemByName(string name)
    {
      return itemList.Find(i => i.Name.ToLower() == name.ToLower());
    }

    public static void RemoveUserItem(User user, UserItem item, int amount)
    {
      using (var dbContext = new DatabaseContext())
      {
        var userItem = dbContext.UserItems.FirstOrDefault(i => i.Id == item.Id);

        userItem.Amount -= amount;

        dbContext.SaveChanges();

        if (userItem.Amount == 0)
        {
          dbContext.Remove(userItem);
          dbContext.SaveChanges();
        }
        Player player = user.Player;

        List<UserItem> itemList = GetUserItems(player);
        if (itemList == null) return;
        dbContext.SaveChanges();
      }
    }

    public static void RemoveVehicleItem(ServerVehicle sVeh, VehicleItem item, int amount, Player player)
    {
      using (var dbContext = new DatabaseContext())
      {
        var vehicleItem = dbContext.VehicleItems.FirstOrDefault(i => i.Id == item.Id);

        vehicleItem.Amount -= amount;

        dbContext.SaveChanges();

        if (vehicleItem.Amount == 0)
        {
          dbContext.Remove(vehicleItem);
          dbContext.SaveChanges();
        }
        Vehicle vehicle = sVeh.GetVehicle();
        List<VehicleItem> itemList = GetVehicleItems(vehicle);
        if (itemList == null) return;
        dbContext.SaveChanges();
      }
    }

    public static List<VehicleItem> GetVehicleItems(Vehicle vehicle)
    {
      return new DatabaseContext().VehicleItems.ToList().FindAll(i => i.GetVehicle().GetVehicle() == vehicle);
    }

    public static List<UserItem> GetUserItems(Player player)
    {
      var user = player.GetUser();

      using (var context = new DatabaseContext())
      {
        return context.UserItems.ToList().FindAll(i => i.UserId == user.Id);
      }
    }

    public static int GetUserInventoryWeight(Player player)
    {
      var user = player.GetUser();
      var inventoryWeight = 0;

      using (var context = new DatabaseContext())
      {
        List<UserItem> userItems = context.UserItems.ToList().FindAll(i => i.UserId == user.Id);
        foreach (var item in userItems)
        {
          IItem iItem = GetItemById(item.ItemId);
          var currentItemWeight = iItem.Gewicht * item.Amount;
          inventoryWeight += currentItemWeight;
        }
      }

      return inventoryWeight;
    }

    public static int GetVehicleInventoryWeight(Vehicle veh)
    {
      var inventoryWeight = 0;

      using (var context = new DatabaseContext())
      {
        ServerVehicle serverVehicle = VehicleManager.GetServerVehicleFromVehicle(veh, context);
        List<VehicleItem> vehItems = context.VehicleItems.ToList().FindAll(i => i.VehicleId == serverVehicle.Id);
        foreach (var item in vehItems)
        {
          IItem iItem = GetItemById(item.ItemId);
          var currentItemWeight = iItem.Gewicht * item.Amount;
          inventoryWeight += currentItemWeight;
        }
      }

      return inventoryWeight;
    }

    public static UserItem UserHasThisItem(Player player, int itemId)
    {
      var user = player.GetUser();
      using (var context = new DatabaseContext())
      {
        UserItem existingItem = context.UserItems.FirstOrDefault(i => i.UserId == user.Id && i.ItemId == itemId);
        return existingItem;
      }
    }

    public static void GetUserItemsAsAdmin(Player player, User user)
    {
      var inventoryWeight = 0;

      using (var context = new DatabaseContext())
      {
        List<UserItem> userItems = context.UserItems.ToList().FindAll(i => i.UserId == user.Id);
        string[][] items = new string[userItems.Count][];
        foreach (var item in userItems)
        {
          IItem iItem = GetItemById(item.ItemId);
          var currentItemWeight = iItem.Gewicht * item.Amount;
          inventoryWeight += currentItemWeight;

          items[userItems.IndexOf(item)] = new string[6];
          items[userItems.IndexOf(item)][0] = iItem.Name;
          items[userItems.IndexOf(item)][1] = iItem.Description;
          items[userItems.IndexOf(item)][2] = iItem.Gewicht.ToString();
          items[userItems.IndexOf(item)][3] = item.Amount.ToString();
          items[userItems.IndexOf(item)][4] = item.Slot.ToString();
          items[userItems.IndexOf(item)][5] = item.Id.ToString();
        }
        player.TriggerEvent("showInventoryToAdmin", user.Name, inventoryWeight, items);
      }
    }

    [RemoteEvent("transferToBackpack")]
    public void TransferToBackpack(Player player, string jsonItemID, string jsonItemAmount)
    {
      var itemID = JsonConvert.DeserializeObject<int>(jsonItemID);
      var itemAmount = JsonConvert.DeserializeObject<int>(jsonItemAmount);

      using (var context = new DatabaseContext())
      {
        if (!vehicleItems.ContainsKey(player))
          vehicleItems.Add(player, new List<InventoryItem>());
        Vehicle veh = null;
        if (vehicleItems[player].Count != 0)
          veh = vehicleItems[player].FirstOrDefault().vehicle;

        ServerVehicle serverVehicle = VehicleManager.GetServerVehicleFromVehicle(veh, context);

        VehicleItem vehItem = context.VehicleItems.Where(v => v.VehicleId == serverVehicle.Id && v.ItemId == itemID).FirstOrDefault();
        if (vehItem == null)
        {
          player.TriggerEvent("Error", "Transfer nicht möglich.");
          return;
        }

        RemoveVehicleItem(serverVehicle, vehItem, itemAmount, player);
        SetVehicleItems(player);

        AddItemToInventory(player, itemID, itemAmount);

        SetBackpackItems(player);
        context.SaveChanges();
        IItem iItem = GetItemById(vehItem.ItemId);
      }
    }

    [RemoteEvent("transferToVehicle")]
    public void TransferToVehicle(Player player, string jsonItemID, string jsonItemAmount)
    {
      var user = player.GetUser();
      var itemID = JsonConvert.DeserializeObject<int>(jsonItemID);
      var itemAmount = JsonConvert.DeserializeObject<int>(jsonItemAmount);

      using (var context = new DatabaseContext())
      {
        UserItem item = context.UserItems.Where(u => u.UserId == user.Id && u.ItemId == itemID && u.Amount >= itemAmount).FirstOrDefault();

        if (item == null)
        {
          player.TriggerEvent("Error", "Transfer nicht möglich.");
          return;
        }
        /*
                if (!vehicleItems.ContainsKey(player))
                {
                  player.TriggerEvent("Error", "Transfer nicht möglich.");
                  return;
                }

                if (!vehicleItems.ContainsKey(player))
                  vehicleItems.Add(player, new List<InventoryItem>());
                Vehicle veh = null;
                if (vehicleItems[player].Count != 0)
                  veh = vehicleItems[player].FirstOrDefault().vehicle;
        */

        Vehicle veh = NAPI.Pools.GetAllVehicles().ToList().Where(v => v.Position.DistanceTo(player.Position) <= 2).FirstOrDefault();
        if (veh == null)
        {
          player.TriggerEvent("Error", "Transfer nicht möglich.");
          return;
        }

        RemoveUserItem(user, item, itemAmount);
        AddItemToVehicleInventory(veh, itemID, itemAmount);

        SetVehicleItems(player);
        VehicleItem vehicleItem = context.VehicleItems.Where(f => f.ItemId == itemID && f.Vehicle == VehicleManager.GetServerVehicleFromVehicle(veh, context)).FirstOrDefault();
        context.SaveChanges();
        IItem iItem = GetItemById(itemID);
        player.TriggerEvent("aproveTransfer", vehicleItem.Amount, iItem.Name, "Kofferraum");
      }
    }

    public static void AddItemToInventory(Player player, int itemId, int amount)
    {
      var user = player.GetUser();
      using (var context = new DatabaseContext())
      {
        int setAmount = 0;
        for (int i = 1; i <= amount; i++)
        {
          if (GetUserInventoryWeight(player) + (i * GetItemById(itemId).Gewicht) > 40000)
          {
            break;
          }
          else
          {
            setAmount = i;
          }
        }

        if (setAmount == 0)
        {
          player.TriggerEvent("Error", "Du kannst nicht so viel tragen.");
          return;
        }

        UserItem existingItem = context.UserItems.FirstOrDefault(i => i.ItemId == itemId && i.UserId == user.Id);
        if (existingItem != null)
        {
          existingItem.Amount += setAmount;
        }
        else
        {
          var newItem = new UserItem()
          {
            ItemId = itemId,
            Amount = setAmount,
            Slot = -1,
            UserId = user.Id,
          };
          context.UserItems.Add(newItem);
        }
        context.SaveChanges();
        IItem iItem = GetItemById(itemId);
        player.TriggerEvent("aproveTransfer", setAmount, iItem.Name, "Rucksack");
      }
    }

    public static void AddItemToVehicleInventory(Vehicle veh, int itemId, int amount)
    {
      using (var context = new DatabaseContext())
      {
        int setAmount = 0;
        for (int i = 1; i <= amount; i++)
        {
          if (GetVehicleInventoryWeight(veh) + (i * GetItemById(itemId).Gewicht) > 200000)
          {
            break;
          }
          else
          {
            setAmount = i;
          }
        }

        if (setAmount == 0)
        {
          return;
        }

        ServerVehicle sVeh = VehicleManager.GetServerVehicleFromVehicle(veh);
        VehicleItem existingItem = context.VehicleItems.FirstOrDefault(i => i.ItemId == itemId && i.VehicleId == sVeh.Id);
        if (existingItem != null)
        {
          existingItem.Amount += amount;
        }
        else
        {
          var newVehItem = new VehicleItem()
          {
            ItemId = itemId,
            Amount = amount,
            Slot = -1,
            VehicleId = sVeh.Id,
          };
          context.VehicleItems.Add(newVehItem);
        }
        context.SaveChanges();
      }
    }

    public static void RemoveAllItemsfromVehicleInventory(Vehicle veh)
    {
      using (var context = new DatabaseContext())
      {
        ServerVehicle sVeh = VehicleManager.GetServerVehicleFromVehicle(veh);
        var allItemsByVehicle = context.VehicleItems.Where(i => i.VehicleId == sVeh.Id);
        context.VehicleItems.RemoveRange(allItemsByVehicle);
        context.SaveChanges();
      }
    }

    [RemoteEvent("removeItemAsAdmin")]
    public void RemoveAsAdminInventory(Player player, string amount, string userItemId, string targetPlayerName)
    {
      using (var context = new DatabaseContext())
      {
        UserItem fItem = context.UserItems.FirstOrDefault(j => j.Id == int.Parse(userItemId));

        Player targetPlayer = PlayerService.GetPlayerByNameOrId(targetPlayerName);

        if (amount == "stack")
        {
          var itemSlot = fItem.Slot;
          targetPlayer.TriggerEvent("removeItem", userItemId, fItem.Amount);
          context.UserItems.Remove(fItem);
        }
        else if (amount == "one")
        {
          var itemSlot = fItem.Slot;
          targetPlayer.TriggerEvent("removeItem", userItemId, 1);
          fItem.Amount--;
        }
        context.SaveChanges();
      }
    }

    [RemoteEvent("itemInteract")]
    public void ItemInteract(Player player, string jsonType, string jsonAmount, string jsonItemId)
    {
      int amount = JsonConvert.DeserializeObject<int>(jsonAmount);
      int itemId = JsonConvert.DeserializeObject<int>(jsonItemId);
      string type = JsonConvert.DeserializeObject<string>(jsonType);

      User user = player.GetUser();

      using (var context = new DatabaseContext())
      {
        UserItem fItem = context.UserItems.Where(u => u.UserId == user.Id && u.ItemId == itemId).FirstOrDefault();
        IItem iItem = GetItemById(fItem.ItemId);

        switch (type)
        {
          case "use":
            if (iItem == null)
            {
              player.TriggerEvent("Error", "Dieses Essen existiert nicht.");
              return;
            }

            if (fItem == null)
            {
              player.TriggerEvent("Error", "Du hast dieses Item nicht");
              return;
            }

            if (iItem is IUsableItem usableItemObj)
            {
              usableItemObj.Use(fItem);
              List<InventoryItem> items = backpackItems[player];
              player.SetSharedData("backpackItems", JsonConvert.SerializeObject(items.ToArray()));
              player.TriggerEvent("aproveUse", 1, iItem.Name);
            }
            else player.TriggerEvent("Error", "Du kannst dieses Item nicht benutzen.");
            break;

          case "drop":

            if (iItem == null)
            {
              player.TriggerEvent("Error", "Dieses Item existiert nicht.");
              return;
            }

            if (fItem == null)
            {
              player.TriggerEvent("Error", "Du hast dieses Item nicht");
              return;
            }

            if (iItem is IDroppableItem usableItemObj2)
            {
              Vector3 dropPosition = PlayerExtension.GetPositionFromPlayer(player, 0.6f, 0);
              //new Vector3(player.Position.X, player.Position.Y, player.Position.Z - 0.8f);
              Random r = new Random();
              GTANetworkAPI.Object grndObject;
              Vector3 textPos = dropPosition;
              if (iItem is IWeaponDealItem obj)
              {
                dropPosition.Z -= 1.05f;
                grndObject = NAPI.Object.CreateObject(3666746839, dropPosition, new Vector3(0, 0, r.Next(0, 360)), 255, 0);
              }
              else
              {
                dropPosition.Z -= 0.8f;
                grndObject = NAPI.Object.CreateObject(3777723516, dropPosition, new Vector3(0, 0, r.Next(0, 360)), 255, 0);
              }
              GroundItem grndItem = new GroundItem { ItemId = iItem.Id, Amount = amount, Position = dropPosition };
              TextLabel grndTxtLbl = NAPI.TextLabel.CreateTextLabel(iItem.Name + " ~s~(~y~" + amount + "~s~)", textPos, 5, 0.5f, 4, new Color(255, 255, 255), false, 0);
              GroundItem.AddGroundItem(grndItem, grndObject, grndTxtLbl);
              if (fItem.Amount > amount)
              {
                fItem.Amount -= amount;
              }
              else if (fItem.Amount == amount)
              {
                context.UserItems.Remove(fItem);
              }
              context.SaveChanges();
              SetBackpackItems(player);
              player.TriggerEvent("aproveThrow", amount, iItem.Name);
            }
            break;
        }
        context.SaveChanges();
      }
    }

    [RemoteEvent("SERVER:BuyItems")]
    public void SrvEvent_BuyItem(Player client, string itemName)
    {
      IItem shopItem = GetItemByName(itemName);

      using (var dbContext = new DatabaseContext())
      {
        User user = client.GetUser(dbContext);

        if (user.Handmoney < shopItem.Price)
        {
          client.TriggerEvent("itemMenu:Error");
          return;
        }

        UserItem item = dbContext.UserItems.Where(i => i.ItemId == shopItem.Id && i.UserId == user.Id).FirstOrDefault();
        if (item == null)
        {
          var newItem = new UserItem
          {
            ItemId = shopItem.Id,
            UserId = user.Id,
            Amount = 1,
            Slot = -1
          };
          dbContext.UserItems.Add(newItem);
        }
        else
        {
          item.Amount += 1;
        }
        user.Handmoney -= shopItem.Price;
        dbContext.SaveChanges();
      //client.TriggerEvent("SERVER:SET_HANDMONEY", user.Handmoney);
      }
    }
  }
}
