#include "tests.hpp"
#include "../../shared.hpp"
#include "../../hyprctlCompat.hpp"
#include <cstdint>
#include <string>
#include <format>
#include <hyprutils/os/Process.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
#include "../shared.hpp"

using namespace Hyprutils::OS;
using namespace Hyprutils::Memory;

#define UP CUniquePointer
#define SP CSharedPointer

static std::string getCommandStdOut(std::string command) {
    CProcess process("bash", {"-c", command});
    process.addEnv("HYPRLAND_INSTANCE_SIGNATURE", HIS);
    process.runSync();

    const std::string& stdOut = process.stdOut();

    // Remove trailing new line
    return stdOut.substr(0, stdOut.length() - 1);
}

static void setWindowProp(const std::string& selector, const std::string& prop, const std::string& value) {
    getFromSocket(std::format("/dispatch hl.dsp.window.set_prop({{ window = '{}', prop = '{}', value = '{}' }})", selector, prop, value));
}

TEST_CASE(hyprctlDevicesActiveLayoutIndex) {
    // configure layouts
    OK(getFromSocket("r/eval hl.config({ input = { kb_layout = \"us,pl,ua\" } })"));

    for (uint8_t i = 0; i < 3; i++) {
        // set layout
        getFromSocket(std::format("/switchxkblayout all {}", i));
        std::string devicesJson = getFromSocket("j/devices");
        std::string expected    = std::format(R"("active_layout_index": {})", i);
        // check layout index
        EXPECT_CONTAINS(devicesJson, expected);
    }
}

TEST_CASE(hyprctlGetprop) {
    if (!Tests::spawnKitty()) {
        FAIL_TEST("Could not spawn kitty");
    }

    // animation
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty animation"), "(unset)");
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty animation -j"), R"({"animation": ""})");
    setWindowProp("class:kitty", "animation", "teststyle");
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty animation"), "teststyle");
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty animation -j"), R"({"animation": "teststyle"})");

    // max_size
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty max_size"), "inf inf");
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty max_size -j"), R"({"max_size": [null,null]})");
    setWindowProp("class:kitty", "max_size", "200 150");
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty max_size"), "200 150");
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty max_size -j"), R"({"max_size": [200,150]})");

    // min_size
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size"), "20 20");
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size -j"), R"({"min_size": [20,20]})");
    setWindowProp("class:kitty", "min_size", "100 50");
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size"), "100 50");
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size -j"), R"({"min_size": [100,50]})");

    // expr-based min/max _size
    getFromSocket("/dispatch hl.dsp.window.float({ action = 'set', window = 'class:kitty' })"); // need to set floating for tests below
    setWindowProp("class:kitty", "max_size", "90+10 25*2");                                     // set max to the same as min above, forcing window to 100*50
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty max_size"), "100 50");
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty max_size -j"), R"({"max_size": [100,50]})");
    setWindowProp("class:kitty", "min_size", "window_w*0.5 window_h-10");
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size"), "50 40");
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size -j"), R"({"min_size": [50,40]})");
    getFromSocket("/dispatch hl.dsp.window.float({ action = 'unset', window = 'class:kitty' })"); // go back to tiled for consistency

    // opacity
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity"), "1");
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity -j"), R"({"opacity": 1})");
    setWindowProp("class:kitty", "opacity", "0.3");
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity"), "0.3");
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity -j"), R"({"opacity": 0.3})");

    // opacity_inactive
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive"), "1");
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive -j"), R"({"opacity_inactive": 1})");
    setWindowProp("class:kitty", "opacity_inactive", "0.5");
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive"), "0.5");
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive -j"), R"({"opacity_inactive": 0.5})");

    // opacity_fullscreen
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen"), "1");
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen -j"), R"({"opacity_fullscreen": 1})");
    setWindowProp("class:kitty", "opacity_fullscreen", "0.75");
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen"), "0.75");
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen -j"), R"({"opacity_fullscreen": 0.75})");

    // opacity_override
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_override"), "false");
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_override -j"), R"({"opacity_override": false})");
    setWindowProp("class:kitty", "opacity_override", "true");
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_override"), "true");
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_override -j"), R"({"opacity_override": true})");

    // opacity_inactive_override
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive_override"), "false");
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive_override -j"), R"({"opacity_inactive_override": false})");
    setWindowProp("class:kitty", "opacity_inactive_override", "true");
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive_override"), "true");
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive_override -j"), R"({"opacity_inactive_override": true})");

    // opacity_fullscreen_override
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen_override"), "false");
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen_override -j"), R"({"opacity_fullscreen_override": false})");
    setWindowProp("class:kitty", "opacity_fullscreen_override", "true");
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen_override"), "true");
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen_override -j"), R"({"opacity_fullscreen_override": true})");

    // active_border_color
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty active_border_color"), "ee33ccff ee00ff99 45deg");
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty active_border_color -j"), R"({"active_border_color": "ee33ccff ee00ff99 45deg"})");
    setWindowProp("class:kitty", "active_border_color", "rgb(abcdef)");
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty active_border_color"), "ffabcdef 0deg");
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty active_border_color -j"), R"({"active_border_color": "ffabcdef 0deg"})");

    // bool window properties
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty allows_input"), "false");
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty allows_input -j"), R"({"allows_input": false})");
    setWindowProp("class:kitty", "allows_input", "true");
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty allows_input"), "true");
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty allows_input -j"), R"({"allows_input": true})");

    // int window properties
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding"), "10");
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding -j"), R"({"rounding": 10})");
    setWindowProp("class:kitty", "rounding", "4");
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding"), "4");
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding -j"), R"({"rounding": 4})");

    // float window properties
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding_power"), "2");
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding_power -j"), R"({"rounding_power": 2})");
    setWindowProp("class:kitty", "rounding_power", "1.25");
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding_power"), "1.25");
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding_power -j"), R"({"rounding_power": 1.25})");

    // errors
    EXPECT(getCommandStdOut("hyprctl getprop"), "not enough args");
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty"), "not enough args");
    EXPECT(getCommandStdOut("hyprctl getprop class:nonexistantclass animation"), "window not found");
    EXPECT(getCommandStdOut("hyprctl getprop class:kitty nonexistantprop"), "prop not found");
}

