using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
#if !UNITY_WEBGL && !UNITY_SERVER
using CesiumForUnity;
#endif
using Cysharp.Threading.Tasks;
using Proyecto26;
using UnityEngine;
using UnityEngine.Networking;
using XenseAR;

namespace CBLP.MegaStudio
{

    public class XenseARBlockTool : MonoBehaviour
    {
        [Serializable]
        private class EditorSession
        {
            public string sessid;
            public string session_name;
            public string token;
            public User data;
        }

        [Serializable]
        private class User
        {
            public string uid = "";
            public string name;
            public string mail;
            public string token;
        }

        string rootName = "XenseARBlocks";
        string cacheFile = "";
        public string account = "";
        public string password = "";
        public bool loggedIn = false;
        public XenseARBlockRoot RootBlock;
        public XenseStudioConfigScriptableObjectScript StudioConfig;
        public List<BlockItem> Blocks = new List<BlockItem>();
        List<BlockItem> serachResult = new List<BlockItem>();
        public List<BlockItem> FullList = new List<BlockItem>();
        public string searchKey = "";

        [SerializeField]
        private EditorSession currentSess;

        public Vector2 scrollPosition = new Vector2(0, 0);

        public CancellationTokenSource cts;
        public bool importing = false;
        public string importUrl = "";

        public string message = "";

        public bool hideChild = false;

        [Serializable]
        public class BlockItem
        {
            public string blockId;
            public string title;
            public string url;

            public BlockItem(string blockId, string title, string url)
            {
                this.blockId = blockId;
                this.title = title;
                this.url = url;
            }
        }

        [Serializable]
        private class AvailableBlocksResponse
        {
            public string status;
            public string message;
            public List<BlockItem> data;
        }

        private void Start()
        {
            string backendEndpoint = StudioConfig.hostUrl + "/api/v1";
            BackendServices.Instance.UpdateHostUrl(backendEndpoint);
            BackendServices.Instance.UpdateAuthorizationUid(currentSess.data.uid);
            BackendServices.Instance.UpdateAuthenticationUid(currentSess.data.token);
        }

        public void Login()
        {
            if (string.IsNullOrEmpty(account) || string.IsNullOrEmpty(password))
            {
                Debug.Log("Log in failed");
            }
            else
            {
                LogginIn();
            }

        }

        public void LogOut()
        {
            StopImportMesh();
            ClearChild(RootBlock.gameObject);
            Blocks.Clear();
            serachResult.Clear();
            FullList.Clear();
            searchKey = "";
            currentSess = null;
            loggedIn = false;
        }

        public void CreateBlockRoot()
        {
            RootBlock = new GameObject(rootName).AddComponent<XenseARBlockRoot>();
        }

        public void LoadBlockList()
        {
            //Load blocks
            StartCoroutine(GetAvailableBlock());
        }

        private async void LogginIn()
        {
            string loginRoute = "/api/auth_v2";
            if (StudioConfig == null)
            {
                Debug.Log("Please fill the Studio setting at *ARWorld -> Settings -> Studio Setting* on Menu bar!");
                return;
            }
            if (StudioConfig.overrideAuthenRoute)
            {
                loginRoute = StudioConfig.authenRoute;
                Debug.Log("Override auth: " + loginRoute);
            }
            using (UnityWebRequest www = UnityWebRequest.Post(StudioConfig.hostUrl + loginRoute, "{\"type\": \"UserLogin\", \"username\": \"" + account + "\", \"password\": \"" + password + "\"}", "application/json"))
            {
                Debug.Log("Logging in....");
                var loginTask = www.SendWebRequest();

                while (!loginTask.isDone)
                {
                    await UniTask.Yield();
                }

                if (www.result != UnityWebRequest.Result.Success)
                {
                    Debug.Log("Log in failed");
                    Debug.LogError(www.error);
                }
                else
                {
                    if (www.downloadHandler.text.Contains("error"))
                    {
                        Debug.Log("Log in failed");
                    }
                    else
                    {
                        currentSess = JsonUtility.FromJson<EditorSession>(www.downloadHandler.text);
                        if (string.IsNullOrEmpty(currentSess.data.name))
                        {
                            Debug.Log("Log in failed");
                            return;
                        }
                        RootBlock = FindAnyObjectByType<XenseARBlockRoot>();
                        Debug.Log($"User {currentSess.data.name} Logged in");
                        currentSess.data.token = GetBothCookie(www.GetResponseHeaders());
                        loggedIn = true;
                    }
                }
            }

        }

