/*
 * This file is part of GriefPrevention, licensed under the MIT License (MIT).
 *
 * Copyright (c) bloodmc
 * Copyright (c) contributors
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package me.ryanhamshire.griefprevention;

import com.google.gson.Gson;
import com.google.gson.JsonObject;

import me.ryanhamshire.griefprevention.util.HttpClient;
import org.spongepowered.api.Sponge;
import org.spongepowered.api.command.CommandSource;
import org.spongepowered.api.entity.living.player.User;
import org.spongepowered.api.plugin.PluginContainer;
import org.spongepowered.api.text.Text;
import org.spongepowered.api.text.action.TextActions;
import org.spongepowered.api.text.format.TextColors;
import org.spongepowered.api.util.Tristate;

import okhttp3.MediaType;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.zip.GZIPOutputStream;

public class GPDebugData {
    private static final String BYTEBIN_ENDPOINT = "https://bytebin.lucko.me/post";
    private static final String DEBUG_VIEWER_URL = "https://griefprevention.github.io/debug/?";
    private static final MediaType PLAIN_TYPE = MediaType.parse("text/plain; charset=utf-8");

    private static final int MAX_LINES = 5000;
    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
    private static final Text GP_TEXT = Text.of(TextColors.RESET, "[", TextColors.AQUA, "GP", TextColors.WHITE, "] ");

    private final CommandSource source;
    private final List<String> header;
    private final List<String> records;
    private final long startTime = System.currentTimeMillis();
    private boolean verbose;
    private User target;

    public GPDebugData(CommandSource source, User target, boolean verbose) {
        this.source = source;
        this.target = target;
        this.verbose = verbose;
        this.records = new ArrayList<>();
        this.header = new ArrayList<>();
        this.header.add("# GriefPrevention Debug Log");
        this.header.add("#### This file was automatically generated by [GriefPrevention](https://github.com/MinecraftPortCentral/GriefPrevention) ");
        this.header.add("");
        this.header.add("### Metadata");
        this.header.add("| Key | Value |");
        this.header.add("|-----|-------|");
        this.header.add("| GP Version | " + GriefPreventionPlugin.IMPLEMENTATION_VERSION + "|");
        this.header.add("| Sponge Version | " + GriefPreventionPlugin.SPONGE_VERSION + "|");
        final PluginContainer lpContainer = Sponge.getPluginManager().getPlugin("luckperms").orElse(null);
        if (lpContainer != null) {
            final String version = lpContainer.getVersion().orElse(null);
            if (version != null) {
                this.header.add("| LuckPerms Version | " + version);
            }
        }
        this.header.add("| User | " + (this.target == null ? "ALL" : this.target.getName()) + "|");
        this.header.add("| Record start | " + DATE_FORMAT.format(new Date(this.startTime)) + "|");
    }

    public void addRecord(String flag, String trust, String source, String target, String location, String user, Tristate result) {
        if (this.records.size() < MAX_LINES) {
            this.records.add("| " + flag + " | " + trust + " | " + source + " | " + target + " | " + location + " | " + user + " | " + result + " | ");
        } else {
            this.source.sendMessage(Text.of("MAX DEBUG LIMIT REACHED!", "\n",
                    TextColors.GREEN, "Pasting output..."));
            this.pasteRecords();
            this.records.clear();
            GriefPreventionPlugin.debugActive = false;
            this.source.sendMessage(Text.of(GP_TEXT, TextColors.GRAY, "Debug ", TextColors.RED, "OFF"));
        }
    }

    public CommandSource getSource() {
        return this.source;
    }

    public User getTarget() {
        return this.target;
    }

    public boolean isRecording() {
        return !this.verbose;
    }

    public void setTarget(User user) {
        this.target = user;
    }

    public void setVerbose(boolean verbose) {
        this.verbose = verbose;
    }

    public void pasteRecords() {
        if (this.records.isEmpty()) {
            this.source.sendMessage(Text.of(TextColors.RED, "No debug records to paste!"));
            return;
        }

        final long endTime = System.currentTimeMillis();
        List<String> debugOutput = new ArrayList<>(this.header);
        debugOutput.add("| Record end | " + DATE_FORMAT.format(new Date(endTime)) + "|");
        long elapsed = (endTime - startTime) / 1000L; 
        debugOutput.add("| Time elapsed | " + elapsed + " seconds" + "|");
        debugOutput.add("");
        debugOutput.add("### Output") ;
        debugOutput.add("| Flag | Trust | Source | Target | Location | User | Result |");
        debugOutput.add("|------|-------|--------|--------|----------|------|--------|");

        debugOutput.addAll(this.records);

        String content = String.join("\n", debugOutput);

        String pasteId;
        try {
            pasteId = postContent(content);
        } catch (Exception e) {
            this.source.sendMessage(Text.of(TextColors.RED, "Error uploading content : ", TextColors.WHITE, e.getMessage()));
            return;
        }

        String url = DEBUG_VIEWER_URL + pasteId;

        URL jUrl;
        try {
            jUrl = new URL(url);
        } catch (MalformedURLException e) {
            throw new RuntimeException(e);
        }

        this.source.sendMessage(Text.builder().append(Text.of(TextColors.GREEN, "Paste success! : " + url))
                .onClick(TextActions.openUrl(jUrl)).build());
    }

    private static String postContent(String content) throws IOException {
        ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
        try (GZIPOutputStream writer = new GZIPOutputStream(byteOut)) {
            writer.write(content.getBytes(StandardCharsets.UTF_8));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        RequestBody body = RequestBody.create(PLAIN_TYPE, byteOut.toByteArray());

        Request.Builder requestBuilder = new Request.Builder()
                .url(BYTEBIN_ENDPOINT)
                .header("Content-Encoding", "gzip")
                .post(body);

        Request request = requestBuilder.build();
        try (Response response = HttpClient.makeCall(request)) {
            try (ResponseBody responseBody = response.body()) {
                if (responseBody == null) {
                    throw new RuntimeException("No response");
                }

                try (InputStream inputStream = responseBody.byteStream()) {
                    try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
                        JsonObject object = new Gson().fromJson(reader, JsonObject.class);
                        return object.get("key").getAsString();
                    }
                }
            }
        }
    }
}