using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using System;
using Newtonsoft.Json;
using RothenburgAR.Common;
using System.IO;
using TMPro;

namespace RothenburgAR.Updater
{
    public class UpdaterBehaviour : MonoBehaviour
    {
        public UnityEngine.UI.Slider ProgressBar;
        public GameObject UpdateDialog;
        public GameObject UpdateConfirmationDialog;
        public GameObject UpdateCompletedDialog;
        public GameObject UpdateFailedDialog;

        private readonly float afterDownloadWaitTime = 10f;

        public VersioncheckAnswer VersionAnswer { get; set; }
        private List<HttpHandler> httpHandlers = new List<HttpHandler>();
        private LogFileHandler logFileHandler;

        void Start()
        {
            /*	TODO: allg. fragen
                punkt zum eintragen der credentials?
            */

#if !UNITY_EDITOR
            logFileHandler = new LogFileHandler();
#else
            Debug.Log("Found Unity Editor; Disabled logging into file.");
#endif
            InitUI();

            if (Application.internetReachability == NetworkReachability.NotReachable)
            {
                // just continue to app
                LoadMainScene();
                return;
            }

            CheckForUpdates();
        }

        private void InitUI()
        {
            FindObjectsOfType<Camera>().First().enabled = true;
            UpdateDialog.SetActive(false);
            UpdateConfirmationDialog.SetActive(false);
            UpdateCompletedDialog.SetActive(false);
            UpdateFailedDialog.SetActive(false);
            ProgressBar.value = 0;
        }

        private void CheckForUpdates()
        {
            // check for updates and ask for permission to download if there are any
            string versionMap = GenerateVersionMap();
            HttpHandler http = new HttpRequest(ApiInfo.VersionCheckEndpoint, HttpVerb.POST, versionMap).Send();
            httpHandlers.Add(http);

            http.Operation.completed += ar =>
            {
                if (CheckNetworkErrors(http))
                {
                    return;
                }

                Debug.Log(http.Download.text);

                VersionAnswer = JsonConvert.DeserializeObject<VersioncheckAnswer>(http.Download.text);
                //VersionAnswer = JsonConvert.DeserializeObject<VersioncheckAnswer>(@"{""languages"":[""de"",""en""],""data"":[{""id"":""006e164c-5e31-4ddf-adf5-df7016c8b3a8"",""meta"":{""status"":""ok"",""updateUrl"":""https://lambdalike.pa.kaim.network/meta/006e164c-5e31-4ddf-adf5-df7016c8b3a7/{lang}""},""tracker"":{""status"":""ok"",""updateUrl"":""https://lambdalike.pa.kaim.network/meta/006e164c-5e31-4ddf-adf5-df7016c8b3a7/{lang}""}},{""id"":""006e164c-5e31-4ddf-adf5-df7016c8b3a7"",""meta"":{""status"":""updated"",""updateUrl"":""https://lambdalike.pa.kaim.network/meta/006e164c-5e31-4ddf-adf5-df7016c8b3a7/{lang}""},""tracker"":{""status"":""ok"",""updateUrl"":""https://lambdalike.pa.kaim.network/meta/006e164c-5e31-4ddf-adf5-df7016c8b3a7/{lang}""}}]}");
                Debug.Log(string.Format("{1}-DONE with {0}", ApiInfo.VersionCheckEndpoint, Time.realtimeSinceStartup));

                var answer = VersionAnswer;

                if (answer.Data.TrueForAll(d => d.Meta.Status == VersionStatus.ok && d.Tracker.Status == VersionStatus.ok))
                {
                    // no updates required, continue to app
                    LoadMainScene();
                    return;
                }

                UpdateDialog.SetActive(true);

                if (Application.internetReachability == NetworkReachability.ReachableViaLocalAreaNetwork)
                {
                    TriggerUpdate();
                }
                else
                {
                    UpdateConfirmationDialog.SetActive(true);
                }
            };
        }

