using AwInitilizer.Assist;
using AwInitilizer.Model;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using System.Windows.Ink;

namespace AwInitilizer.Procedure
{
    public class FirmwareUpdateProcedure : ProcedureBase
    {
        internal string Version;
        internal string fileName;
        internal string module;
        //internal int SleepMinuts = 6;

        private FirmwareUpdateModel _model;

        public FirmwareUpdateProcedure(FirmwareUpdateModel model) :base()
        {
            _model = model;

            Name = string.Format("Firmware {0}", model.Module);
            Content = string.Format("Update {0} Firemware and check version matched", model.Module);

            Version = model.Version;
            fileName = model.FirmwareFileName;
            module = model.Module;
        }

        internal override async Task<bool> Run()
        {
            var oldVersion = await GetSpecificVersion();
            LogPair.Add($"{module}OldVersion", oldVersion);
            if (string.IsNullOrEmpty(oldVersion))
            {
                InfoLog += $"Get {Name} version failed\n";
                Logger.Print($"Get {Name} version failed",isError:true);
                return false;
            }
            InfoLog += $"version before update : {oldVersion}\n";
            if (oldVersion == Version)
            {
                Logger.Print("Updated version detected");
            }
            Logger.Print("Firmware Uploading...");
            var uploadResult = await Uploadfiremware(fileName);
            LogPair.Add($"{module}Upload", uploadResult.ToString());
            if (uploadResult)
            {
                //wait restart
                Logger.Print("Waiting restart..");

                bool response = false;
                int pollinfCnt = 0;
                await Task.Delay(TimeSpan.FromMinutes(2));
                for (pollinfCnt = 0; pollinfCnt < 28; pollinfCnt++)
                {
                    await Task.Delay(TimeSpan.FromSeconds(15));
                    response = await ChekCsuBootCompelete();
                    if (response)
                        break;
                }

                //timeout
                if(pollinfCnt>=28)
                {
                    Logger.Print("Wait restart timeout",isError:true);
                    return false;
                }
            }
            else
            {
                InfoLog += $"Upload {Name} failed\n";
                Logger.Print($"Upload {Name} failed", isError: true);
                return false;
            }
            var updatedVersion = await GetSpecificVersion();
            LogPair.Add($"{module}NewVersion", updatedVersion);
            if (string.IsNullOrEmpty(updatedVersion))
            {
                InfoLog += $"Get updated {Name} version failed\n";
                Logger.Print($"Get updated {Name} version failed", isError: true);
                return false;
            }
            InfoLog += $"Get updated version : {updatedVersion}\n";
            bool isVersionMatched = false;
            if(module== "CsuRootFsFwRev")
            {
                isVersionMatched = updatedVersion.StartsWith(Version);
            }
            else
            {
                isVersionMatched = updatedVersion == Version;
            }
            if (isVersionMatched)
            {
                InfoLog += $"{Name}:Updated Version mismatched\n";
                Logger.Print($"{Name}:Updated Version mismatched", isError: true);
                return false;
            }
            else
            {
                Logger.Print($"{Name}:updated success");
            }

            return true;
        }

        internal virtual async Task<string> GetSpecificVersion()
        {
            var versions = await GetVersion();
            if(!versions.ContainsKey(module))
            {
                return "";
            }
            return versions[module];
        }

        internal async Task<Dictionary<string,string>> GetVersion(bool isConnectTest = false)
        {
            try
            {
                using (WebClientTimeout webClient = new WebClientTimeout())
                {
                    NameValueCollection parameters = new NameValueCollection();
                    parameters.Add("opt", "1");
                    webClient.QueryString = parameters;

                    using (Stream stream = await webClient.OpenReadTaskAsync($"https://{ServerIpAddress}/get_query_action.php"))
                    // 使用 StreamReader 讀取 stream 內的字元
                    using (StreamReader reader = new StreamReader(stream))
                    {
                        // 將 StreamReader 所讀到的字元轉為 string
                        string request = reader.ReadToEnd();
                        InfoLog += $"get version response:{request}\n";
                        var values = JsonConvert.DeserializeObject<Dictionary<string, object>>(request);

                        var toReturn = new Dictionary<string, string>();
                        foreach(var pair in values)
                        {
                            if (pair.Value is string v)
                            {
                                toReturn.Add(pair.Key, v);
                            }
                            else if(pair.Value is Newtonsoft.Json.Linq.JArray a)
                            {
                                try
                                {
                                    var versionList = JsonConvert.DeserializeObject<List<string>>(a.ToString());
                                    for (int index = 0; index < versionList.Count; index++)
                                    {
                                        toReturn.Add(string.Format("{0}{1}", pair.Key, index), versionList[index]);
                                    }
                                }
                                catch
                                {

                                }
                            }
                        }

                        return toReturn;
                    }
                }
            }
            catch(Exception e)
            {
                if (!isConnectTest)
                {
                    Logger.Print("Get Version Failed", isError: true);
                    Logger.Print(e.Message + "", isError: true);

                    InfoLog += "Get Version Failed\n";
                    InfoLog += e.Message;
                    InfoLog += "\n";
                }

                return null;
            }
        }

