using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Security.Policy;
using System.Text;
using System.Xml;
using System.Xml.Serialization;

namespace FogbugzWrapper
{
    public class FogbugzClient
    {
        public string Token { get; } = "1u4thrqm93uqpfr8ptbj2bc112gseo";
        public static string FogbugzURL { get; set; }
        private bool _loggedOn = false;

        public string FogbugzLogDir { get; set; } = "FogbugzLog\\";

        public bool IsLoggedIn()
        {
            return _loggedOn;
        }

        public FogbugzClient()
        {
        }
        public FogbugzClient(string uRL, string token)
        {
            Init(uRL);
            Token = token;
            _loggedOn = LogOn();
        }
        public FogbugzClient(string uRL, string email, string password)
        {
            Init(uRL);
            var doc = new XmlDocument();
            var resp = Command($"logon&email={System.Net.WebUtility.UrlEncode(email)}&password={password}");
            doc.LoadXml(resp);

            Token = doc.SelectSingleNode("response").FirstChild.InnerText;
            _loggedOn = LogOn();
        }
        private void Init(string uRL)
        {
            if (!Directory.Exists($"{FogbugzLogDir}"))
            {
                Directory.CreateDirectory($"{FogbugzLogDir}");
            }
            FogbugzURL = uRL;
        }

        private string TokenedCommand(string command)
        {
            return Command($"{command}&token={Token}");
        }

        private string Command(string command)
        {
            // Create a request using a URL that can receive a post.
            WebRequest request = WebRequest.Create(FogbugzURL);
            request.Proxy = null;
            // Set the Method property of the request to POST.
            request.Method = "POST";

            // Create POST data and convert it to a byte array.
            string postData = $"cmd={command}";
            byte[] byteArray = Encoding.UTF8.GetBytes(postData);

            // Set the ContentType property of the WebRequest.
            request.ContentType = "application/x-www-form-urlencoded";
            // Set the ContentLength property of the WebRequest.
            request.ContentLength = byteArray.Length;

            // Get the request stream.
            Stream dataStream = request.GetRequestStream();
            // Write the data to the request stream.
            dataStream.Write(byteArray, 0, byteArray.Length);
            // Close the Stream object.
            dataStream.Close();

            // Get the response.
            var response = request.GetResponse();
            // Display the status.
            //Console.WriteLine(((HttpWebResponse)response).StatusDescription);

            var responseFromServer = string.Empty;
            // Get the stream containing content returned by the server.
            // The using block ensures the stream is automatically closed.
            using (dataStream = response.GetResponseStream())
            {
                // Open the stream using a StreamReader for easy access.
                StreamReader reader = new StreamReader(dataStream);
                // Read the content.
                responseFromServer = reader.ReadToEnd();
                // Display the content.
                //Console.WriteLine(responseFromServer);
            }
            // Close the response.
            response.Close();
            return responseFromServer;
        }

        public string GetArea(int bugNumber)
        {
            var doc = new XmlDocument();
            var resp = TokenedCommand($"search&q={bugNumber}&cols=sArea");
            doc.LoadXml(resp);

            return doc.SelectSingleNode("response").FirstChild.FirstChild.FirstChild.InnerText;
        }

        public void CreateNewTicket(string projectName, string projArea, string title, string description, string assignedTo)
        {
            var doc = new XmlDocument();
            var resp = TokenedCommand($"new&sProject={projectName}&sArea={projArea}&sTitle={title}&sEvent={description}&sPersonAssignedTo={assignedTo}&fRichText=1");
            doc.LoadXml(resp);

            //return doc.SelectSingleNode("response").FirstChild.FirstChild.FirstChild.InnerText;
        }

        public string GetTitle(int bugNumber)
        {
            var doc = new XmlDocument();
            var resp = TokenedCommand($"search&q={bugNumber}&cols=sTitle");
            doc.LoadXml(resp);

            return doc.SelectSingleNode("response").FirstChild.FirstChild.FirstChild.InnerText;
        }