        string GetCookie(IDictionary<string, string> headers, string cookieName)
        {
            if (!headers.TryGetValue("Set-Cookie", out string cookieHeader) &&
                !headers.TryGetValue("SET-COOKIE", out cookieHeader))
            {
                return null; // no cookie found
            }

            var cookieParts = cookieHeader.Split(';');

            foreach (var part in cookieParts)
            {
                var trimmed = part.Trim();
                if (trimmed.StartsWith(cookieName + "="))
                {
                    return trimmed.Substring(cookieName.Length + 1); // return only value
                }
            }

            return null;
        }

        string GetBothCookie(IDictionary<string, string> headers)
        {
            if (!headers.TryGetValue("Set-Cookie", out string cookieHeader) &&
                !headers.TryGetValue("SET-COOKIE", out cookieHeader))
            {
                Debug.LogWarning("No 'Set-Cookie' header found in the response.");
                return null;
            }

            var cookieParts = cookieHeader.Split(';');

            foreach (var part in cookieParts)
            {
                var trimmed = part.Trim();
                if (trimmed.StartsWith("authToken="))
                {
                    return trimmed.Substring("authToken".Length + 1); // return only value
                }
            }

            return cookieParts[0].Trim(); 
        }

        IEnumerator GetAvailableBlock()
        {
            if (string.IsNullOrEmpty(currentSess.data.uid)) yield break;
            using (UnityWebRequest www = UnityWebRequest.Post(StudioConfig.hostUrl + "/api/v1", "{ \"type\": \"GetAvailableBlock\"}", "application/json"))
            {
                if (currentSess.data.token.StartsWith("SESS"))
                {
                  www.SetRequestHeader("Cookie", currentSess.data.token);
                } else {
                  www.SetRequestHeader("Authorization", currentSess.data.token);
                }
                yield return www.SendWebRequest();

                if (www.result != UnityWebRequest.Result.Success)
                {
                    Debug.LogError(www.error);
                }
                else
                {
                    OnFinishSave(www.downloadHandler.text);
                }
            }
        }

        private void OnFinishSave(string resText)
        {
            Debug.Log("Load done");
            FullList = JsonUtility.FromJson<AvailableBlocksResponse>(resText).data;
            Blocks = FullList;
        }

        public void Search()
        {
            serachResult.Clear();
            foreach (var item in FullList)
            {
                if (item.title.ToLower().Contains(searchKey.ToLower()))
                {
                    serachResult.Add(item);
                }
            }
            Blocks = serachResult;
        }

        public void ClearChild(GameObject parent)
        {
            List<GameObject> childBlocks = new List<GameObject>();
            int childCount = parent.transform.childCount;
            for (int i = 0; i < childCount; i++)
            {
                childBlocks.Add(parent.transform.GetChild(i).gameObject);
            }

            foreach (GameObject childBlock in childBlocks)
            {
                DestroyImmediate(childBlock);
            }
        }

        public void ExportModel(string id, string url, string saveFolderPath)
        {
            if (!string.IsNullOrEmpty(saveFolderPath))
            {
                string fileName = "";

                if (url.Contains(".glb") || url.Contains(".gltf"))
                {
                    // use GltfCacher to get cache file path
                    GltfCacher cacher = new GltfCacher();
                    string cacheFile = cacher.GetCacheFile(url);

                    if (File.Exists(cacheFile))
                    {
                        fileName = Path.GetFileName(url);
                        File.Copy(cacheFile, Path.Combine(saveFolderPath, fileName), true);
                        Debug.Log("[" + fileName + "]" + " Export successfully from cache to: " + Path.Combine(saveFolderPath, fileName));
                        return;

                    }
                    else
                    {
                        fileName = Path.GetFileName(url);
                        StartCoroutine(DownloadFile(url, Path.Combine(saveFolderPath, fileName), fileName));
                        Debug.Log("Cache file not found, downloading from source...");
                        return;
                    }
                }
                else
                {
                    fileName = Path.GetFileName(url + ".glb");
                    url = url + ".glb";
                    StartCoroutine(DownloadFile(url, Path.Combine(saveFolderPath, fileName), fileName));
                    return;
                }

            }
        }