        internal async Task<bool> Uploadfiremware(string fileName)
        {
            try
            {
                //using (var stream = File.Open(fileName, FileMode.Open))
                //{
                //    UploadFileAsync(
                //    new NameValueCollection()
                //    {
                //        {"fw_tag","iso" }
                //    },
                //    new UploadFile()
                //    {
                //        Name = "file",
                //        Filename = Path.GetFileName(fileName),
                //        Stream = stream
                //    }
                //    );
                //}

                //return true;
                using (var stream = File.Open(fileName, FileMode.Open))
                {
                    var response = await UploadFiles(
                    $"https://{ServerIpAddress}/upgrade_iso_action.php",
                    new List<UploadFile>() {
                        new UploadFile()
                        {
                            Name="file",
                            Filename= Path.GetFileName(fileName),
                            Stream = stream
                        }
                    },
                    new NameValueCollection() {
                        {"fw_tag","iso" }
                    }
                    );

                    var responseStr = Encoding.ASCII.GetString(response).ToLower();
                    InfoLog += $"get firmware update response {responseStr}\n";
                    if (responseStr.Contains("file is uploaded"))
                        return true;
                    return false;
                }
                return true;
                //using (WebClient webClient = new WebClient())
                //{
                //    NameValueCollection parameters = new NameValueCollection();
                //    parameters.Add("fw_tag", "iso");
                //    webClient.QueryString = parameters;

                //    var responseBytes = await webClient.UploadFileTaskAsync($"https://{ServerIpAddress}/upgrade_iso_action.php", fileName);
                //    string responseString = Encoding.ASCII.GetString(responseBytes);
                //    return true;
                //}
            }
            catch(Exception e)
            {
                Logger.Print("Upload Firmware Failed", isError: true);
                Logger.Print(e.Message + "", isError: true);

                InfoLog += "Upload Firmware Failed\n";
                InfoLog += e.Message;
                InfoLog += "\n";

                return false;
            }
        }

