package dev.conorthedev.mediamod.media.spotify;

import com.google.gson.annotations.SerializedName;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import dev.conorthedev.mediamod.MediaMod;
import dev.conorthedev.mediamod.config.Settings;
import dev.conorthedev.mediamod.media.base.AbstractMediaHandler;
import dev.conorthedev.mediamod.media.base.exception.HandlerInitializationException;
import dev.conorthedev.mediamod.media.spotify.api.SpotifyAPI;
import dev.conorthedev.mediamod.media.spotify.api.playing.CurrentlyPlayingObject;
import dev.conorthedev.mediamod.util.*;
import net.minecraft.event.ClickEvent;
import net.minecraft.event.HoverEvent;
import net.minecraft.util.ChatComponentText;
import net.minecraft.util.IChatComponent;
import net.minecraftforge.fml.client.FMLClientHandler;

import java.awt.*;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * The main class for all Spotify-related things
 */
public class SpotifyHandler extends AbstractMediaHandler {
    public static final SpotifyHandler INSTANCE = new SpotifyHandler();
    public static SpotifyAPI spotifyApi = null;
    public static boolean logged = false;
    private static HttpServer server = null;
    private boolean hasListenedToSong = false;

    private static void handleRequest(String code) {
        ScheduledExecutorService ses = Executors.newSingleThreadScheduledExecutor();
        ses.scheduleAtFixedRate(() -> {
            if (logged) {
                INSTANCE.refreshSpotify();
            }
        }, 59, 59, TimeUnit.MINUTES);

        PlayerMessager.sendMessage("&7Exchanging authorization code for access token, this may take a moment...");
        try {
            TokenAPIResponse tokenAPIResponse = WebRequest.requestToMediaModAPI(WebRequestType.GET, "spotify/getToken?code=" + code, TokenAPIResponse.class);
            if (tokenAPIResponse == null) {
                MediaMod.INSTANCE.LOGGER.error("Error: tokenAPIResponse is null");
                PlayerMessager.sendMessage("&c&lERROR: &rFailed to login to Spotify!");
                return;
            }

            spotifyApi = new SpotifyAPI(tokenAPIResponse.accessToken, tokenAPIResponse.refreshToken);

            if (spotifyApi.getRefreshToken() != null) {
                logged = true;
                Settings.REFRESH_TOKEN = spotifyApi.getRefreshToken();
                Settings.saveConfig();
                PlayerMessager.sendMessage("&a&lSUCCESS! &rLogged into Spotify!");
                CurrentlyPlayingObject currentTrack = spotifyApi.getCurrentTrack();

                if (MediaMod.INSTANCE.DEVELOPMENT_ENVIRONMENT && currentTrack != null) {
                    PlayerMessager.sendMessage("&8&lDEBUG: &rCurrent Song: " + currentTrack.item.name + " by " + currentTrack.item.album.artists[0].name);
                }
            }
        } catch (Exception e) {
            logged = false;
            MediaMod.INSTANCE.LOGGER.error("Error: " + e.getMessage());
            PlayerMessager.sendMessage("&c&lERROR: &rFailed to login to Spotify!");
        }
    }

    public void connectSpotify() {
        attemptToOpenAuthURL();
    }

