package provider

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"

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

const (
	defaultNotionApiBase = "api.notion.com"
	notionApiVersion     = "2021-08-16"
)

type notionProvider struct {
	*oauth2.Config
	APIPath string
}

type notionUser struct {
	Bot struct {
		Owner struct {
			User struct {
				ID        string `json:"id"`
				Name      string `json:"name"`
				AvatarURL string `json:"avatar_url"`
				Person    struct {
					Email string `json:"email"`
				} `json:"person"`
			} `json:"user"`
		} `json:"owner"`
	} `json:"bot"`
}

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

	authHost := chooseHost(ext.URL, defaultNotionApiBase)

	return &notionProvider{
		Config: &oauth2.Config{
			ClientID:     ext.ClientID[0],
			ClientSecret: ext.Secret,
			Endpoint: oauth2.Endpoint{
				AuthURL:  authHost + "/v1/oauth/authorize",
				TokenURL: authHost + "/v1/oauth/token",
			},
			RedirectURL: ext.RedirectURI,
		},
		APIPath: authHost,
	}, nil
}

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

func (g notionProvider) GetUserData(ctx context.Context, tok *oauth2.Token) (*UserProvidedData, error) {
	var u notionUser

	// Perform http request, because we need to set the Notion-Version header
	req, err := http.NewRequest("GET", g.APIPath+"/v1/users/me", nil)

	if err != nil {
		return nil, err
	}

	// set headers
	req.Header.Set("Notion-Version", notionApiVersion)
	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 notion", 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
	}

	data := &UserProvidedData{}
	if u.Bot.Owner.User.Person.Email != "" {
		data.Emails = []Email{{
			Email:    u.Bot.Owner.User.Person.Email,
			Verified: true, // Notion dosen't provide data on if email is verified.
			Primary:  true,
		}}
	}

	data.Metadata = &Claims{
		Issuer:  g.APIPath,
		Subject: u.Bot.Owner.User.ID,
		Name:    u.Bot.Owner.User.Name,
		Picture: u.Bot.Owner.User.AvatarURL,

		// To be deprecated
		AvatarURL:  u.Bot.Owner.User.AvatarURL,
		FullName:   u.Bot.Owner.User.Name,
		ProviderId: u.Bot.Owner.User.ID,
	}
	return data, nil
}