TEST_CASE(hyprctlSubmap) {
    EXPECT(getCommandStdOut("hyprctl submap"), "default\n");
    EXPECT(getCommandStdOut("hyprctl submap -j | jq -r \".\""), "default");
}

TEST_CASE(hyprctlJsonErrors) {
    CProcess jqProc("bash", {"-c", "hyprctl descriptions | jq"});
    jqProc.addEnv("HYPRLAND_INSTANCE_SIGNATURE", HIS);
    jqProc.runSync();
    EXPECT(jqProc.exitCode(), 0);
}

TEST_CASE(hyprctlBindsJson) {
    EXPECT(getFromSocket("/eval hl.bind('SUPER + F12', hl.dsp.exec_cmd('true'), { description = 'hyprctl binds JSON regression', locked = true, repeating = true, "
                         "allow_input_capture = false })"),
           "ok");

    CProcess jqProc("bash", {"-c", R"(hyprctl -j binds | jq -e '
        type == "array" and
        ([.[] | select(.key == "F12")] | length == 1) and
        any(.[];
            .key == "F12" and
            .has_description == true and
            (.description | type) == "string" and
            .description == "hyprctl binds JSON regression" and
            (.allow_input_capture | type) == "boolean" and
            .allow_input_capture == false
        )
    ')"});
    jqProc.addEnv("HYPRLAND_INSTANCE_SIGNATURE", HIS);
    jqProc.runSync();
    EXPECT(jqProc.exitCode(), 0);

    const auto BINDS    = getFromSocket("/binds");
    const auto DESC_POS = BINDS.find("description: hyprctl binds JSON regression");
    EXPECT(DESC_POS != std::string::npos, true);
    if (DESC_POS != std::string::npos) {
        const auto BIND_POS = BINDS.rfind("bind\n", DESC_POS);
        EXPECT(BIND_POS != std::string::npos, true);
        if (BIND_POS != std::string::npos) {
            const auto BLOCK = BINDS.substr(BIND_POS, DESC_POS - BIND_POS);
            EXPECT(BLOCK.contains("\tflags: locked, repeat\n"), true);
        }
    }

    EXPECT(getFromSocket("/eval hl.unbind('SUPER + F12')"), "ok");
}

TEST_CASE(hyprctlREPL) {
    EXPECT(getCommandStdOut("hyprctl repl 'print(type(hl))'"), "table");
    EXPECT(getCommandStdOut("hyprctl eval 'print(type(hl))'"), "ok");
}
