using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Threading.Tasks;
using Sockeye.Biz;
using Microsoft.Extensions.Logging;

namespace Sockeye.Util
{

    //Track processes and kill any that go past their expiry date
    internal static class ReportRenderManager
    {

        internal static ConcurrentBag<ReportRenderInstanceInfo> _baginstances;

        static ReportRenderManager()
        {
            _baginstances = new ConcurrentBag<ReportRenderInstanceInfo>();
        }

        internal class ReportRenderInstanceInfo
        {
            internal int ReporterProcessId { get; set; }
            internal DateTime Expires { get; set; }
            internal Guid JobId { get; set; }

            internal ReportRenderInstanceInfo(Guid jobId, DateTime expires)
            {
                JobId = jobId;
                Expires = expires;
                ReporterProcessId = -1;
            }
        }


        internal static async Task KillExpiredRenders(ILogger log)
        {
            log.LogDebug("Clear potential expired render jobs check");
            //check for expired and remove
            var Instances = _baginstances.ToArray();
            var dtNow = DateTime.UtcNow;
            foreach (ReportRenderInstanceInfo i in Instances)
            {
                if (i.Expires < dtNow)
                {
                    log.LogDebug($"attempting close of expired process {i.ReporterProcessId} for job {i.JobId}");
                    await CloseRenderProcess(i, log, true);
                }
            }
        }

        internal static async Task<bool> CloseRenderProcess(ReportRenderInstanceInfo instance, ILogger log, bool force)
        {
            if (force)
                log.LogDebug($"Force CloseRenderProcess on report render instance id {instance.ReporterProcessId} expiry {instance.Expires.ToString()} utc");
            else
                log.LogDebug($"Normal CloseRenderProcess on report render instance id {instance.ReporterProcessId}");

            try
            {
                //either way, clear the job so the client gets informed
                if (force)
                {
                    if (instance.Expires < DateTime.UtcNow)
                    {
                        var json = Newtonsoft.Json.JsonConvert.SerializeObject(new { rendererror = new { timeout = true, timeoutsetting = ServerBootConfig.SOCKEYE_REPORT_RENDERING_TIMEOUT } }, Newtonsoft.Json.Formatting.None);
                        await JobsBiz.LogJobAsync(instance.JobId, json);
                        await JobsBiz.UpdateJobStatusAsync(instance.JobId, JobStatus.Failed);
                    }
                    else
                    {
                       var json = Newtonsoft.Json.JsonConvert.SerializeObject(new { rendererror = new { cancelled = true} }, Newtonsoft.Json.Formatting.None);
                        await JobsBiz.LogJobAsync(instance.JobId, json);
                        await JobsBiz.UpdateJobStatusAsync(instance.JobId, JobStatus.Completed);
                    }
                }

                if (instance.ReporterProcessId != -1)//if a job doesn't have a process id yet it will be -1
                {
                    var p = Process.GetProcessById(instance.ReporterProcessId);
                    if (p != null)
                    {
                        //we have an existing process                   
                        //try to kill it                   
                        p.Kill(true);
                        if (p.HasExited == false)
                        {
                            log.LogWarning($"Expired report render instance id {instance.ReporterProcessId} could not be force closed");
                            return false;//can't kill it so can't free up a slot
                        }
                    }
                }
                //remove it from the list, it's either gone or killed at this point
                //this would not be unexpected since it will normally just close on it's own
                //at the finally block in render report
                _baginstances.TryTake(out instance);
                return true;//process that was there is now not there so while not perfect system we will consider it free

            }
            catch (ArgumentException)
            {
                //do nothing, this is normal, the process could not be found and this means it's already been removed:
                //ArgumentException
                //The process specified by the processId parameter is not running. The identifier might be expired.
                _baginstances.TryTake(out instance);
                return true;
            }
        }


        internal static void AddJob(Guid jobId, ILogger log)
        {
            log.LogDebug($"AddJob - {jobId} to the collection");
            _baginstances.Add(new ReportRenderInstanceInfo(jobId, DateTime.UtcNow.AddMinutes(ServerBootConfig.SOCKEYE_REPORT_RENDERING_TIMEOUT)));
            log.LogDebug($"AddJob - there are currently {_baginstances.Count} instances in the collection");
        }


        internal static void SetProcess(Guid jobId, int processId, ILogger log)
        {
            log.LogDebug($"SetProcess - setting {jobId} to render process id {processId}");
            foreach (var i in _baginstances)
            {
                if (i.JobId == jobId)
                {
                    i.ReporterProcessId = processId;
                    break;
                }
            }
        }

        internal static async Task RemoveJob(Guid jobId, ILogger log, bool force)
        {
            foreach (var i in _baginstances)
            {
                if (i.JobId == jobId)
                {
                    await CloseRenderProcess(i, log, force);
                    break;
                }
            }
        }


        internal static bool KeepGoing(Guid jobId)
        {
           
            //if job id is empty it means it was called from outside of a job (report designer get data for example)
            if(jobId==Guid.Empty) return true;
            foreach (var i in _baginstances)
            {
                if (i.JobId == jobId)
                {
                    return true;
                }
            }
            return false;
        }



    }//eoc
}//eons