        public string GetProject(int bugNumber)
        {
            var doc = new XmlDocument();
            var resp = TokenedCommand($"search&q={bugNumber}&cols=sProject");
            doc.LoadXml(resp);

            return doc.SelectSingleNode("response").FirstChild.FirstChild.FirstChild.InnerText;
        }
        public void TouchCase(int caseIdx)
        {
            TokenedCommand($"edit&ixBug={caseIdx}&sEvent=Touched");
        }
        public void ResolveAndClose(int caseIdx)
        {
            TokenedCommand($"resolve&ixBug={caseIdx}&sEvent=ResolvedByManuscriptEdit");
            TokenedCommand($"close&ixBug={caseIdx}&sEvent=ClosedByManuscriptEdit");
        }
        public ProjectResponse GetProjects()
        {
            var resp = TokenedCommand($"listProjects");
            var serializer = new XmlSerializer(typeof(ProjectResponse));

            using (var reader = new StringReader(resp))
            {
                return (ProjectResponse)serializer.Deserialize(reader);
            }
        }

        public CasesResponse GetCases(int filter)
        {
            var resp = TokenedCommand($"listCases&sFilter={filter}&cols=ixBug,ixProject,fOpen,sTitle,sProject,sArea,sStatus,dtLastUpdated,events");
            var serializer = new XmlSerializer(typeof(CasesResponse));
            using (var reader = new StringReader(resp))
            {
                return (CasesResponse)serializer.Deserialize(reader);
            }
        }
        public List<FBEvent> GetEvents(int bugNumber)
        {
            var doc = new XmlDocument();
            var resp = TokenedCommand($"search&q={bugNumber}&cols=events,plugin_customfield");
            doc.LoadXml(resp);
            var timeStamps = doc.GetElementsByTagName("dt");
            if (timeStamps.Count == 0) return new List<FBEvent>();
            var lastUpdate = DateTime.Parse(timeStamps.Item(timeStamps.Count - 1).InnerText);

            if (File.Exists($"{FogbugzLogDir}{bugNumber}.{lastUpdate.Ticks}.xml"))
            {
                return DeSerializeObject<List<FBEvent>>($"{FogbugzLogDir}{bugNumber}.{lastUpdate.Ticks}.xml");
            }

            var fbEventList = new List<FBEvent>();
            var events = doc.SelectSingleNode("response").FirstChild.FirstChild.FirstChild;

            foreach (XmlNode eventNode in doc.SelectSingleNode("response").FirstChild.FirstChild.FirstChild)
            {
                fbEventList.Add(new FBEvent(eventNode));
            }
            fbEventList.Reverse();
            SerializeObject(fbEventList, $"{FogbugzLogDir}{bugNumber}.{lastUpdate.Ticks}.xml");

            return fbEventList;
        }
        public List<ZendeskCase> GetZendeskCases(int updatedMinutes)
        {
            Console.WriteLine($"GetZendeskCases - updatedMinutes:{updatedMinutes}");
            var rt = new List<ZendeskCase>();
            var doc = new XmlDocument();
            if (!IsLoggedIn())
            {
                Console.WriteLine("GetZendeskCases - Not Logged In.");
                return null;
            }
            var resp = TokenedCommand($"search&q=ZendeskTicketNumber:* lastupdated:\"-{updatedMinutes}m..now\"&cols=plugin_customfields_at_fogcreek_com_zendeskxissuea73,events,sStatus");
            doc.LoadXml(resp);
            //Console.WriteLine($"Full Response: {doc.SelectSingleNode("response").InnerText}");
            var zdCases = doc.SelectSingleNode("response").ChildNodes[0];
            if (zdCases == null)
            {
                Console.WriteLine($"GetZendeskCases - zdCases is null");                
                return null;
            }
            Console.WriteLine($"GetZendeskCases - zdCases.ChildNodes.Count:{zdCases.ChildNodes.Count}");
            foreach (XmlNode zdNode in zdCases)
            {
                if (0 == zdNode.SelectNodes("events").Count
                    || string.IsNullOrEmpty(zdNode.SelectSingleNode("plugin_customfields_at_fogcreek_com_zendeskxissuea73").InnerText))
                { continue; }
                var zdNumber = int.Parse(zdNode.SelectSingleNode("plugin_customfields_at_fogcreek_com_zendeskxissuea73").InnerText);
                var fbNumber = int.Parse(zdNode.Attributes["ixBug"].Value);
                var oldFbStatus = FBEvent.FBStatuses.UNKNOWN;
                foreach (XmlNode fbEvent in zdNode.SelectSingleNode("events"))
                {
                    var fbLatestEvent = new FBEvent(fbEvent);
                    if (DateTime.Parse(fbLatestEvent.TimeStamp) > DateTime.Now.AddMinutes(-updatedMinutes) &&
                        fbLatestEvent.Status != oldFbStatus &&
                        fbLatestEvent.StatusString != "Edited" &&
                        fbLatestEvent.StatusString != "Assigned")
                    {
                        rt.Add(new ZendeskCase(zdNumber, fbNumber, fbLatestEvent));
                    }
                    else
                    {
                        //
                    }
                    oldFbStatus = fbLatestEvent.Status;
                }
                
            }
            return rt;
        }

