using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using Unity.VisualScripting;
using UnityEditor;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.UIElements;
using static UnityEngine.GraphicsBuffer;
using static UnityEngine.UI.GridLayoutGroup;

[ExecuteAlways]
public class GameManager : MonoBehaviour
{
    public Transform ConnectionParent;
    public GameObject ConnectionPrefab;
    public Transform NodeParent;
    public GameObject NodePrefab;

    public Player players;
    public int currentPlayer = -1;
    public Node pressedNode = null;

    public static GameManager Instance { get; private set; }

    public bool regenerateOnChange = false;

    [HideInInspector] public float minConnectionLength = 6;
    [HideInInspector] public float maxConnectionLength = 6;
    [HideInInspector] public int nodeCount = 100;
    [HideInInspector] public int hoverRadiusCon = 50;
    [HideInInspector] public int hoverRadiusNode = 50;
    [HideInInspector] public int selectedLevel = -1;

    [SerializeField] [HideInInspector] public List<LevelData> levels = new List<LevelData>();

    public enum ActionType { MOVE_UNITS, DESTRUCT_CON, EXPLODE_CON };

    public struct Action
    {
        public ActionType type;
        public int nodeFromId;
        public int nodeToId;
        public int player;
    }

    [Serializable]
    public class LevelData
    {
        [Serializable]
        public class NodeData
        {
            public Vector3 position;
            public int owner;
        }

        [Serializable]
        public class ConnectionData
        {
            public int nodeAIndex;
            public int nodeBIndex;
            public bool allowed = true;
        }

        public float minConnectionLength = 0;
        public float maxConnectionLength = 0;
        public List<NodeData> nodes = new List<NodeData>();
        public List<ConnectionData> connections = new List<ConnectionData>();
    }

    void Awake()
    {
        if (Instance != null && Instance != this)
        {
            if (Application.isPlaying)
                Destroy(gameObject);
            return;
        }

        Instance = this;
        if (Application.isPlaying)
            DontDestroyOnLoad(gameObject);


        GetConnections().ForEach(obj => obj.DelConnect());
        GetConnections().ForEach(obj => obj.SetConnect());

    }

