package provider

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"strings"
	"time"

	"github.com/supabase/auth/internal/conf"
	"github.com/supabase/auth/internal/utilities"
	"golang.org/x/oauth2"
)

// Twitch

const (
	defaultTwitchAuthBase = "id.twitch.tv"
	defaultTwitchAPIBase  = "api.twitch.tv"
)

type twitchProvider struct {
	*oauth2.Config
	APIHost string
}

type twitchUsers struct {
	Data []struct {
		ID              string    `json:"id"`
		Login           string    `json:"login"`
		DisplayName     string    `json:"display_name"`
		Type            string    `json:"type"`
		BroadcasterType string    `json:"broadcaster_type"`
		Description     string    `json:"description"`
		ProfileImageURL string    `json:"profile_image_url"`
		OfflineImageURL string    `json:"offline_image_url"`
		ViewCount       int       `json:"view_count"`
		Email           string    `json:"email"`
		CreatedAt       time.Time `json:"created_at"`
	} `json:"data"`
}

// NewTwitchProvider creates a Twitch account provider.
func NewTwitchProvider(ext conf.OAuthProviderConfiguration, scopes string) (OAuthProvider, error) {
	if err := ext.ValidateOAuth(); err != nil {
		return nil, err
	}

	apiHost := chooseHost(ext.URL, defaultTwitchAPIBase)
	authHost := chooseHost(ext.URL, defaultTwitchAuthBase)

	oauthScopes := []string{
		"user:read:email",
	}

	if scopes != "" {
		oauthScopes = append(oauthScopes, strings.Split(scopes, ",")...)
	}

	return &twitchProvider{
		Config: &oauth2.Config{
			ClientID:     ext.ClientID[0],
			ClientSecret: ext.Secret,
			Endpoint: oauth2.Endpoint{
				AuthURL:  authHost + "/oauth2/authorize",
				TokenURL: authHost + "/oauth2/token",
			},
			RedirectURL: ext.RedirectURI,
			Scopes:      oauthScopes,
		},
		APIHost: apiHost,
	}, nil
}

func (t twitchProvider) GetOAuthToken(code string) (*oauth2.Token, error) {
	return t.Exchange(context.Background(), code)
}

func (t twitchProvider) GetUserData(ctx context.Context, tok *oauth2.Token) (*UserProvidedData, error) {
	var u twitchUsers

	// Perform http request, because we neeed to set the Client-Id header
	req, err := http.NewRequest("GET", t.APIHost+"/helix/users", nil)

	if err != nil {
		return nil, err
	}

	// set headers
	req.Header.Set("Client-Id", t.Config.ClientID)
	req.Header.Set("Authorization", "Bearer "+tok.AccessToken)

	client := &http.Client{Timeout: defaultTimeout}
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer utilities.SafeClose(resp.Body)

	if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
		return nil, fmt.Errorf("a %v error occurred with retrieving user from twitch", resp.StatusCode)
	}

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	err = json.Unmarshal(body, &u)
	if err != nil {
		return nil, err
	}

	if len(u.Data) == 0 {
		return nil, errors.New("unable to find user with twitch provider")
	}

	user := u.Data[0]

	data := &UserProvidedData{}
	if user.Email != "" {
		data.Emails = []Email{{
			Email:    user.Email,
			Verified: true,
			Primary:  true,
		}}
	}

	data.Metadata = &Claims{
		Issuer:   t.APIHost,
		Subject:  user.ID,
		Picture:  user.ProfileImageURL,
		Name:     user.Login,
		NickName: user.DisplayName,
		CustomClaims: map[string]interface{}{
			"broadcaster_type":  user.BroadcasterType,
			"description":       user.Description,
			"type":              user.Type,
			"offline_image_url": user.OfflineImageURL,
			"view_count":        user.ViewCount,
		},

		// To be deprecated
		Slug:       user.DisplayName,
		AvatarURL:  user.ProfileImageURL,
		FullName:   user.Login,
		ProviderId: user.ID,
	}

	return data, nil
}