        private IEnumerator DownloadFile(string url, string savePath, string fileName)
        {
            UnityWebRequest request = UnityWebRequest.Get(url);
            request.SendWebRequest();
            while (!request.isDone)
            {
                Debug.Log("[" + fileName + "]" + " Download progress: " + request.downloadProgress * 100 + "%");
                yield return null;
            }
            if (request.result == UnityWebRequest.Result.Success)
            {
                File.WriteAllBytes(savePath, request.downloadHandler.data);
                Debug.Log("[" + fileName + "]" + " Download successfully to: " + savePath);
            }
            else
            {
                Debug.Log("[" + fileName + "]" + " Error while download file!");
            }
        }

        public async void ImportMesh(string id, string url)
        {
            if (importing) return;
            cts = new CancellationTokenSource();
            ClearChild(RootBlock.gameObject);

            foreach (var block in Blocks)
            {
                if (!id.Equals(block.blockId)) continue;
                ARWorldBlockInfo newChild = new GameObject("Block_" + block.title).AddComponent<ARWorldBlockInfo>();
                newChild.transform.parent = RootBlock.transform;
                BlockInfo newBlockInfo = new BlockInfo();
                newBlockInfo.ID = block.blockId;
                newBlockInfo.Name = block.title;
                newChild.UpdateInfo(newBlockInfo);

                //Special case with GPS block
                //check vps service
                Defines.LocationSettingsData setting = null;
                bool isCheckService = true;

                string jsonPayload = "{ \"type\": \"GetLocationSettingsByBlockId\", \"BlockID\": \"" + id + "\"}";

                using (UnityWebRequest www = new UnityWebRequest(StudioConfig.hostUrl + "/api/v1", "POST"))
                {
                    byte[] bodyRaw = System.Text.Encoding.UTF8.GetBytes(jsonPayload);
                    www.uploadHandler = new UploadHandlerRaw(bodyRaw);
                    www.downloadHandler = new DownloadHandlerBuffer();
                    www.SetRequestHeader("Content-Type", "application/json");
                    www.SetRequestHeader("Authorization", currentSess.data.token);

                    try
                    {
                        await www.SendWebRequest().ToUniTask();

                        if (www.result != UnityWebRequest.Result.Success)
                        {
                            Debug.LogError(www.error);
                            isCheckService = false;
                        }
                        else
                        {
                            setting = JsonUtility.FromJson<Defines.LocationSettingsData>(www.downloadHandler.text);
                            isCheckService = false;
                        }
                    }
                    catch (System.Exception ex)
                    {
                        Debug.LogError($"Exception during request: {ex.Message}");
                        isCheckService = false;
                    }
                }

                while (isCheckService) await UniTask.Yield();

                if (setting.data.vpsService.ToLower() == "gps")
                {
#if !UNITY_WEBGL && !UNITY_SERVER
                    CesiumGeoreference cesiumGeoreference = FindAnyObjectByType<CesiumGeoreference>(FindObjectsInactive.Include);
                    cesiumGeoreference.gameObject.SetActive(true);
                    cesiumGeoreference.latitude = setting.data.gps.x;
                    cesiumGeoreference.longitude = setting.data.gps.y;
                    cesiumGeoreference.height = 0;
                    cesiumGeoreference.MoveOrigin();
                    Cesium3DTileset cesium3DTileset = cesiumGeoreference.GetComponentsInChildren<Cesium3DTileset>().Where(tileset => tileset.gameObject.name.Contains("Google")).First();
                    try
                    {
                        cesium3DTileset.OnTileGameObjectCreated -= Cesium3DTileset_OnTileGameObjectCreated;
                    }
                    catch { }
                    cesium3DTileset.OnTileGameObjectCreated += Cesium3DTileset_OnTileGameObjectCreated;
                    cesium3DTileset.RecreateTileset();
                    Debug.Log("[" + block.title + "]" + " Import Done -> Enter Play Mode to load Map");
#endif
                }
                else
                {
#if !UNITY_WEBGL && !UNITY_SERVER
                    CesiumGeoreference cesiumGeoreference = FindAnyObjectByType<CesiumGeoreference>(FindObjectsInactive.Include);
                    cesiumGeoreference.gameObject.SetActive(false);
#endif
                    var loadSuccess = false;

                    if (string.IsNullOrEmpty(url))
                    {
                        Debug.Log("Empty Mesh Data detected!!! Contact your provider!");
                    }

                    else if (url.ToLower().Contains(".glb") || url.ToLower().Contains(".gltf"))
                    {
                        hideChild = false;

                        var newObject = new GameObject();
                        var gltfLoader = newObject.AddComponent<GltfLoader>();

                        gltfLoader.LoadOnStartup = false;

                        newObject.transform.parent = this.gameObject.transform;

                        var loadingTask = gltfLoader.LoadWithCancelationToken(url, null, null, null, null, cts.Token);

                        importing = true;
                        importUrl = url;
                        Debug.Log("Import in progress");
                        message = "Downloading...";

                        ShowGltfLoadingProgress(gltfLoader);
                        loadSuccess = await loadingTask;

                    }

                    else
                    {
                        hideChild = false;
                        AssetBundleLoader loader = new AssetBundleLoader(this, url, "");
                        var loadingTask = loader.LoadAsync(null, true, null, cts.Token);

                        importing = true;
                        importUrl = url;
                        Debug.Log("Import in progress");
                        message = "Downloading...";

                        ShowAssetBundleLoadingProgress(loader);
                        loadSuccess = await loadingTask;
                    }


                    importing = false;
                    importUrl = string.Empty;
                    message = "";
                    hideChild = true;

                    if (loadSuccess)
                    {
                        Debug.Log("Import Mesh Done");
                    }
                    else
                    {
                        Debug.Log("Import Mesh Failed");
                        ClearChild(newChild.gameObject);
                    }

                }
                if (!loggedIn)
                {
                    break;
                }
            }
        }