    private void attemptToOpenAuthURL() {
        try {
            if (server == null) {
                initializeHandler();
            }
        } catch (HandlerInitializationException e) {
            e.printStackTrace();
        }

        Desktop desktop = Desktop.getDesktop();
        String spotifyUrl = "https://accounts.spotify.com/authorize?client_id=2892e1e967084cc2b9cbba8fb90c7e56&response_type=code&redirect_uri=http%3A%2F%2Flocalhost:9103%2Fcallback%2F&scope=user-read-playback-state%20user-read-currently-playing%20user-modify-playback-state&state=34fFs29kd09";
        try {
            desktop.browse(new URI(spotifyUrl));
        } catch (URISyntaxException e) {
            MediaMod.INSTANCE.LOGGER.fatal("Something has gone terribly wrong... SpotifyHandler:l59");
            e.printStackTrace();
        } catch (Exception e) {
            PlayerMessager.sendMessage("&cFailed to open browser with the Spotify Auth URL!");
            IChatComponent urlComponent = new ChatComponentText(ChatColor.translateAlternateColorCodes('&', "&lOpen URL"));
            urlComponent.getChatStyle().setChatClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, spotifyUrl));
            urlComponent.getChatStyle().setChatHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ChatComponentText(ChatColor.translateAlternateColorCodes('&',
                    "&7Click this to open the Spotify Auth URL"))));
            PlayerMessager.sendMessage(urlComponent);
        }
    }

    private void refreshSpotify() {
        if (spotifyApi.getRefreshToken() != null) {
            if (FMLClientHandler.instance().getClient().thePlayer != null) {
                PlayerMessager.sendMessage("&8INFO: &9Attempting to refresh access token...");
            } else {
                MediaMod.INSTANCE.LOGGER.info("Attempting to refresh access token...");
            }

            try {
                RefreshResponse refreshResponse = WebRequest.requestToMediaModAPI(WebRequestType.GET, "spotify/refreshToken?token=" + spotifyApi.getRefreshToken(), RefreshResponse.class);

                if (refreshResponse == null) {
                    MediaMod.INSTANCE.LOGGER.error("Error: tokenAPIResponse is null");
                    PlayerMessager.sendMessage("&8&lDEBUG: &rFailed to login to Spotify!");
                    return;
                }

                spotifyApi = new SpotifyAPI(refreshResponse.accessToken, spotifyApi.getRefreshToken());

                if (spotifyApi.getRefreshToken() != null) {
                    logged = true;
                    Settings.REFRESH_TOKEN = spotifyApi.getRefreshToken();
                    Settings.saveConfig();
                    PlayerMessager.sendMessage("&a&lSUCCESS! &rLogged into Spotify!");

                    CurrentlyPlayingObject currentTrack = spotifyApi.getCurrentTrack();

                    if (MediaMod.INSTANCE.DEVELOPMENT_ENVIRONMENT && currentTrack != null) {
                        PlayerMessager.sendMessage("&8&lDEBUG: &rCurrent Song: " + currentTrack.item.name + " by " + currentTrack.item.album.artists[0].name);
                    }
                }
            } catch (Exception e) {
                logged = false;
                MediaMod.INSTANCE.LOGGER.error("Error: " + e.getMessage());
                PlayerMessager.sendMessage("&8&lDEBUG: &rFailed to login to Spotify!");
            }
        }
    }

    @Override
    public String getHandlerName() {
        return "Spotify Handler";
    }

    @Override
    public CurrentlyPlayingObject getCurrentTrack() {
        try {
            CurrentlyPlayingObject object = spotifyApi.getCurrentTrack();
            lastProgressUpdate = System.currentTimeMillis();

            if (object != null) {
                lastProgressMs = object.progress_ms;
                paused = !object.is_playing;
                durationMs = object.item.duration_ms;
                hasListenedToSong = true;
            } else {
                durationMs = 0;
                lastProgressMs = 0;
                if (hasListenedToSong) paused = true;
            }

            return object;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public void initializeHandler() throws HandlerInitializationException {
        if (!Settings.REFRESH_TOKEN.isEmpty()) {
            logged = true;
            spotifyApi = new SpotifyAPI(null, Settings.REFRESH_TOKEN);
            refreshSpotify();
        }

        try {
            server = HttpServer.create(new InetSocketAddress(9103), 0);
        } catch (IOException e) {
            throw new HandlerInitializationException(e);
        }

        server.createContext("/callback", new SpotifyCallbackHandler());
        server.setExecutor(null);
        server.start();
    }

    @Override
    public boolean handlerReady() {
        return logged;
    }

    @Override
    public boolean supportsSkipping() {
        return true;
    }

    @Override
    public boolean supportsPausing() {
        return true;
    }

    @Override
    public boolean skipTrack() {
        return spotifyApi.skipTrack();
    }

    @Override
    public boolean pausePlayTrack() {
        return spotifyApi.pausePlayTrack();
    }

    private static class SpotifyCallbackHandler implements HttpHandler {
        @Override
        public void handle(HttpExchange t) throws IOException {
            Multithreading.runAsync(() -> handleRequest(t.getRequestURI().toString().replace("/callback/?code=", "").substring(0, t.getRequestURI().toString().replace("/callback/?code=", "").length() - 18)));

            String response = "<!DOCTYPE html>\n" +
                    "<html>\n" +
                    "  <head>\n" +
                    "    <meta charset=\"utf-8\">\n" +
                    "    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n" +
                    "    <title>MediaMod</title>\n" +
                    "    <link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.5/css/bulma.min.css\">\n" +
                    "    <script defer src=\"https://use.fontawesome.com/releases/v5.3.1/js/all.js\"></script>\n" +
                    "  </head>\n" +
                    "  <body class=\"hero is-dark is-fullheight\">\n" +
                    "  <section class=\"section has-text-centered\">\n" +
                    "    <div class=\"container\">\n" +
                    "      <img src=\"https://raw.githubusercontent.com/MediaModMC/MediaMod/master/src/main/resources/assets/mediamod/header.png\" width=\"400px\">" + "\n" +
                    "      <h1 class=\"title\">\n" +
                    "        Success!\n" +
                    "      </h1>\n" +
                    "      <p class=\"subtitle\">\n" +
                    "        Please close this window and go back into Minecraft!\n" +
                    "      </p>\n" +
                    "    </div>\n" +
                    "  </section>\n" +
                    "  </body>\n" +
                    "</html>";

            t.sendResponseHeaders(200, response.length());

            OutputStream os = t.getResponseBody();
            os.write(response.getBytes());
            os.close();
        }
    }

    private static class TokenAPIResponse {
        @SerializedName("access_token")
        final String accessToken;
        @SerializedName("expires_in")
        final int expiresIn;
        @SerializedName("refresh_token")
        final String refreshToken;

        TokenAPIResponse(String access_token, int expires_in, String refresh_token) {
            this.accessToken = access_token;
            this.expiresIn = expires_in;
            this.refreshToken = refresh_token;
        }
    }

    private static class RefreshResponse {
        @SerializedName("access_token")
        final String accessToken;
        @SerializedName("token_type")
        final String tokenType;
        @SerializedName("expires_in")
        final int expiresIn;
        final String scope;

        RefreshResponse(String access_token, int expires_in, String token_type, String scope) {
            this.accessToken = access_token;
            this.expiresIn = expires_in;
            this.tokenType = token_type;
            this.scope = scope;
        }
    }
}