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

namespace ReallifeGamemode.Server.Managers
{
  public class JobManager : Script
  {
    private static List<JobBase> _jobs = new List<JobBase>();

    public static Dictionary<Player, Vector3> playerJobStartPosition = new Dictionary<Player, Vector3>();

    public static void LoadJobs()
    {
      IEnumerable<Type> jobTypes = Assembly.GetExecutingAssembly().GetTypes().Where(t => t.IsSubclassOf(typeof(JobBase)) && !t.IsAbstract);

      foreach (var type in jobTypes)
      {
        var instance = Activator.CreateInstance(type) as JobBase;
        if (GetJob(instance.Id) != null)
        {
          throw new InvalidOperationException($"Double Job ID found: {instance.Id} | {instance.Name}");
        }

        if (instance.Deactivated)
        {
          NAPI.Util.ConsoleOutput("Job {0} is deactivated", instance.Name);
          continue;
        }
        _jobs.Add(instance);
        NAPI.Util.ConsoleOutput($"Loading job {instance.Name}");
      }
      NAPI.Util.ConsoleOutput($"Loaded {_jobs.Count} jobs");
    }

    public static JobBase GetJob(int id) => _jobs.Where(j => j.Id == id).FirstOrDefault();

    public static T GetJob<T>() where T : JobBase
    {
      return _jobs.Where(j => j.GetType() == typeof(T)).FirstOrDefault() as T;
    }

    public static List<JobBase> GetJobs() => _jobs.OrderBy(j => j.Id).ToList();

    public static Dictionary<Player, PlayerTimer> playerTimersJobVehicleRespawn = new Dictionary<Player, PlayerTimer>();

    [RemoteEvent("CLIENT:JobCenter_CancelJob")]
    public void CancelJobEvent(Player player)
    {
      using (var dbContext = new DatabaseContext())
      {
        User u = player.GetUser(dbContext);

        if (u == null) return;

        if (u.JobId == null)
        {
          ChatService.ErrorMessage(player, "Du bist bereits arbeitslos");
          return;
        }

        u.JobId = null;

        ChatService.SendMessage(player, "~y~[JOBCENTER]~s~ Du hast deinen Job erfolgreich gekündigt.");

        dbContext.SaveChanges();
      }
    }

    [RemoteEvent("CLIENT:JobCenter_AcceptJob")]
    public void AcceptJobEvent(Player player, int jobId)
    {
      using (var dbContext = new DatabaseContext())
      {
        User u = player.GetUser(dbContext);

        if (u == null) return;

        if (u.JobId != null)
        {
          ChatService.ErrorMessage(player, "Du musst deinen alten Job kündigen, bevor du einen Neuen ausüben kannst");
          return;
        }

        //Pilotjob
        if (jobId == 3 && u.FlyingLicensePlane == false)
        {
          ChatService.ErrorMessage(player, "Um diesen Job annehmen zu können, musst du einen Flugschein besitzen");
          return;
        }

        if (u.DriverLicenseVehicle == false)
        {
          ChatService.ErrorMessage(player, "Um diesen Job annehmen zu können, musst du einen Führerschein besitzen");
          return;
        }

        JobBase job = JobManager.GetJob(jobId);

        if (job == null)
        {
          ChatService.ErrorMessage(player, "Bei der Job-Annahme ist ein Fehler aufgetretet: Dieser Job wurde nicht gefunden");
          return;
        }

        ChatService.SendMessage(player, $"~y~[JOBCENTER]~s~ Du hast erfolgreich deinen neuen Job: ~o~{job.Name}~s~ angenommen.");

        u.JobId = jobId;

        dbContext.SaveChanges();
      }
    }

    [RemoteEvent("CLIENT:Job_StartJob")]
    public void StartJobEvent(Player player)
    {
      User u = player.GetUser();

      if (u.JobId == null) return;

      JobBase job = GetJob(u.JobId.Value);

      if (job.GetUsersInJob().Contains(player))
      {
        ChatService.ErrorMessage(player, "Du bist schon in deinem Job aktiv");
        return;
      }

      if (job.NeedVehicleToStart && !job.GetJobVehicles().Any(v => v.GetVehicle().Handle.Value == player.Vehicle?.Handle.Value))
      {
        ChatService.ErrorMessage(player, "Zum Start dieses Jobs musst du in einem Jobfahrzeug sein");
        return;
      }

      if (player.Vehicle != null)
      {
        player.SetData<Vehicle>("LastVehicle", player.Vehicle);
      }

      playerJobStartPosition[player] = player.Position;
      job.StartJob(player);
    }

    [ServerEvent(Event.PlayerExitVehicle)]
    public void JobManagerPlayerExitVehicle(Player player, Vehicle veh)
    {
      User u = player.GetUser();

      if (u.JobId == null) return;

      JobBase job = GetJob(u.JobId ?? -1);

      if (job == null || !job.GetUsersInJob().Contains(player))
        return;

      job.StartJobEndTimer(player);
    }