        private void Cesium3DTileset_OnTileGameObjectCreated(GameObject obj)
        {
            MeshFilter meshFilter = obj.GetComponentInChildren<MeshFilter>();

            Vector3 size = meshFilter.mesh.bounds.size;
            if (size.x > 100f || size.z > 100f) return;

            Vector3 max = meshFilter.mesh.bounds.max;
            Vector3 min = meshFilter.mesh.bounds.min;
            if (max.x * min.x > 0 || max.z * min.z > 0) return;

            RaycastHit raycastHit;
            if (Physics.Raycast(new Vector3(0, 10000, 0), Vector3.down, out raycastHit, Mathf.Infinity))
            {
                float heightDif = raycastHit.point.y;
#if !UNITY_WEBGL && !UNITY_SERVER
                CesiumGeoreference cesiumGeoreference = FindAnyObjectByType<CesiumGeoreference>(FindObjectsInactive.Include);
                cesiumGeoreference.height = cesiumGeoreference.height + heightDif;
#endif
            }
        }

        private async UniTask<bool> ShowAssetBundleLoadingProgress(AssetBundleLoader loader)
        {
            while (importing)
            {
                UpdateDownloadProgress(loader.getDownloadProgress());
                await UniTask.WaitForSeconds(0.3f);
            }
            return true;
        }

        private void UpdateDownloadProgress(float progress)
        {
            message = "Downloading... " + (progress * 100).ToString("F2") + " %";
        }

        public GameObject GetGameObject()
        {
            return this.gameObject;
        }

        public void StopImportMesh()
        {
            cts?.Cancel();
            importing = false;
            importUrl = string.Empty;
            message = "";
            hideChild = false;
        }

        private async UniTask<bool> ShowGltfLoadingProgress(GltfLoader gltfLoader)
        {
            while (importing)
            {
                float progress = gltfLoader.getDownloadProgress();
                UpdateDownloadProgress(progress);
                await UniTask.WaitForSeconds(0.3f);
            }
            return true;
        }
    }
}