        private string GenerateVersionMap()
        {
            string result = "{ ";

            var rootDir = new DirectoryInfo(PathHelper.ExhibitionPath);
            if (!rootDir.Exists)
            {
                rootDir.Create();
            }

            var exhibitionDirs = rootDir.GetDirectories().ToList();
            exhibitionDirs.ForEach(dir =>
            {
                var subdir = dir.GetDirectories().ToList().FirstOrDefault();
                if (subdir == null) return;

                var metaFilePath = Path.Combine(subdir.FullName, "meta.json");
                if (!File.Exists(metaFilePath)) return;

                var exhibits = JsonConvert.DeserializeObject<List<Exhibit>>(File.ReadAllText(metaFilePath));
                long newestVersion = 0;
                foreach (var exhibit in exhibits)
                {
                    newestVersion = Math.Max(newestVersion, exhibit.UpdatedTime);
                    //result += @"""{id}"":""{version}"",".Replace("{id}", exhibit.Id).Replace("{version}", exhibit.UpdatedTime.ToString());
                }

                result += @"""{id}"":""{version}"",".Replace("{id}", dir.Name).Replace("{version}", newestVersion.ToString());
            });

            result = result.Substring(0, result.Length - 1) + "}";
            return result;
        }

        public void TriggerUpdate()
        {
            /*
             * dir structure:
             *     data
             *         exhibitions
             *             id
             *                 tracker.dat
             *                 tracker.xml
             *                 de
             *                     meta.json
             *                 en
             *                     meta.json
             *             id
             *                 ...
             *         poi
             *             ...
             *         media
             *             ...
             */

            UpdateConfirmationDialog.SetActive(false);
            UpdateFailedDialog.SetActive(false);

            //TODO write languages to file the app can read (so that the languagemanager can decide which languages the user can choose from)

            var updatedMeta = VersionAnswer.Data.Where(d => d.Meta.Status == VersionStatus.updated).ToList();
            var updatedTracker = VersionAnswer.Data.Where(d => d.Tracker.Status == VersionStatus.updated).ToList();
            updatedMeta.Union(updatedTracker).ToList().ForEach(updatedExhibition =>
            {
                // create exhibition directories
                var path = Path.Combine(PathHelper.ExhibitionPath, updatedExhibition.Id);
                if (!Directory.Exists(path))
                {
                    Directory.CreateDirectory(path);
                }
            });

            updatedMeta.ForEach(d => UpdateMeta(d));
            updatedTracker.ForEach(d => UpdateTracker(d));

            var deletedData = VersionAnswer.Data.Where(d =>
                d.Meta.Status == VersionStatus.deleted
                || d.Tracker.Status == VersionStatus.deleted).ToList();
            deletedData.ForEach(d => DeleteExhibition(d));
        }

        private void DeleteExhibition(ExhibitionVersion d)
        {
            //TODO implement DeleteExhibition
            throw new NotImplementedException();
        }

        private void UpdateMeta(ExhibitionVersion exhibition)
        {
            foreach (var lang in VersionAnswer.Languages)
            {
                var path = PathHelper.CombinePaths(PathHelper.ExhibitionPath, exhibition.Id, lang);
                var url = exhibition.Meta.UpdateUrl.Replace("{lang}", lang);
                var http = new HttpRequest(url, HttpVerb.GET).Send();
                httpHandlers.Add(http);

                http.Operation.completed += ar =>
                {
                    if (CheckNetworkErrors(http))
                    {
                        return;
                    }

                    if (!Directory.Exists(path))
                    {
                        Directory.CreateDirectory(path);
                    }

                    File.WriteAllText(Path.Combine(path, "meta.json"), http.Download.text, System.Text.Encoding.UTF8);
                    Debug.Log(string.Format("{1}-DONE with {0}", url, Time.realtimeSinceStartup));

                    var exhibits = JsonConvert.DeserializeObject<List<Exhibit>>(http.Download.text);
                    foreach (var exhibit in exhibits)
                    {
                        UpdateMedia(exhibit);
                    }
                };
            }

        }

        private void UpdateMedia(Exhibit exhibit)
        {
            var mediaIDs = exhibit.Pois.Select(p => p.MediaId).Except(new List<string> { null }).ToList();

            foreach (var mediaId in mediaIDs)
            {
                var path = PathHelper.CombinePaths(PathHelper.MediaPath, mediaId);
                var filepath = Path.Combine(path, "media.zip");

                //TODO cancel download if unnecessary, how to check if unnecessary?
                if (File.Exists(filepath)) continue;

                // create file and dir if nonexistent
                if (!Directory.Exists(path)) Directory.CreateDirectory(path);
                if (!File.Exists(filepath)) File.Create(filepath).Close();

                var url = ApiInfo.FileEndpoint.Replace("{id}", mediaId);
                var http = new HttpRequest(url, HttpVerb.GET).Send();
                httpHandlers.Add(http);

                http.Operation.completed += ar =>
                {
                    if (CheckNetworkErrors(http))
                    {
                        return;
                    }

                    var type = http.Request.GetResponseHeader("Content-Type");
                    File.WriteAllBytes(filepath, http.Download.data);
                    Debug.Log(string.Format("{1}-DONE with {0}", url, Time.realtimeSinceStartup));

                    //TODO unzip
                };
            }
        }

        private bool CheckNetworkErrors(HttpHandler http)
        {
            if (http.Request.isNetworkError || http.Request.isHttpError)
            {
                Debug.LogError(String.Format("Error while downloading\nurl: {0}\nNetwork Error: {1}\nHttp Error: {2}\nHttp Response Code: {3}",
                    http.Request.url,
                    http.Request.isNetworkError,
                    http.Request.isHttpError,
                    http.Request.responseCode));

                UpdateDialog.SetActive(true);
                UpdateFailedDialog.SetActive(true);

                //TODO decide on level of detail for user notification 
                //var all = UpdateFailedDialog.GetComponentsInChildren<TextMeshProUGUI>().ToList();
                //var errorText = all.First(c => c.name == "ErrorText");
                //errorText.text = string.Format("Fehlercode: {0}", http.Request.responseCode.ToString());

                return true;
            }
            return false;
        }

        private void UpdateTracker(ExhibitionVersion exhibition)
        {
            //TODO implement UpdateTracker
            throw new NotImplementedException();
        }

        public void LoadMainScene()
        {
            Debug.Log("Loading mainScene");
            UnityEngine.SceneManagement.SceneManager.LoadScene("mainScene");
        }

        private DateTime downloadEndedTime = DateTime.Now.AddYears(1);

        void Update()
        {
            // Update the download progress bar
            if (httpHandlers.Count > 0)
            {
                float downloadProgress = 0f;
                httpHandlers.ForEach(h =>
                {
                    downloadProgress += h.Operation.progress;
                });

                downloadProgress /= httpHandlers.Count;

                // operation.progress is bugged and goes all over the place:
                //      jumps from 0 to 1 and back despite not being done 
                //      steadily increases from 0,5 to 0,99 and resets to 0,5 multiple times (5-15x) during a single download

                // update progress bar and ignore all negative changes
                ProgressBar.value = ProgressBar.value < downloadProgress ? downloadProgress : ProgressBar.value;
            }

            // Continue to Main Scene after all downloads are done and this.afterDownloadWaitTime seconds have passed without any new downloads triggering.
            if (httpHandlers.All(h => h.IsDone))
            {
                if (downloadEndedTime > DateTime.Now)
                {
                    Debug.Log("Done Updating");
                    UpdateCompletedDialog.SetActive(true);
                    downloadEndedTime = DateTime.Now;
                }
                if (downloadEndedTime.AddSeconds(afterDownloadWaitTime) < DateTime.Now)
                {
                    LoadMainScene();
                }
            }
            else
            {
                downloadEndedTime = DateTime.Now.AddSeconds(1);
            }
        }
    }
}