using System;
using System.Linq;
using System.Threading.Tasks;
using Cirno.Scripts.Enums;
using Cirno.Scripts.Resources;
using Cirno.Scripts.Utils;
using Godot;
using Godot.Collections;
using GTweens.Builders;
using GTweensGodot.Extensions;

public partial class GlobalState : Node
{
    public static GlobalState Instance { get; private set; }
    public static SessionSettings Session => GlobalState.Instance.SessionSettings;
    public Node CurrentScene { get; set; }
    private ColorRect _fader { get; set; }

    public SessionSettings SessionSettings { get; set; } = new();

    private PackedScene _plaqueTemplate;

    private Control _loadingPlaque;

    private readonly StringName _menuMouseTexturePath = "uid://d3oxwik1uoe4j";
    private readonly StringName _reticuleMouseTexturePath = "uid://b76bhqv247nft";

    private readonly StringName _mapsDatabaseResource = "uid://blf2ii0j3fqil";

    private MapsDatabase _mapsDatabase;

    private Texture2D _menuMouseTexture;
    private Image _menuMouseImage;
    private Texture2D _reticuleMouseTexture;
    private Image _reticuleMouseImage;

    public bool UseMenuCursor { get; set; } = true;
    
    public override void _Ready()
    {
        Instance = this;

        this.ProcessMode = ProcessModeEnum.Always;

        _mapsDatabase = ResourceLoader.Load<MapsDatabase>(_mapsDatabaseResource);

        Viewport root = GetTree().Root;
        // Using a negative index counts from the end, so this gets the last child node of `root`.
        CurrentScene = root.GetChild(-1);

        _fader = CreateFader();

        _menuMouseTexture = ResourceLoader.Load<Texture2D>(_menuMouseTexturePath);
        _menuMouseImage = _menuMouseTexture.GetImage();
        
        _reticuleMouseTexture = ResourceLoader.Load<Texture2D>(_reticuleMouseTexturePath);
        _reticuleMouseImage = _reticuleMouseTexture.GetImage();

        GetTree().GetRoot().SizeChanged += OnSizeChanged;
        //_mouseTexture =
        OnSizeChanged();
        //LoadPlaque();
    }

    private void OnSizeChanged()
    {
        ResizeCursor();
    }

    public void ChangeCursor(bool useMenu)
    {
        UseMenuCursor = useMenu;
        ResizeCursor();
    }

    public void ResizeCursor()
    {
        var root = GetTree().GetRoot();
        var baseSize = root.ContentScaleSize;
        var newSize = root.Size;

        int scaleX = newSize.X / baseSize.X;
        int scaleY = newSize.Y / baseSize.Y;
        int scale = Math.Min(scaleX, scaleY); // Ensure pixel-perfect scaling

        ResizeCursor(UseMenuCursor ? scale / 2 : scale, UseMenuCursor);
    }

    public void GotoScene(string path)
    {
        // This function will usually be called from a signal callback,
        // or some other function from the current scene.
        // Deleting the current scene at this point is
        // a bad idea, because it may still be executing code.
        // This will result in a crash or unexpected behavior.

        // The solution is to defer the load to a later time, when
        // we can be sure that no code from the current scene is running:

        //CallDeferred(MethodName.DeferredGotoScene, path, new MapStartDataResource());

        GoToScene(path, new MapStartDataResource());
    }

    public void GoToScene(string path, MapStartDataResource startData)
    {
        GTweenSequenceBuilder.New()
            .AppendCallback(() => { _loadingPlaque?.Show(); })
            //.Append(_fader.TweenModulateAlpha(0, 0f))
            .Append(_fader.TweenModulateAlpha(1, 0.5f))
            .AppendCallback(() => { CallDeferred(MethodName.DeferredGotoScene, path, startData); })
            .Build()
            .PlayUnpausable();

        //CallDeferred(MethodName.DeferredGotoScene, path, startData);
    }

    public void GotoScene(MapResource map)
    {
        this.SessionSettings.LevelNumber = map.LevelId;
        GoToScene(map.ScenePath.ToString(), map.StartData);
    }

    private void DeferredGotoScene(string path, MapStartDataResource startData = null)
    {
        // It is now safe to remove the current scene.
        CurrentScene.Free();

        //var sceneParent = CurrentScene.GetParent();
        //sceneParent.RemoveChild(CurrentScene);
        //sceneParent.QueueFree();

        // Load a new scene.
        var nextScene = GD.Load<PackedScene>(path);

        // Instance the new scene.
        CurrentScene = nextScene.Instantiate();

        // Add it to the active scene, as child of root.
        GetTree().Root.AddChild(CurrentScene);

        // Optionally, to make it compatible with the SceneTree.change_scene_to_file() API.
        GetTree().CurrentScene = CurrentScene;

        // // Update current scene here too
        // CurrentScene = GetTree().Root.GetChild(-1);

        if (startData is not null && GameManager.Instance is not null)
        {
            // Call deferred if it gives issues
            DeferredAddStartDataToGameManager(startData);
        }

        // Do not do this here because it might get the old gamemanager instead
        // if (GameManager.Instance is not null) {
        //     GameManager.Instance.ApplySessionState(SessionSettings);
        // }

        FadeIn();
    }

