using System;

using System.Collections.Generic;
using System.Threading.Tasks;
using Newtonsoft.Json;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Networking;
using System.Net;
using System.Net.Sockets;
public class DBmanager : MonoBehaviour
{
    public static string phpRoot = "http://vps.playpoolstudios.com/upf/";

    public static string username = null;

    private static float level = 0;
    private static int xp = 0;
    private static int coins = 0;
    private static int gems = 0;
    private static int metal = 0;
    private static int oxygen = 0;
    private static List<InventoryEntry> inventory;
    private static List<int> expPassCollected = new List<int>();
    private static List<string> skinsPurchased = new List<string>();
    public static List<BuildingState> buildingStates = new List<BuildingState>();
    public static UnityEvent OnStateChanged = new UnityEvent();

    public static int Xp => xp;
    public static int Coins => coins;
    public static int Gems => gems;
    public static int Metal => metal;
    public static int Oxygen => oxygen;
    public static float Level => level;
    public static int LevelInt => Mathf.CeilToInt(level);
    public static List<int> ExpPassCollected => expPassCollected;
    public static List<InventoryEntry> Inventory => inventory;
    public static List<string> SkinsPurchased => skinsPurchased;


    public static bool LoggedIn { get { return username != null; } }
    public static void LogOut()
    {
        username = null;
    }

