/***
@overview Life of German - Economy
@author kookroach
@copyright (c) 2008 - 2019 Life of German
*/

using System;
using System.Collections.Generic;
using System.Linq;
using GTANetworkAPI;
using Microsoft.EntityFrameworkCore;
using ReallifeGamemode.Database.Entities;
using ReallifeGamemode.Database.Models;
using ReallifeGamemode.Server.Extensions;
using ReallifeGamemode.Server.Services;
using ReallifeGamemode.Server.Wanted;

namespace ReallifeGamemode.Server.Finance
{
  public class Economy
  {
    public static Dictionary<int, Paycheck> Paychecks { get; set; } = new Dictionary<int, Paycheck>();

    public static (int, float, float) GetEconomyClass(Player client, int wage)
    {
      int bankAccount = client.GetUser().BankAccount.Balance;
      float financialHelp = -(float)Math.Pow(1.0001, -(bankAccount + client.GetUser().Handmoney)) * -1000;
      float financialInterest = 1 - (float)Math.Pow(1.00006, -wage) * 1;
      if (financialInterest >= 0.7)
        financialInterest = 0.7f;

      if (financialHelp > 2500)
        financialHelp = 2500;

      return (bankAccount, financialHelp, financialInterest);
    }

    public static int GetVehicleTaxation(Player client)
    {
      int vehicleTaxation = 0;
      using (var dbContext = new DatabaseContext())
      {
        User user = client.GetUser(dbContext);
        foreach (UserVehicle uVeh in dbContext.UserVehicles.Where(u => u.UserId == user.Id))
        {
          if (uVeh.Price == null)
            vehicleTaxation += 400;
          else
            vehicleTaxation += (int)(uVeh.Price * 0.0015f);

          int taxation = uVeh.BusinessId switch
          {
            3 => 75,    ///<see cref="Business.VapidCarDealerBusiness"/>
            4 => 200,   ///<see cref="Business.PremiumDeluxeMotorsportCarDealerBusiness"/>
            5 => 150,   ///<see cref="Business.BoatDealerBusiness"/>
            6 => 150,   ///<see cref="Business.HelicopterDealerBusiness"/>
            7 => 150,   ///<see cref="Business.AirplaneDealerBusiness"/>
            8 => 50,    ///<see cref="Business.BikeDealerBusiness"/>
            9 => 20,    ///<see cref="Business.BeachDealerBusiness"/>
            10 => 50,   ///<see cref="Business.LastTrainDealerBusiness"/>
            11 => 100,  ///<see cref="Business.SUVDealerBusiness"/>
            12 => 50,   ///<see cref="Business.AdminDealerBusiness"/>
            13 => 100,  ///<see cref="Business.ClassicAndLuxuryCarshop"/>
            14 => 200,  ///<see cref="Business.OldschoolCarshop"/>
            _ => 0,
          };

          if (taxation is 0)
            Console.WriteLine($"[FINANCE] USER: {user.Name} ({user.Id}) has no BusinessId on UserVehicle ({uVeh.Id})");

          vehicleTaxation += taxation;
        }
      }
      return vehicleTaxation;
    }

    public static float GetPropertyTaxation(Player client)
    {
      float propertyTaxation = 0;
      User user = client.GetUser();
      if (user.HouseId != null)
      {
        propertyTaxation += user.House.Price * 0.005f;
      }

      return propertyTaxation;
    }

