using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Text;
using Disco.Client.Extensions;
using Newtonsoft.Json;

namespace Disco.Client
{
    public static class ErrorReporting
    {
        private const string ServicePathTemplate = "http://DISCO:9292/Services/Client/ClientError";
        public static string DeviceIdentifier { get; set; }
        public static string EnrolmentSessionId { get; set; }

        public static void ReportError(Exception Ex, bool ReportToServer)
        {
            bool isClientServiceException = Ex is ClientServiceException;

            ErrorReport report = new ErrorReport()
            {
                DeviceIdentifier = DeviceIdentifier,
                SessionId = EnrolmentSessionId,
                JsonException = Ex.IntenseExceptionSerialization()
            };

            try
            {
                LogToFile(report);
            }
            catch (Exception) { }

            try
            {
                LogToEventLog(report);
            }
            catch (Exception) { }

            // Don't log server errors back to the server
            if (!isClientServiceException && ReportToServer)
            {
                try
                {
                    LogToServer(report);
                }
                catch (Exception) { }
            }

            try
            {
                Presentation.WriteFatalError(Ex);
            }
            catch (Exception) { }
        }

        #region Log Actions

        private static void LogToFile(ErrorReport report)
        {
            var logPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "ErrorLog.csv");

            using (var streamWriter = File.AppendText(logPath))
            {
                streamWriter.Write(DateTime.Now.ToString("s"));
                streamWriter.Write(",");
                streamWriter.Write(report.DeviceIdentifier);
                streamWriter.Write(",\"");
                streamWriter.Write(report.JsonException);
                streamWriter.Write("\"");
                streamWriter.Flush();
            }
        }
        private static void LogToEventLog(ErrorReport report)
        {
            string eventSource = "Disco Client";

            if (!EventLog.SourceExists(eventSource))
                EventLog.CreateEventSource(eventSource, "Application");

            EventLog.WriteEntry(eventSource, report.JsonException, EventLogEntryType.Error, 400);
        }
        private static void LogToServer(ErrorReport report)
        {
            string reportJson = JsonConvert.SerializeObject(report);
            string reportResponse;

            HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(ServicePathTemplate);
            request.UserAgent = string.Format("Disco-Client/{0}", Assembly.GetExecutingAssembly().GetName().Version.ToString(3));
            request.ContentType = "application/json";
            request.Method = WebRequestMethods.Http.Post;
            request.UseDefaultCredentials = true;
            request.Timeout = 300000; // 5 Minutes

            using (StreamWriter requestWriter = new StreamWriter(request.GetRequestStream()))
            {
                requestWriter.Write(reportJson);
            }

            using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
            {
                using (StreamReader responseReader = new StreamReader(response.GetResponseStream()))
                {
                    reportResponse = responseReader.ReadToEnd();
                }
            }

            System.Diagnostics.Debug.WriteLine("Error Report Logged to Server; Response: {0}", reportResponse);
        }

        #endregion

        public class ErrorReport
        {
            public string SessionId { get; set; }
            public string DeviceIdentifier { get; set; }
            public string JsonException { get; set; }
        }

        public static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            try
            {
                var ex = e.ExceptionObject as Exception;
                if (ex != null)
                {
                    ReportError(ex, true);
                }
            }
            catch (Exception)
            {
                // Igore failure to Log Errors
            }
        }

        #region Exception Serialization
        private static string IntenseExceptionSerialization(this Exception Ex)
        {
            try
            {
                return JsonConvert.SerializeObject(Ex);
            }
            catch (Exception)
            {
                try
                {
                    var encapsulatedEx = Ex.ToEncapsulatedException();
                    return JsonConvert.SerializeObject(encapsulatedEx);
                }
                catch (Exception)
                {
                    try
                    {
                        var encapsulatedEx = Ex.ToEncapsulatedException(0);
                        return JsonConvert.SerializeObject(encapsulatedEx);
                    }
                    catch (Exception)
                    {
                        return JsonConvert.SerializeObject(Ex.Message);
                    }
                }
            }
        }

        private static EncapsulatedException ToEncapsulatedException(this Exception ex, int InnerRecursionDepth = 4)
        {
            EncapsulatedException inner = null;
            if (InnerRecursionDepth > 0 && ex.InnerException != null)
                inner = ex.InnerException.ToEncapsulatedException(--InnerRecursionDepth);

            return new EncapsulatedException()
            {
                EncapsulatedType = ex.GetType().Name,
                Message = ex.Message,
                StackTrace = ex.StackTrace,
                InnerException = inner
            };
        }
        public class EncapsulatedException
        {
            public string Message { get; set; }
            public string EncapsulatedType { get; set; }
            public string StackTrace { get; set; }
            public EncapsulatedException InnerException { get; set; }
        }
        #endregion

    }
}
