package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"strings"

	"github.com/gofrs/uuid/v3"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"

	"github.com/Luzifer/twitch-bot/v3/pkg/twitch"
	"github.com/Luzifer/twitch-bot/v3/plugins"
)

var instanceState = uuid.Must(uuid.NewV4()).String()

func init() {
	for _, rd := range []plugins.HTTPRouteRegistrationArgs{
		{
			Description:  "Updates the bots token for connection to chat and API",
			HandlerFunc:  handleAuthUpdateBotToken,
			Method:       http.MethodGet,
			Module:       "auth",
			Name:         "Update bot token",
			Path:         "/update-bot-token",
			ResponseType: plugins.HTTPRouteResponseTypeTextPlain,
		},
		{
			Description:  "Updates scope configuration for EventSub subscription of a channel",
			HandlerFunc:  handleAuthUpdateChannelGrant,
			Method:       http.MethodGet,
			Module:       "auth",
			Name:         "Update channel scopes",
			Path:         "/update-channel-scopes",
			ResponseType: plugins.HTTPRouteResponseTypeTextPlain,
		},
	} {
		if err := registerRoute(rd); err != nil {
			logrus.WithError(err).Fatal("Unable to register auth routes")
		}
	}
}

func handleAuthUpdateBotToken(w http.ResponseWriter, r *http.Request) {
	var (
		code  = r.FormValue("code")
		state = r.FormValue("state")
	)

	if state != instanceState {
		http.Error(w, "invalid state, please start again", http.StatusBadRequest)
		return
	}

	params := make(url.Values)
	params.Set("client_id", cfg.TwitchClient)
	params.Set("client_secret", cfg.TwitchClientSecret)
	params.Set("code", code)
	params.Set("grant_type", "authorization_code")
	params.Set("redirect_uri", strings.Join([]string{
		strings.TrimRight(cfg.BaseURL, "/"),
		"auth", "update-bot-token",
	}, "/"))

	req, _ := http.NewRequestWithContext(r.Context(), http.MethodPost, fmt.Sprintf("https://id.twitch.tv/oauth2/token?%s", params.Encode()), nil)
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		http.Error(w, errors.Wrap(err, "getting access token").Error(), http.StatusInternalServerError)
		return
	}
	defer func() {
		if err := resp.Body.Close(); err != nil {
			logrus.WithError(err).Error("closing response body (leaked fd)")
		}
	}()

	var rData twitch.OAuthTokenResponse
	if err := json.NewDecoder(resp.Body).Decode(&rData); err != nil {
		http.Error(w, errors.Wrap(err, "decoding access token").Error(), http.StatusInternalServerError)
		return
	}

	_, botUser, err := twitch.New(cfg.TwitchClient, cfg.TwitchClientSecret, rData.AccessToken, "").GetAuthorizedUser(r.Context())
	if err != nil {
		http.Error(w, errors.Wrap(err, "getting authorized user").Error(), http.StatusInternalServerError)
		return
	}

	if err = accessService.SetBotUsername(botUser); err != nil {
		http.Error(w, errors.Wrap(err, "storing bot username").Error(), http.StatusInternalServerError)
		return
	}

	twitchClient.UpdateToken(rData.AccessToken, rData.RefreshToken)

	if err = accessService.SetExtendedTwitchCredentials(botUser, rData.AccessToken, rData.RefreshToken, rData.Scope); err != nil {
		http.Error(w, errors.Wrap(err, "storing access scopes").Error(), http.StatusInternalServerError)
		return
	}

	http.Error(w, fmt.Sprintf("Authorization as %q complete, you can now close this window.", botUser), http.StatusOK)

	frontendNotifyHooks.Ping(frontendNotifyTypeReload) // Tell frontend to update its config
}

func handleAuthUpdateChannelGrant(w http.ResponseWriter, r *http.Request) {
	var (
		code  = r.FormValue("code")
		state = r.FormValue("state")
	)

	if state != instanceState {
		http.Error(w, "invalid state, please start again", http.StatusBadRequest)
		return
	}

	params := make(url.Values)
	params.Set("client_id", cfg.TwitchClient)
	params.Set("client_secret", cfg.TwitchClientSecret)
	params.Set("code", code)
	params.Set("grant_type", "authorization_code")
	params.Set("redirect_uri", strings.Join([]string{
		strings.TrimRight(cfg.BaseURL, "/"),
		"auth", "update-channel-scopes",
	}, "/"))

	req, _ := http.NewRequestWithContext(r.Context(), http.MethodPost, fmt.Sprintf("https://id.twitch.tv/oauth2/token?%s", params.Encode()), nil)
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		http.Error(w, errors.Wrap(err, "getting access token").Error(), http.StatusInternalServerError)
		return
	}
	defer func() {
		if err := resp.Body.Close(); err != nil {
			logrus.WithError(err).Error("closing response body (leaked fd)")
		}
	}()

	var rData twitch.OAuthTokenResponse
	if err := json.NewDecoder(resp.Body).Decode(&rData); err != nil {
		http.Error(w, errors.Wrap(err, "decoding access token").Error(), http.StatusInternalServerError)
		return
	}

	_, grantUser, err := twitch.New(cfg.TwitchClient, cfg.TwitchClientSecret, rData.AccessToken, "").GetAuthorizedUser(r.Context())
	if err != nil {
		http.Error(w, errors.Wrap(err, "getting authorized user").Error(), http.StatusInternalServerError)
		return
	}

	if err = accessService.SetExtendedTwitchCredentials(grantUser, rData.AccessToken, rData.RefreshToken, rData.Scope); err != nil {
		http.Error(w, errors.Wrap(err, "storing access token").Error(), http.StatusInternalServerError)
		return
	}

	http.Error(w, fmt.Sprintf("Scopes for %q updated, you can now close this window.", grantUser), http.StatusOK)

	frontendNotifyHooks.Ping(frontendNotifyTypeReload) // Tell frontend to update its config
}