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

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

        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}");
                }
                _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();

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

                if (u == null) return;

                if (u.JobId == null)
                {
                    ChatService.ErrorMessage(player, "Du hast momentan keinen Job, den du kündigen könntest.");
                    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(Client 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;
                }

                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:JobManager_ShowJobMenu")]
        public void ShowJobMenuEvent(Client player)
        {
            User u = player.GetUser();

            if (u.JobId == null) return;

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

            dynamic data = null;

            if (job.Id == 1 && job.GetUsersInJob().Contains(player))
            {
                var taxiCalls = JobManager.GetJob<TaxiDriverJob>().TaxiContracts;
                if (!taxiCalls.Any(t => t.Driver?.Handle == player.Handle)) // Spieler in keiner aktiven Fahrt
                { 
                    data = new
                    {
                        job.Id,
                        Status = 0,
                        JobData = new
                        {
                            TaxiCalls = taxiCalls
                            .Where(t => t.Driver == null)
                            .Select(t => new
                            {
                                t.Name,
                                Distance = Math.Round(t.Position.DistanceTo(player.Position), 0)
                            })
                            .OrderBy(t => t.Distance)
                        }
                    };
                }
                else // Spieler in aktiver Fahrt
                {
                    data = new
                    {
                        job.Id,
                        Status = 1,
                        JobData = new
                        {
                            taxiCalls.Where(t => t.Driver.Handle == player.Handle).First().Name
                        }
                    };
                }

            }

            var json = JsonConvert.SerializeObject(data);

            player.TriggerEvent("SERVER:Job_ShowJobMenu", job.Name, json);
        }

        [RemoteEvent("CLIENT:Job_StartJob")]
        public void StartJobEvent(Client 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.Vehicle.Handle.Value == player.Vehicle?.Handle.Value))
            {
                ChatService.ErrorMessage(player, "Zum Start dieses Jobs musst du in einem Jobfahrzeug sein");
                return;
            }

            job.StartJob(player);
        }

        [RemoteEvent("CLIENT:Job_StopJob")]
        public void StopJob(Client 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);
            }

            job.StopJob(player);
        }

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

            if (u.JobId == null) return;

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

            if (job.GetUsersInJob().Contains(player) && job.NeedVehicleToStart)
            {
                job.StopJob(player);
                return;
            }
        }

        [ServerEvent(Event.PlayerEnterVehicle)]
        public void JobManagerPlayerEnterVehicle(Client player, Vehicle veh, sbyte seat)
        {
            ServerVehicle sVeh = veh.GetServerVehicle();
            if (sVeh == null) return;
            if (sVeh is JobVehicle jV && jV.JobId == 1) // Spieler steigt in Taxi ein
            {
                var taxiJob = JobManager.GetJob<TaxiDriverJob>();

                var taxiContracts = taxiJob.TaxiContracts.Where(t => t.Name == player.Name);
                if (taxiContracts.Count() == 0) return;

                var contract = taxiContracts.First();
                taxiJob.TaxiContracts.Remove(contract);

                Client driver = veh.GetDriver();

                if(driver != null && contract.Driver.Handle != veh.GetDriver().Handle)
                {
                    ChatService.SendMessage(contract.Driver, $"!{{02FCFF}}{player.Name} ist in ein Taxi eingestiegen, der Auftrag wurde beendet.");
                    ChatService.SendMessage(player, $"!{{02FCFF}}Du bist in ein Taxi eingestiegen, der Auftrag wurde beendet.");
                }
            }
        }

        [RemoteEvent("CLIENT:JobManager_TaxiJob_AcceptCall")]
        public void TaxiJobAcceptCall(Client 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 = ClientService.GetClientByNameOrId(name);
            if (target == null)
            {
                taxiJob.TaxiContracts.Remove(job);
                ChatService.ErrorMessage(player, "Dieser Spieler ist nicht mehr online");
                return;
            }

            job.Driver = player;

            player.TriggerEvent("SERVER:Util_setWaypoint", target.Position.X, target.Position.Y);

            ChatService.SendMessage(target, $"!{{02FCFF}}Dein Auftrag wurde von {player.Name} angenommen. Warte an deiner aktuellen Position.");
            ChatService.SendMessage(player, $"!{{02FCFF}}Du hast den Auftrag von {name} angenommen. Hole ihn nun ab.");
        }
    }
}