    public static async Task<DateTime> GetNetworkTime()
    {
        int unixTime = 0;
        using (UnityWebRequest www = UnityWebRequest.Get(phpRoot + "get_time.php"))
        {
            var operation = www.SendWebRequest();
            while (!operation.isDone)
            {
                await Task.Yield();
            }
            try
            {
                unixTime = int.Parse(www.downloadHandler.text);
            }
            catch
            {
                Debug.Log("Invalid response from server : " + www.downloadHandler.text);
            }
        }

        if (unixTime > 0)
        {
            return DateTimeOffset.FromUnixTimeSeconds(unixTime).UtcDateTime;
        }

        //default Windows time server
        const string ntpServer = "time.windows.com";

        // NTP message size - 16 bytes of the digest (RFC 2030)
        var ntpData = new byte[48];

        //Setting the Leap Indicator, Version Number and Mode values
        ntpData[0] = 0x1B; //LI = 0 (no warning), VN = 3 (IPv4 only), Mode = 3 (Client Mode)

        var addresses = Dns.GetHostEntry(ntpServer).AddressList;

        //The UDP port number assigned to NTP is 123
        var ipEndPoint = new IPEndPoint(addresses[0], 123);
        //NTP uses UDP

        using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp))
        {
            socket.Connect(ipEndPoint);

            //Stops code hang if NTP is blocked
            socket.ReceiveTimeout = 3000;

            socket.Send(ntpData);
            socket.Receive(ntpData);
            socket.Close();
        }

        //Offset to get to the "Transmit Timestamp" field (time at which the reply
        //departed the server for the client, in 64-bit timestamp format."
        const byte serverReplyTime = 40;

        //Get the seconds part
        ulong intPart = BitConverter.ToUInt32(ntpData, serverReplyTime);

        //Get the seconds fraction
        ulong fractPart = BitConverter.ToUInt32(ntpData, serverReplyTime + 4);

        //Convert From big-endian to little-endian
        intPart = SwapEndianness(intPart);
        fractPart = SwapEndianness(fractPart);

        var milliseconds = (intPart * 1000) + ((fractPart * 1000) / 0x100000000L);

        //**UTC** time
        var networkDateTime = (new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc)).AddMilliseconds((long)milliseconds);

        return networkDateTime.ToLocalTime();
    }


    static uint SwapEndianness(ulong x)
    {
        return (uint)(((x & 0x000000ff) << 24) +
        ((x & 0x0000ff00) << 8) +
        ((x & 0x00ff0000) >> 8) +
        ((x & 0xff000000) >> 24));
    }

    public async static Task SetXp(int newValue, bool justOffline = false)
    {
        WWWForm form = new WWWForm();
        form.AddField("name", username);
        form.AddField("xp", newValue);
        if (justOffline) { xp = newValue; }
        else
        {
            using (UnityWebRequest www = UnityWebRequest.Post(phpRoot + "set_xp.php", form))
            {
                var operation = www.SendWebRequest();
                while (!operation.isDone)
                {
                    await Task.Yield();
                }

                if (www.downloadHandler.text == "0")
                {
                    xp = newValue;
                    // Debug
                }
                else
                {
                    Debug.Log("Response : " + www.downloadHandler.text);
                    Debug.LogWarning("Failed to set xp to " + newValue);
                }
            }
        }

        //        for(int i =0; i < xp / 100; i++){
        //            i
        //        }

        level = (Mathf.Sqrt((float)Mathf.Clamp(xp, 100, float.PositiveInfinity) / 100f));
        if (level == LevelInt)
        {
            level -= 0.1f;
        }
        Debug.Log("Level : " + (float)xp / 100f + " : " + level + " : " + LevelInt);
        GameManagerInstance.gameManager.RefreshData();
        OnStateChanged.Invoke();
    }

    public async static Task SetCoins(int newValue, bool justOffline = false)
    {
        WWWForm form = new WWWForm();
        form.AddField("name", username);
        form.AddField("coins", newValue);
        coins = newValue;
        if (justOffline) { return; }
        using (UnityWebRequest www = UnityWebRequest.Post(phpRoot + "set_coins.php", form))
        {
            var operation = www.SendWebRequest();
            while (!operation.isDone)
            {
                await Task.Yield();
            }

            if (www.downloadHandler.text == "0")
            {
                coins = newValue;
            }
            else
            {
                Debug.Log("Response : " + www.downloadHandler.text);
                Debug.LogWarning("Failed to set coins to " + newValue);
            }
        }

        GameManagerInstance.gameManager.RefreshData();
        OnStateChanged.Invoke();
    }

    public async static void SetGems(int newValue, bool justOffline = false)
    {
        WWWForm form = new WWWForm();
        form.AddField("name", username);
        form.AddField("gems", newValue);
        if (justOffline) { gems = newValue; return; }
        using (UnityWebRequest www = UnityWebRequest.Post(phpRoot + "set_gems.php", form))
        {
            var operation = www.SendWebRequest();
            while (!operation.isDone)
            {
                await Task.Yield();
            }

            if (www.downloadHandler.text == "0")
            {
                gems = newValue;
            }
            else
            {
                Debug.Log("Response : " + www.downloadHandler.text);
                Debug.LogWarning("Failed to set gems to " + newValue);
            }
        }

        GameManagerInstance.gameManager.RefreshData();
        OnStateChanged.Invoke();
    }

    public async static void SetMetal(int newValue, bool justOffline = false)
    {
        WWWForm form = new WWWForm();
        form.AddField("name", username);
        form.AddField("metal", newValue);
        if (justOffline) { metal = newValue; return; }
        using (UnityWebRequest www = UnityWebRequest.Post(phpRoot + "set_metal.php", form))
        {
            var operation = www.SendWebRequest();
            while (!operation.isDone)
            {
                await Task.Yield();
            }

            if (www.downloadHandler.text == "0")
            {
                metal = newValue;
                Debug.Log("Metal updated on "+ username);
            }
            else
            {
                Debug.Log("Response : " + www.downloadHandler.text);
                Debug.LogWarning("Failed to set metal to " + newValue);
            }
        }

        GameManagerInstance.gameManager.RefreshData();
        OnStateChanged.Invoke();
    }

    public async static void SetOxygen(int newValue, bool justOffline = false)
    {
        WWWForm form = new WWWForm();
        form.AddField("name", username);
        form.AddField("oxygen", newValue);
        if (justOffline) { oxygen = newValue; return; }
        using (UnityWebRequest www = UnityWebRequest.Post(phpRoot + "set_oxygen.php", form))
        {
            var operation = www.SendWebRequest();
            while (!operation.isDone)
            {
                await Task.Yield();
            }

            if (www.downloadHandler.text == "0")
            {
                oxygen = newValue;
            }
            else
            {
                Debug.Log("Response : " + www.downloadHandler.text);
                Debug.LogWarning("Failed to set oxygen to " + newValue);
            }
        }

        GameManagerInstance.gameManager.RefreshData();
        OnStateChanged.Invoke();
    }

    public static void SetExpPassCollected(string rawData, bool justOffline = false)
    {
        try
        {
            string[] data = rawData.Split(',');
            expPassCollected = new List<int>();
            foreach (string item in data)
            {
                try
                {
                    int itemVal = int.Parse(item);
                    expPassCollected.Add(itemVal);
                }
                catch
                {

                }
            }

            GameManagerInstance.gameManager.RefreshData();
            OnStateChanged.Invoke();
        }
        catch
        {

        }
    }

    public static void SetPurchasedSkins(string data){
        skinsPurchased= new List<string>();
        string[] skins = data.Split(',');
        foreach(string skin in skins){
            skinsPurchased.Add(skin);
        }
    }

    public static void PurchaseSkin(SkinShopItemData data){
        if(metal < data.price){
            return;
        }
        SetMetal(metal-data.price);
        skinsPurchased.Add(data.name);
        UpdatePurchasedSkins();
    }

    public async static void UpdatePurchasedSkins(){
        string output ="";
        foreach(string skin in skinsPurchased){
            if(skin.Length==0){continue;}
            output += skin +",";
        }
        if(output.Length>0){output = output.Substring(0,output.Length-1);} // <-- Removes the last ','

        WWWForm form = new WWWForm();
        form.AddField("name", username);
        form.AddField("skins", output);
        using (UnityWebRequest www = UnityWebRequest.Post(phpRoot + "set_skins_purchased.php", form))
        {
            var operation = www.SendWebRequest();
            while (!operation.isDone)
            {
                await Task.Yield();
            }

            if (www.downloadHandler.text == "0")
            {
                Debug.Log("Success updating PurchasedSkins");
            }
            else
            {
                Debug.Log("Response : " + www.downloadHandler.text);
                Debug.LogWarning("Failed to set PurchasedSkins to " + output);
            }
        }

        GameManagerInstance.gameManager.RefreshData();
        OnStateChanged.Invoke();
    }

    public async static void AddCollectedExpPass(int newPassLevel)
    {
        expPassCollected.Add(newPassLevel);
        string output = "";
        for (int i = 0; i < expPassCollected.Count; i++)
        {
            output += expPassCollected[i].ToString();
            if (i < expPassCollected.Count - 1)
            {
                output += ",";
            }
        }

        WWWForm form = new WWWForm();
        form.AddField("name", username);
        form.AddField("expPass", output);
        using (UnityWebRequest www = UnityWebRequest.Post(phpRoot + "set_expPassCollected.php", form))
        {
            var operation = www.SendWebRequest();
            while (!operation.isDone)
            {
                await Task.Yield();
            }

            if (www.downloadHandler.text == "0")
            {
                Debug.Log("Success updating ExpPassCollected");
            }
            else
            {
                Debug.Log("Response : " + www.downloadHandler.text);
                Debug.LogWarning("Failed to set exp_pass_collected to " + output);
            }
        }

        GameManagerInstance.gameManager.RefreshData();
        OnStateChanged.Invoke();
    }

    public static bool GetBuildingStates(string rawData)
    {
        bool success = false;
        //try{

        buildingStates = JsonConvert.DeserializeObject<List<BuildingState>>(rawData);
        Debug.Log("Updating buildings data, isNull: " + (buildingStates == null).ToString());
        if (buildingStates == null)
        {
            buildingStates = new List<BuildingState>();
        }
        success = true;
        //        }catch(Exception e){
        //            Debug.LogError(e.Message+'\n' + e.StackTrace);
        //
        //            Debug.LogError("Error updating buildings from server, Response:" + rawData);
        //            success=false;
        //        }

        OnStateChanged.Invoke();
        return success;
    }

   

    public async static Task AddBuilding(BuildingData buildingData)
    {

        foreach (BuildingState buildingState in buildingStates)
        {
            if (buildingState.id == buildingData.buildingName)
            {
                Debug.LogError("Building already exists. Cannot add " + buildingState.id);
                return;
            }
        }
        Debug.Log("adding new building " + buildingData.buildingName);
        buildingStates.Add(new BuildingState(buildingData.buildingName, 0, Vector3.zero, await GetNetworkTime()));
        Debug.Log("Added new building " + buildingData.buildingName);
        await UpdateBuildingsToServer();
        OnStateChanged.Invoke();
        SetXp(xp + buildingData.levels[0].xpGain);
    }

    public async static Task UpgradeBuilding(string id, int newLevel)
    {
        for (int i = 0; i < buildingStates.Count; i++)
        {
            if (buildingStates[i].id == id)
            {
                buildingStates[i].level = newLevel;
                Debug.Log("Upgrading " + id + " to " + newLevel);
                break;
            }
        }

        await UpdateBuildingsToServer();

        OnStateChanged.Invoke();
    }


    public async static Task CollectBuilding(string id)
    {
        for (int i = 0; i < buildingStates.Count; i++)
        {
            if (buildingStates[i].id == id)
            {
                buildingStates[i].lastCollectedTimestamp = await GetNetworkTime();
                Debug.Log("Setting " + id + " last collected to " + buildingStates[i].lastCollectedTimestamp);
                break;
            }
        }

        await UpdateBuildingsToServer();

        OnStateChanged.Invoke();
    }

    public async static Task RelocateBuilding(string id, Vector3 newPosition)
    {
        for (int i = 0; i < buildingStates.Count; i++)
        {
            if (buildingStates[i].id == id)
            {
                buildingStates[i].position = newPosition;
                Debug.Log("Relocating " + id + " to " + newPosition);
                // break;
            }
        }
        Debug.Log("Going to update to server" + JsonConvert.SerializeObject(buildingStates));
        await UpdateBuildingsToServer();

        OnStateChanged.Invoke();
    }

    public async static Task UpdateBuildingsToServer()
    {
        string buildingsJson = JsonConvert.SerializeObject(buildingStates);
        Debug.Log(buildingsJson);

        WWWForm form = new WWWForm();
        form.AddField("name", username);
        form.AddField("buildings", buildingsJson);

        using (UnityWebRequest www = UnityWebRequest.Post(phpRoot + "set_buildings.php", form))
        {
            var operation = www.SendWebRequest();
            while (!operation.isDone)
            {
                await Task.Yield();
            }

            if (www.downloadHandler.text == "0")
            {

            }
            else
            {
                Debug.Log("Response : " + www.downloadHandler.text);
                Debug.LogWarning("Failed to set buildings to " + buildingsJson);
            }
        }
    }

    public static bool GetInventoryFromServer(string rawJson)
    {
        // try{
            inventory = JsonConvert.DeserializeObject<List<InventoryEntry>>(rawJson);
        // }catch{
        //     inventory = null;
            
        // }

        if(inventory==null){
            Debug.Log("Failed to set inventory, server said : " + rawJson);
            inventory= new List<InventoryEntry>();
        }

        return true;
    }

    public async static void AddInventoryItem(InventoryItem item)
    {
        bool exists = false;
        if(inventory==null){inventory = new List<InventoryEntry>();}
        
        foreach(InventoryEntry entry in inventory){
            if(entry.Item == item.itemName){
                entry.Count++;
                exists=true;
                break;
            }
        }
        if(!exists){
            inventory.Add(new InventoryEntry(item.itemName,1));
        }

        await UpdateInventoryToServer();
        OnStateChanged.Invoke();
    }

    public async static void RemoveInventoryItem(string item)
    {
        List<InventoryEntry> temp_inventory = inventory;
        inventory = new List<InventoryEntry>();
        foreach(InventoryEntry entry in temp_inventory){
            if(entry.Item == item){continue;}
            inventory.Add(entry);
        }

        if(inventory.Count == temp_inventory.Count){Debug.LogError("No item was removed from inventory");}

        await UpdateInventoryToServer();
        OnStateChanged.Invoke();
    }


    public async static Task UpdateInventoryToServer()
    {
        string inventoryJson = JsonConvert.SerializeObject(inventory);
        Debug.Log(inventoryJson);

        WWWForm form = new WWWForm();
        form.AddField("name", username);
        form.AddField("inventory", inventoryJson);

        using (UnityWebRequest www = UnityWebRequest.Post(phpRoot + "set_inventory.php", form))
        {
            var operation = www.SendWebRequest();
            while (!operation.isDone)
            {
                await Task.Yield();
            }

            if (www.downloadHandler.text == "0")
            {

            }
            else
            {
                Debug.Log("Response : " + www.downloadHandler.text);
                Debug.LogWarning("Failed to set inventory to " + inventoryJson);
            }
        }
    }



    public async static Task SellItem(string item, int coins_amount, int gems_amount, int metals_amount, int oxygen_amount){
        
        //Insert new trade entry to server
        WWWForm form = new WWWForm();
        form.AddField("name", username);
        form.AddField("item", item);
        form.AddField("coins", coins_amount);
        form.AddField("gems", gems_amount);
        form.AddField("metals", metals_amount);
        form.AddField("oxygen", oxygen_amount);



        using (UnityWebRequest www = UnityWebRequest.Post(phpRoot + "add_trade.php", form))
        {
            var operation = www.SendWebRequest();
            while (!operation.isDone)
            {
                await Task.Yield();
            }

            if (www.downloadHandler.text == "0")
            {
                RemoveInventoryItem(item);
            }
            else
            {
                Debug.Log("Response : " + www.downloadHandler.text);
                Debug.LogWarning("Failed to add trade entry");
            }
        }

        OnStateChanged.Invoke();
    }

}