    [RemoteEvent("CLIENT:Job_StopJob")]
    public void StopJob(Player player)
    {
      User u = player.GetUser();

      if (u.JobId == null) return;

      JobBase job = GetJob(u.JobId.Value);

      if (!job.GetUsersInJob().Contains(player))
      {
        ChatService.ErrorMessage(player, "Du führst deinen Job momentan nicht aus");
        return;
      }

      if (job.NeedVehicleToStart && player.Vehicle != null)
      {
        VehicleStreaming.SetEngineState(player.Vehicle, false);
      }
      player.Position = playerJobStartPosition[player];
      job.StopJob(player);

      if (playerTimersJobVehicleRespawn.ContainsKey(player))
      {
        playerTimersJobVehicleRespawn[player].Stop();
      }
      CheckPointHandle.DeleteCheckpoints(player);
      ChatService.SendMessage(player, $"~y~[JOB]~s~ Du hast deinen Job ~o~{job.Name}~s~ beendet.");
    }

    [ServerEvent(Event.PlayerEnterVehicle)]
    public void JobManagerPlayerEnterVehicle(Player player, Vehicle veh, sbyte seat)
    {
      JobBase job = GetJob(player.GetUser().JobId ?? -1);

      if (job != null && job.GetUsersInJob().Contains(player))
      {
        if (job.CheckVehicle(player, veh) && seat == 0)
        {
          if (!playerTimersJobVehicleRespawn.ContainsKey(player))
            return;

          playerTimersJobVehicleRespawn[player].Stop();
          playerTimersJobVehicleRespawn.Remove(player);
        }
        else if (job != GetJob<RefuseCollectorJob>())
        {
          player.StopAnimation();
          player.WarpOutOfVehicle();
          player.Position = player.Position.Add(new Vector3(0, 0, 2.0f));
        }
      }
    }

    [ServerEvent(Event.PlayerEnterVehicleAttempt)]
    private void JobManagerEnterVehicleAttempt(Player player, Vehicle vehicle, sbyte seat)
    {
      if (JobManager.GetJob<RefuseCollectorJob>().GetUsersInJob().Contains(player)) { JobManager.GetJob<RefuseCollectorJob>().MuellmannOnPlayerEnterVehicle(player, vehicle, seat); }
    }

    [ServerEvent(Event.PlayerDeath)]
    private void JobManagerOnPlayerDeath(Player player, Player killer, uint reason)
    {
      if (JobManager.GetJob<RefuseCollectorJob>().GetUsersInJob().Contains(player)) { JobManager.GetJob<RefuseCollectorJob>().MuellmannOnPlayerDeath(player); }
    }

    [ServerEvent(Event.PlayerDisconnected)]
    private void JobManagerOnPlayerDisconnect(Player player, DisconnectionType type, string reason)
    {
      if (JobManager.GetJob<RefuseCollectorJob>().GetUsersInJob().Contains(player)) { JobManager.GetJob<RefuseCollectorJob>().MuellmannOnPlayerDc(player); }
    }

    [ServerEvent(Event.PlayerExitVehicle)]
    public void JobManagerPlayerExitVehicle2(Player player, Vehicle veh)
    {
    }

    [RemoteEvent("CLIENT:JobManager_TaxiJob_AcceptCall")]
    public void TaxiJobAcceptCall(Player player, string name)
    {
      var taxiJob = JobManager.GetJob<TaxiDriverJob>();

      if (!taxiJob.TaxiContracts.Any(t => t.Name == name))
      {
        ChatService.ErrorMessage(player, "Dieser Spieler hat kein Taxi gerufen oder der Auftrag ist nicht mehr aktuell");
        return;
      }

      var job = taxiJob.TaxiContracts.Where(t => t.Name == name).First();

      var target = PlayerService.GetPlayerByNameOrId(name);
      if (target == null)
      {
        taxiJob.TaxiContracts.Remove(job);
        ChatService.ErrorMessage(player, "Dieser Spieler ist nicht mehr online");
        return;
      }
      if (target == player)
      {
        ChatService.ErrorMessage(player, "Du kannst deinen eigenen Auftrag nicht annehmen");
      }

      job.Driver = player;

      player.TriggerEvent("SERVER:Util_setWaypoint", target.Position.X, target.Position.Y);
      player.SetData<bool>("hasPassager", true);

      ChatService.SendMessage(target, $"!{{02FCFF}}Dein Auftrag wurde von {player.Name} angenommen. Warte an deiner aktuellen Position.");
      ChatService.SendMessage(target, $"!{{02FCFF}}Für die Anfahrt fallen zusätzlich Kosten auf.");
      ChatService.SendMessage(player, $"!{{02FCFF}}Du hast den Auftrag von {name} angenommen. Hole ihn nun ab.");
    }
  }
}
