package oauth

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

	"golang.org/x/oauth2"

	"github.com/chaitin/panda-wiki/consts"
	"github.com/chaitin/panda-wiki/log"
)

const (
	githubAuthorizeURL    = "https://github.com/login/oauth/authorize"
	githubTokenURL        = "https://github.com/login/oauth/access_token"
	githubUserInfoURL     = "https://api.github.com/user"
	githubUserEmailURL    = "https://api.github.com/user/emails"
	githubCallbackPathPro = "/share/pro/v1/openapi/github/callback"
	githubCallbackPath    = "/share/v1/openapi/github/callback"
)

func NewGithubClient(ctx context.Context, logger *log.Logger, clientID, clientSecret, redirectURI, proxyURL string) (*Client, error) {
	licenseEdition, ok := ctx.Value(consts.ContextKeyEdition).(consts.LicenseEdition)
	if !ok {
		return nil, fmt.Errorf("failed to retrieve license edition from context")
	}

	redirectURL, _ := url.Parse(redirectURI)
	redirectURL.Path = githubCallbackPath

	if licenseEdition > consts.LicenseEditionFree {
		redirectURL.Path = githubCallbackPathPro
	}

	redirectURI = redirectURL.String()

	var httpClient *http.Client
	if proxyURL != "" {
		proxyURLParsed, err := url.Parse(proxyURL)
		if err != nil {
			return nil, fmt.Errorf("invalid proxy URL: %w", err)
		}

		httpClient = &http.Client{
			Transport: &http.Transport{
				Proxy: http.ProxyURL(proxyURLParsed),
			},
		}
		logger.Info("GitHub OAuth client configured with proxy", log.String("proxy", proxyURL))
	} else {
		httpClient = http.DefaultClient
	}

	config := Config{
		ClientID:     clientID,
		ClientSecret: clientSecret,
		Scopes:       []string{"user:email"},
		AuthorizeURL: githubAuthorizeURL,
		TokenURL:     githubTokenURL,
		UserInfoURL:  githubUserInfoURL,
		IDField:      "id",
		NameField:    "login",
		AvatarField:  "avatar_url",
		EmailField:   "email",
		RedirectURI:  redirectURI,
	}

	oauthConfig := &oauth2.Config{
		ClientID:     config.ClientID,
		ClientSecret: config.ClientSecret,
		Endpoint: oauth2.Endpoint{
			AuthURL:  config.AuthorizeURL,
			TokenURL: config.TokenURL,
		},
		RedirectURL: redirectURI,
		Scopes:      config.Scopes,
	}

	if proxyURL != "" {
		ctx = context.WithValue(ctx, oauth2.HTTPClient, httpClient)
	}

	return &Client{
		ctx:        ctx,
		logger:     logger.WithModule("pkg.oauth"),
		oauth:      oauthConfig,
		httpClient: httpClient,
		config:     &config,
	}, nil
}

func (c *Client) GetGithubPrimaryEmail(token *oauth2.Token) (string, error) {
	var client *http.Client
	if c.httpClient != nil {
		ctx := context.WithValue(c.ctx, oauth2.HTTPClient, c.httpClient)
		client = c.oauth.Client(ctx, token)
	} else {
		client = c.oauth.Client(c.ctx, token)
	}

	type Email struct {
		Email    string `json:"email"`
		Primary  bool   `json:"primary"`
		Verified bool   `json:"verified"`
	}

	resp, err := client.Get(githubUserEmailURL)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

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

	c.logger.Info("GetGithubPrimaryEmail:", log.Any("buf", string(buf)))

	var emails []Email
	if err := json.Unmarshal(buf, &emails); err != nil {
		return "", err
	}

	for _, email := range emails {
		if email.Primary && email.Verified {
			return email.Email, nil
		}
	}

	return "", errors.New("no primary verified email found")
}