    public static void SetPaycheck(Player client, bool minusJail = true)
    {
      User user = client.GetUser();
      int wage = user.Wage;
      (int bankAccount, float financialHelp, float financialInterest) = GetEconomyClass(client, wage);
      float propertyTax = GetPropertyTaxation(client);
      int vehicleTaxation = GetVehicleTaxation(client);
      int rentalFees = GetRentalFees(client);

      int healthInsurance = (int)((bankAccount + user.Handmoney) * 0.001);
      if (healthInsurance < 0)
      {
        healthInsurance = 0;
      }

      int? factionMoney = null;

      if (user.Faction != null && user.FactionRank != null)
      {
        using var dbContext = new DatabaseContext();
        int factionRankCount = dbContext.FactionRanks.Where(r => r.FactionId == user.FactionId).Count();

        int factionWage = (3000 / factionRankCount) * user.FactionRank.Order;

        if (wage > 2500)
        {
          factionWage /= 2;
        }

        factionMoney = factionWage;
      }
      int otheramount = user.otheramount;
      int amount = wage - (int)(wage * financialInterest) - vehicleTaxation - (int)propertyTax + (int)financialHelp - rentalFees - healthInsurance + (factionMoney ?? 0) + otheramount;

      Paycheck paycheck = new Paycheck(financialHelp, financialInterest, vehicleTaxation, propertyTax, wage, amount, rentalFees, healthInsurance, factionMoney, otheramount);
      Paychecks[user.Id] = paycheck;
      ReleasePayDay(client, paycheck, minusJail);
    }

    private static int GetRentalFees(Player client)
    {
      using (var dbContext = new DatabaseContext())
      {
        User user = client.GetUser();
        return dbContext.HouseRentals.Where(h => h.UserId == user.Id).Include(h => h.House).Sum(h => h.House.RentalFee);
      }
    }

    public static void ReleasePayDay(Player client, Paycheck paycheck, bool minusJail)
    {
      NAPI.Util.ConsoleOutput("in release payday.");
      if (client == null || paycheck == null)
      {
        return;
      }

      int putInJail = 0;

      using var dbContext = new DatabaseContext();

      User u = client.GetUser(dbContext);

      UserBankAccount bankAccount = u.BankAccount;

      if (bankAccount.Balance < 0)
      {
        if (bankAccount.Balance >= -2500)
        {
          putInJail = 5;
        }
        else
        {
          putInJail = 10;
        }
      }

      u.BankAccount.Balance += paycheck.Amount;
      u.Wage = 0;
      u.otheramount = 0;
      u.PaydayTimer = 60;

      var rentals = dbContext.HouseRentals
        .Include(hR => hR.House)
          .ThenInclude(h => h.BankAccount)
        .Where(hR => hR.UserId == u.Id);

      dbContext.Factions.Include(f => f.BankAccount).Where(f => f.Id == 2).First().BankAccount.Balance += (int)(paycheck.HealthInsurance * 0.1);

      if (rentals.Any())
      {
        foreach (var rental in rentals)
        {
          if (rental?.House?.BankAccount != null)
          {
            rental.House.BankAccount.Balance += (int)(rental.House.RentalFee * 0.7);
          }
        }
      }
      if (paycheck.Amount > 0)
        ChatService.SendMessage(client, "~g~[PAYDAY]~s~ Dein Stundenumsatz beträgt + $~g~" + paycheck.Amount + "~s~.");
      else
        ChatService.SendMessage(client, "~g~[PAYDAY]~s~ Dein Stundenumsatz beträgt - $~r~" + paycheck.Amount + "~s~.");

      if (putInJail != 0 && minusJail)
      {
        u.JailTime = putInJail * 60;
        ChatService.SendMessage(client, "Du hattest beim PayDay einen negativen Betrag auf deinem Bank-Konto, dafür wirst du eingesperrt.");
        ChatService.SendMessage(client, "Achte darauf, keinen negativen Geldbetrag zu besitzen.");
      }

      dbContext.SaveChanges();

      if (putInJail != 0 && minusJail)
      {
        Jail.Check_PutBehindBars(u, JailInLocations.InCell);
      }
    }

    public static void Timer_Elapsed()
    {
      using var dbContext = new DatabaseContext();
      foreach (var player in NAPI.Pools.GetAllPlayers().Where(p => p.IsLoggedIn()))
      {
        if (player.IsAfk())
        {
          continue;
        }

        User user = player.GetUser(dbContext);
        if (user == null)
        {
          continue;
        }

        user.PlayedMinutes += 1;
        if (user.PaydayTimer <= 0)
        {
          Economy.SetPaycheck(player);
          user.PaydayTimer = 60;
        }
        else if (user.PaydayTimer > 0)
        {
          user.PaydayTimer -= 1;
        }
      }
      dbContext.SaveChanges();
    }
  }
}
