package dev.isxander.controlify.gui.screen;

import com.google.gson.Gson;
import com.google.gson.JsonObject;
import dev.isxander.controlify.Controlify;
import dev.isxander.controlify.controller.Controller;
import dev.isxander.controlify.controller.ControllerType;
import dev.isxander.controlify.hid.HIDDevice;
import dev.isxander.controlify.utils.ClientUtils;
import dev.isxander.controlify.utils.Log;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.ChatFormatting;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.*;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.chat.Component;
import org.lwjgl.glfw.GLFW;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.regex.Pattern;

public class SubmitUnknownControllerScreen extends Screen {
    public static final String SUBMISSION_URL = "https://api-controlify.isxander.dev/api/v1/submit";
    public static final Pattern NAME_PATTERN = Pattern.compile("^[\\w\\- ]{3,32}$");

    private final Controller<?, ?> controller;

    private Checkbox operationalCheckbox;

    private final Screen lastScreen;

    private boolean invalidName;

    private Button submitButton;
    private EditBox nameField;

    public SubmitUnknownControllerScreen(Controller<?, ?> controller, Screen lastScreen) {
        super(Component.translatable("controlify.controller_submission.title").withStyle(ChatFormatting.BOLD));
        if (!canSubmit(controller))
            throw new IllegalArgumentException("Controller ineligible for submission!");
        this.controller = controller;
        this.lastScreen = lastScreen;
    }

    @Override
    protected void init() {
        var content = this.addRenderableWidget(
                new MultiLineTextWidget(
                        Component.translatable("controlify.controller_submission.message"),
                        font
                )
        );
        content.setMaxWidth(this.width - 100);
        content.setX(this.width / 2 - content.getWidth() / 2);

        int titleBottomPadding = 11;
        int checkboxPadding = 6;
        int checkboxHeight = 20;
        int buttonHeight = 20;
        int nameFieldPaddingTop = 5;
        int nameFieldHeight = 20;
        int errorPadding = 4;

        int allHeight = font.lineHeight + titleBottomPadding + content.getHeight() + checkboxPadding + checkboxHeight + checkboxPadding + buttonHeight + nameFieldPaddingTop + nameFieldHeight + errorPadding + font.lineHeight;

        int y = this.height / 2 - allHeight / 2;
        this.addRenderableWidget(ClientUtils.createStringWidget(this.getTitle(), font, 25, y));
        y += font.lineHeight + titleBottomPadding;

        content.setY(y);
        y += content.getHeight() + checkboxPadding;

        var operationalText = Component.translatable("controlify.controller_submission.operational_checkbox")
                .withStyle(ChatFormatting.BOLD);
        this.operationalCheckbox = this.addRenderableWidget(
                new Checkbox(
                        this.width / 2 - font.width(operationalText) / 2 - 8,
                        y,
                        150,
                        checkboxHeight,
                        operationalText,
                        true
                )
        );
        y += checkboxHeight + checkboxPadding;

        this.submitButton = this.addRenderableWidget(
                Button.builder(Component.translatable("controlify.controller_submission.submit"), this::onSubmitButton)
                        .pos(this.width / 2 - 155, y)
                        .width(150)
                        .build()
        );
        this.addRenderableWidget(
                Button.builder(Component.translatable("controlify.controller_submission.skip"), btn -> onClose())
                        .pos(this.width / 2 + 5, y)
                        .width(150)
                        .build()
        );
        y += buttonHeight + nameFieldPaddingTop;

        this.nameField = this.addRenderableWidget(
                new EditBox(
                      font,
                      this.width / 2 - 155,
                      y,
                      310, 20,
                      Component.translatable("controlify.controller_submission.name_narration")
                )
        );
        this.nameField.setHint(Component.translatable("controlify.controller_submission.name_hint"));
        this.nameField.setValue(controller.name());
        this.nameField.setFilter(s -> {
            invalidName = !checkValidName(s);
            submitButton.active = !invalidName;
            return true;
        });
    }

    @Override
    public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) {
        renderBackground(graphics);

        super.render(graphics, mouseX, mouseY, delta);

        if (invalidName) {
            graphics.drawCenteredString(font, Component.translatable("controlify.controller_submission.invalid_name").withStyle(ChatFormatting.RED), this.width / 2, nameField.getRectangle().bottom() + 4, -1);
        }
    }

    protected void onSubmitButton(Button button) {
        if (submit()) {
            dontShowAgain();
            onClose();
        } else {
            // TODO: Show error message
            dontShowAgain();

            onClose();
        }
    }

    protected boolean submit() {
        try {
            HttpClient client = HttpClient.newHttpClient();
            HttpRequest request = HttpRequest.newBuilder()
                    .uri(new URI(SUBMISSION_URL))
                    .POST(HttpRequest.BodyPublishers.ofString(this.generateRequestBody()))
                    .header("Content-Type", "application/json")
                    .build();
            HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());

            if (response.statusCode() / 100 != 2) {
                Log.LOGGER.error("Received non-2xx status code from '{}', got {} with body '{}'", SUBMISSION_URL, response.statusCode(), response.body());
                return false;
            }

            Log.LOGGER.info("Successfully sent controller information to '{}'", SUBMISSION_URL);
            return true;
        } catch (Exception e) {
            Log.LOGGER.error("Failed to submit controller to '%s'".formatted(SUBMISSION_URL), e);
            return false;
        }
    }

    private String generateRequestBody() {
        HIDDevice hid = controller.hidInfo().orElseThrow().hidDevice().orElseThrow();

        JsonObject object = new JsonObject();
        object.addProperty("vendorID", hid.vendorID());
        object.addProperty("productID", hid.productID());
        object.addProperty("GUID", GLFW.glfwGetJoystickGUID(controller.joystickId()));
        object.addProperty("reportedName", nameField.getValue());
        object.addProperty("controlifyVersion", FabricLoader.getInstance().getModContainer("controlify").orElseThrow().getMetadata().getVersion().getFriendlyString());
        object.addProperty("operational", operationalCheckbox.selected());

        Gson gson = new Gson();
        return gson.toJson(object);
    }

    private boolean checkValidName(String name) {
        return NAME_PATTERN.matcher(name.trim()).matches();
    }

    private void dontShowAgain() {
        controller.config().dontShowControllerSubmission = true;
        Controlify.instance().config().setDirty();
    }

    @Override
    public void onClose() {
        dontShowAgain();

        Controlify.instance().config().saveIfDirty();
        minecraft.setScreen(lastScreen);
    }

    @Override
    public boolean shouldCloseOnEsc() {
        return false;
    }

    public static boolean canSubmit(Controller<?, ?> controller) {
        return controller.type() == ControllerType.UNKNOWN
                && !controller.config().dontShowControllerSubmission
                && controller.hidInfo()
                        .map(info -> info.hidDevice().isPresent())
                        .orElse(false);
    }
}