    private void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            pressedNode = GetNodes().Find(n => n.hovered && n.Owner == currentPlayer);
        }
        if (Input.GetMouseButtonUp(0))
        {
            if (pressedNode != null) 
            {
                Node toNode = GetNodes().Find(n => n.hovered && pressedNode != n);

                if (toNode)
                {
                    Connection con = Instance.GetConnections().Find(c => (c.nodeA == pressedNode && c.nodeB == toNode) || (c.nodeB == pressedNode && c.nodeA == toNode));
                    if (con)
                    {
                        var action = new Action { nodeFromId = pressedNode.id, nodeToId = toNode.id, type = ActionType.MOVE_UNITS, player = Instance.currentPlayer };
                        Instance.ExecuteAction(action);
                    }
                }
            }
        }

    }

    public List<Node> GetNodes() => NodeParent.GetComponentsInChildren<Node>().ToList();
    public List<Connection> GetConnections() => ConnectionParent?.GetComponentsInChildren<Connection>().ToList() ?? new();

    public void ExecuteRoundEnd()
    {
        foreach(Connection con in GetConnections())
        {
            if (con.state == Connection.BuildState.CONSTRUCTING)
                con.state = Connection.BuildState.BUILT;
            else if (con.state == Connection.BuildState.DECONSTRUCTING)
                con.state = Connection.BuildState.EMPTY;
        }
    }

    public void ExecuteAction(Action action)
    {
        Node nodeTo = GetNodes().Find(n => n.id == action.nodeToId);
        Node nodeFrom = GetNodes().Find(n => n.id == action.nodeFromId);
        Connection con = GetConnections().Find(c => (c.nodeA == nodeTo && c.nodeB == nodeFrom) || (c.nodeA == nodeFrom && c.nodeB == nodeTo));

        bool hostile = nodeTo.Owner != action.player;

        switch (action.type)
        {
            case ActionType.MOVE_UNITS:
                switch (con.state)
                {
                    case Connection.BuildState.EMPTY:
                        // Build Connection
                        con.state = Connection.BuildState.CONSTRUCTING;
                        Debug.Log("Starting construction from " + nodeTo.id + " to " + nodeFrom.id);
                        return;

                    case Connection.BuildState.CONSTRUCTING:
                        if (hostile)
                        {
                            Debug.Log("Attacking hostile construction from " + nodeTo.id + " to " + nodeFrom.id);
                        }
                        else
                        {
                            Debug.Log("This should not be possible O_o");
                        }
                        return;

                    case Connection.BuildState.DECONSTRUCTING:
                        if (hostile)
                        {
                            Debug.Log("Attacking hostile construction from " + nodeTo.id + " to " + nodeFrom.id);
                        }
                        else
                        {
                            Debug.Log("This should not be possible O_o");
                        }
                        return;

                    case Connection.BuildState.BUILT:
                        if (hostile)
                        {
                            Debug.Log("Attacking hostile units at " + nodeFrom.id);
                        }
                        else
                        {
                            Debug.Log("Moving units from " + nodeTo.id + " to " + nodeFrom.id);
                        }
                        return;

                    default:
                        return;
                }

            case ActionType.DESTRUCT_CON:
                if (!hostile)
                {
                    Debug.Log("Starten deconstruction from " + nodeTo.id + " to " + nodeFrom.id);
                    con.state = Connection.BuildState.DECONSTRUCTING;
                }
                else
                {
                    Debug.Log("This should not be possible O_o");
                }
                return;

            case ActionType.EXPLODE_CON:
                Debug.Log("Exploding connection from " + nodeTo.id + " to " + nodeFrom.id);
                con.state = Connection.BuildState.EMPTY;
                return;
        }
    }
    public void GenerateAlongSphere()
    {
        for (int i = NodeParent.childCount - 1; i >= 0; i--)
            DestroyImmediate(NodeParent.GetChild(i).gameObject);

        float radius = 20f;
        float goldenRatio = (1f + Mathf.Sqrt(5f)) / 2f;
        float angleIncrement = 2f * Mathf.PI * goldenRatio;

        for (int i = 0; i < nodeCount; i++)
        {
            float t = (float)i / nodeCount; // von 0 bis 1
            float inclination = Mathf.Acos(1f - 2f * t);
            float azimuth = angleIncrement * i;

            float x = Mathf.Sin(inclination) * Mathf.Cos(azimuth);
            float y = Mathf.Sin(inclination) * Mathf.Sin(azimuth);
            float z = Mathf.Cos(inclination);

            Vector3 pos = new Vector3(x, y, z) * radius;

            var auto = PrefabUtility.InstantiatePrefab(NodePrefab, NodeParent) as GameObject;
            auto.GetComponent<Node>().id = i;
            auto.transform.localPosition = pos;
        }
    }

    public void GenerateConnections()
    {
        for (int i = ConnectionParent.childCount - 1; i >= 0; i--)
            DestroyImmediate(ConnectionParent.GetChild(i).gameObject);

        var nodes = GetNodes();

        foreach (Node nodeA in nodes)
        {
            if (nodeA == null) continue;
            foreach (Node nodeB in nodes)
            {
                if (nodeB == null) continue;
                bool conExists = false;
                float dist = Vector3.Distance(nodeA.transform.position, nodeB.transform.position);
                if (nodeA == nodeB || dist > maxConnectionLength)
                    continue;

                foreach (Connection con in GetConnections())
                {
                    if ((con.nodeA == nodeA && con.nodeB == nodeB) || (con.nodeA == nodeB && con.nodeB == nodeA))
                    {
                        conExists = true;
                        break;
                    }
                }
                if (!conExists)
                {
                    AddConnection(nodeA, nodeB, dist < minConnectionLength);
                }
            }
                   
        }
    }

    public void AddConnection(Node nodeA, Node nodeB, bool allowed = true)
    {
        var newCon = PrefabUtility.InstantiatePrefab(ConnectionPrefab, ConnectionParent).GetComponent<Connection>();
        newCon.nodeA = nodeA;
        newCon.nodeB = nodeB;
        newCon.allowed = allowed;
    }

    public void LoadLevelData(int index)
    {
        if(index >= levels.Count)
        {
            Debug.LogWarning("LevelIndex out of range");
            return;
        }

        for (int i = NodeParent.childCount - 1; i >= 0; i--)
            DestroyImmediate(NodeParent.GetChild(i).gameObject);

        foreach (LineRenderer line in ConnectionParent.GetComponentsInChildren<LineRenderer>())
            DestroyImmediate(line.gameObject);


        for(int i = 0; i < levels[index].nodes.Count; i++)
        {
            var nodeData = levels[index].nodes[i];
            var auto = PrefabUtility.InstantiatePrefab(NodePrefab, NodeParent) as GameObject;
            auto.transform.localPosition = nodeData.position;
            auto.GetComponent<Node>().Owner = nodeData.owner;
            auto.GetComponent<Node>().id = i;
        }

        var currentNodes = GetNodes();

        int idx = 0;
        foreach (Node node in currentNodes)
            node.id = idx++;

        currentNodes = GetNodes();


        foreach (LevelData.ConnectionData conData in levels[index].connections)
        {
            AddConnection(currentNodes[conData.nodeAIndex], currentNodes[conData.nodeBIndex], conData.allowed);
            Debug.Log(conData.nodeAIndex + " - " + conData.nodeBIndex);
        }
        selectedLevel = index;
        minConnectionLength = levels[index].minConnectionLength;
        maxConnectionLength = levels[index].maxConnectionLength;
        nodeCount = currentNodes.Count;
    }

    public void SaveLevelData(int index = -1)
    {
        LevelData data = new LevelData();

        data.minConnectionLength = minConnectionLength;
        data.maxConnectionLength = maxConnectionLength;

        // Nodes speichern
        foreach (var node in GetNodes())
        {
            data.nodes.Add(new LevelData.NodeData
            {
                position = node.transform.localPosition,
                owner = node.Owner
            });
        }

        // Connections speichern
        foreach (var con in GetConnections())
        {
            if (con.nodeA.id >= 0 && con.nodeB.id >= 0)
            {
                data.connections.Add(new LevelData.ConnectionData
                {
                    nodeAIndex = con.nodeA.id,
                    nodeBIndex = con.nodeB.id,
                    allowed = con.allowed
                });
            }
        }

        if (index == -1 || index >= levels.Count)
            levels.Add(data);
        else
            levels[index] = data;

        int newIndex = index < 0 ? levels.Count - 1 : index;
        selectedLevel = newIndex;
    }
}