        public async Task<byte[]> UploadFiles(string address, IEnumerable<UploadFile> files, NameValueCollection values)
        {
            var request = (HttpWebRequest) HttpWebRequest.Create(address);
            //request.Timeout = 10 * 1000;
            request.KeepAlive = true;
            request.Accept = "*/*";
            request.Method = "POST";
            request.Referer = address;
            request.Expect = "";
            var boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x", NumberFormatInfo.InvariantInfo);
            request.ContentType = "multipart/form-data; boundary=" + boundary;
            boundary = "--" + boundary;

            using (var requestStream = request.GetRequestStream())
            {
                // Write the values
                foreach (string name in values.Keys)
                {
                    var buffer = Encoding.ASCII.GetBytes(boundary + Environment.NewLine);
                    requestStream.Write(buffer, 0, buffer.Length);
                    buffer = Encoding.ASCII.GetBytes(string.Format("Content-Disposition: form-data; name=\"{0}\"{1}{1}", name, Environment.NewLine));
                    requestStream.Write(buffer, 0, buffer.Length);
                    buffer = Encoding.UTF8.GetBytes(values[name] + Environment.NewLine);
                    requestStream.Write(buffer, 0, buffer.Length);
                }

                // Write the files
                foreach (var file in files)
                {
                    var buffer = Encoding.ASCII.GetBytes(boundary + Environment.NewLine);
                    requestStream.Write(buffer, 0, buffer.Length);
                    buffer = Encoding.UTF8.GetBytes(string.Format("Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"{2}", file.Name, file.Filename, Environment.NewLine));
                    requestStream.Write(buffer, 0, buffer.Length);
                    var ctype = MimeMapping.GetMimeMapping(file.Filename);
                    buffer = Encoding.ASCII.GetBytes(string.Format("Content-Type: {0}{1}{1}", MimeMapping.GetMimeMapping(file.Filename), Environment.NewLine));
                    requestStream.Write(buffer, 0, buffer.Length);
                    file.Stream.CopyTo(requestStream);
                    buffer = Encoding.ASCII.GetBytes(Environment.NewLine);
                    requestStream.Write(buffer, 0, buffer.Length);
                }

                var boundaryBuffer = Encoding.ASCII.GetBytes(boundary + "--");
                requestStream.Write(boundaryBuffer, 0, boundaryBuffer.Length);
            }

            using (var response = await request.GetResponseAsync())
            using (var responseStream = response.GetResponseStream())
            using (var stream = new MemoryStream())
            {
                responseStream.CopyTo(stream);
                return stream.ToArray();
            }
        }

        [Obsolete]
        public void UploadFileAsync(NameValueCollection values, UploadFile file )
        {
            //to fire events on the calling thread
            var _asyncOperation = AsyncOperationManager.CreateOperation(null);
            var ms = new MemoryStream();
            //make a copy of the input stream in case sb uses disposable stream
            file.Stream.CopyTo(ms);
            //you cannot set stream position often enough to zero
            ms.Position = 0;

            Task.Factory.StartNew(() =>
            {
                try
                {
                    const string contentType = "application/octet-stream";

                    var request = WebRequest.Create($"https://{ServerIpAddress}/get_query_action.php");
                    request.Method = "POST";
                    var boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x", NumberFormatInfo.InvariantInfo);
                    request.ContentType = "multipart/form-data; boundary=" + boundary;
                    boundary = "--" + boundary;

                    var dataStream = new MemoryStream();
                    byte[] buffer;
                    // Write the values
                    foreach (string name in values.Keys)
                    {
                        buffer = Encoding.ASCII.GetBytes(boundary + Environment.NewLine);
                        dataStream.Write(buffer, 0, buffer.Length);
                        buffer = Encoding.ASCII.GetBytes(string.Format("Content-Disposition: form-data; name=\"{0}\"{1}{1}", name, Environment.NewLine));
                        dataStream.Write(buffer, 0, buffer.Length);
                        buffer = Encoding.UTF8.GetBytes(values[name] + Environment.NewLine);
                        dataStream.Write(buffer, 0, buffer.Length);
                    }

                    // Write the file
                    buffer = Encoding.ASCII.GetBytes(boundary + Environment.NewLine);
                    dataStream.Write(buffer, 0, buffer.Length);
                    buffer = Encoding.UTF8.GetBytes($"Content-Disposition: form-data; name=\"{file.Name}\"; filename=\"{file.Filename}\"{Environment.NewLine}");
                    dataStream.Write(buffer, 0, buffer.Length);
                    buffer = Encoding.ASCII.GetBytes(string.Format("Content-Type: {0}{1}{1}", MimeMapping.GetMimeMapping(file.Filename), Environment.NewLine));
                    dataStream.Write(buffer, 0, buffer.Length);
                    ms.CopyTo(dataStream);
                    buffer = Encoding.ASCII.GetBytes(Environment.NewLine);
                    dataStream.Write(buffer, 0, buffer.Length);

                    buffer = Encoding.ASCII.GetBytes(boundary + "--");
                    dataStream.Write(buffer, 0, buffer.Length);


                    dataStream.Position = 0;
                    //IMPORTANT: set content length to directly write to network socket
                    request.ContentLength = dataStream.Length;
                    var requestStream = request.GetRequestStream();

                    //Write data in chunks and report progress
                    var size = dataStream.Length;
                    const int chunkSize = 64 * 1024;
                    buffer = new byte[chunkSize];
                    long bytesSent = 0;
                    int readBytes;
                    while ((readBytes = dataStream.Read(buffer, 0, buffer.Length)) > 0)
                    {
                        requestStream.Write(buffer, 0, readBytes);
                        bytesSent += readBytes;

                        var status = "Uploading... " + bytesSent / 1024 + "KB of " + size / 1024 + "KB";
                        Console.WriteLine(status);
                    }

                    //get response
                    using (var response = request.GetResponse())
                    using (var responseStream = response.GetResponseStream())
                    using (var stream = new MemoryStream())
                    {
                        // ReSharper disable once PossibleNullReferenceException - exception would get catched anyway
                        responseStream.CopyTo(stream);
                        var result = Encoding.Default.GetString(stream.ToArray());
                        Console.WriteLine(result);
                    }
                }
                catch (Exception e )
                {
                    Console.WriteLine(e);
                }
            }, System.Threading.CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default);
        }
    }
}