        /// <summary>
        /// Serializes an object.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="serializableObject"></param>
        /// <param name="fileName"></param>
        public void SerializeObject<T>(T serializableObject, string fileName)
        {
            if (serializableObject == null) { return; }

            try
            {
                XmlDocument xmlDocument = new XmlDocument();
                XmlSerializer serializer = new XmlSerializer(serializableObject.GetType());
                using (MemoryStream stream = new MemoryStream())
                {
                    serializer.Serialize(stream, serializableObject);
                    stream.Position = 0;
                    xmlDocument.Load(stream);
                    xmlDocument.Save(fileName);
                }
            }
            catch (Exception ex)
            {
                //Log exception here
            }
        }

        /// <summary>
        /// Deserializes an xml file into an object list
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public T DeSerializeObject<T>(string fileName)
        {
            if (string.IsNullOrEmpty(fileName)) { return default(T); }

            T objectOut = default(T);

            try
            {
                XmlDocument xmlDocument = new XmlDocument();
                xmlDocument.Load(fileName);
                string xmlString = xmlDocument.OuterXml;

                using (StringReader read = new StringReader(xmlString))
                {
                    Type outType = typeof(T);

                    XmlSerializer serializer = new XmlSerializer(outType);
                    using (XmlReader reader = new XmlTextReader(read))
                    {
                        objectOut = (T)serializer.Deserialize(reader);
                    }
                }
            }
            catch (Exception)
            {
                //Log exception here
            }

            return objectOut;
        }

        public FBEvent.FBStatuses GetStatus(int bugNumber)
        {
            var fbevents = GetEvents(bugNumber);

            foreach (var e in fbevents)
            {
                if (e.Status != FBEvent.FBStatuses.UNKNOWN)
                {
                    return e.Status;
                }
            }
            return FBEvent.FBStatuses.UNKNOWN;
        }

        public string GetReleaseNote(int bugNumber)
        {
            var fbevents = GetEvents(bugNumber);

            foreach (var e in fbevents)
            {
                if (!string.IsNullOrEmpty(e.ReleaseNote))
                {
                    return e.ReleaseNote;
                }
            }
            return string.Empty;
        }

        public int GetZendeskNumber(int bugNumber)
        {
            var fbevents = GetEvents(bugNumber);

            foreach (var e in fbevents)
            {
                if (0 != e.ZendeskNumber)
                {
                    return e.ZendeskNumber;
                }
            }
            return 0;
        }



        public bool LogOn()
        {
            _loggedOn = true;
            return _loggedOn;
        }

        public void LogOff()
        {
            var resp = TokenedCommand("logoff");
            _loggedOn = false;
        }
    }
}