    private void DeferredAddStartDataToGameManager(MapStartDataResource resource)
    {
        GameManager.Instance?.ApplyMapStartData(resource);
    }

    private Control LoadPlaque()
    {
        _plaqueTemplate = GD.Load<PackedScene>("res://Scenes/HUD/LoadingPlaque.tscn");

        return _plaqueTemplate.Instantiate<Control>();
    }

    private ColorRect CreateFader()
    {
        var canvas = new CanvasLayer();
        canvas.ProcessMode = ProcessModeEnum.Always;

        var rect = new ColorRect();
        rect.ZAsRelative = false;
        rect.ZIndex = 100;
        rect.SetAnchorsPreset(Control.LayoutPreset.FullRect);
        rect.Color = new Color(Colors.Black, 1f);
        rect.ProcessMode = ProcessModeEnum.Always;

        rect.Modulate = new Color(0, 0, 0, 0);

        rect.MouseFilter = Control.MouseFilterEnum.Ignore;

        canvas.CallDeferred("add_child", rect);

        this.CallDeferred("add_child", canvas);

        _loadingPlaque = LoadPlaque();
        canvas.CallDeferred("add_child", _loadingPlaque);
        _loadingPlaque.Hide();

        return rect;
    }

    public void FadeOut()
    {
        _fader.TweenModulateAlpha(1, 0.5f).PlayUnpausable();
        _loadingPlaque?.Show();
    }

    public void FadeIn()
    {
        _loadingPlaque?.Hide();
        _fader.TweenModulateAlpha(0, 1f).PlayUnpausable();
    }

    private readonly string SaveNameFile = "user://savegame.save";

    public void SaveGame()
    {
        var items = InventoryManager.Instance.Save();

        SessionSettings.Items = items;

        var serializedSavedata = new Godot.Collections.Dictionary<string, Variant>()
        {
            { "Items", items },
            { "Level", SessionSettings.LevelNumber },
            { "Difficulty", (int)SessionSettings.Difficulty }
        };

        var saveFile = FileAccess.Open(SaveNameFile, FileAccess.ModeFlags.Write);

        var jsonString = Json.Stringify(serializedSavedata);

        saveFile.StoreLine(jsonString);

        GD.Print("Saved data successfully");
    }

    public bool LoadGame()
    {
        if (!FileAccess.FileExists(SaveNameFile))
        {
            return false; // Error! We don't have a save to load.
        }

        using var saveFile = FileAccess.Open(SaveNameFile, FileAccess.ModeFlags.Read);

        var jsonString = saveFile.GetLine();
        var json = new Json();

        var parseResult = json.Parse(jsonString);
        if (parseResult != Error.Ok)
        {
            GD.Print($"JSON Parse Error: {json.GetErrorMessage()} in {jsonString} at line {json.GetErrorLine()}");
            return false;
        }

        var deserializedSaveData = new Dictionary<string, Variant>((Dictionary)json.Data);

        Dictionary<string, int> items = (Dictionary<string, int>)deserializedSaveData["Items"];

        DifficultyLevel difficulty = (DifficultyLevel)deserializedSaveData["Difficulty"].AsInt32();

        int levelNumber = (int)deserializedSaveData["Level"];

        var levelData = _mapsDatabase.Maps.FirstOrDefault(x => x.LevelId == levelNumber);
        if (levelData is null)
        {
            return false;
        }

        this.SessionSettings.NewSession();
        SessionSettings.LevelNumber = levelNumber;
        SessionSettings.Items = items;
        SessionSettings.Difficulty = difficulty;

        this.GotoScene(levelData);

        return true;
    }

    private void ResizeCursor(float scale, bool useMenuCursor)
    {
        Image scaled;
        Vector2I size;
        if (useMenuCursor)
        {
           scaled = (Image)_menuMouseImage.Duplicate();
           size = (Vector2I)(scale * _menuMouseTexture.GetSize());
           
        }
        else
        {
            scaled = (Image)_reticuleMouseImage.Duplicate();
            size = (Vector2I)(scale * _reticuleMouseTexture.GetSize());
        }

        scaled.Resize(size.X, size.Y, Image.Interpolation.Nearest);

        Input.SetCustomMouseCursor(scaled, Input.CursorShape.Arrow, useMenuCursor ? Vector2.Zero : new Vector2(size.X / 2,size.Y / 2));
        
        //DisplayServer.CursorSetCustomImage(scaled);
    }

    public void RestartLevel()
    {
        GotoScene(CurrentScene.GetSceneFilePath());
    }
}