package oauth2

import (
	"context"
	"fmt"
	"net/http"

	"github.com/influxdata/chronograf"

	"golang.org/x/oauth2"
	goauth2 "google.golang.org/api/oauth2/v2"
	"google.golang.org/api/option"
)

// GoogleEndpoint is Google's OAuth 2.0 endpoint.
// Copied here to remove tons of package dependencies
var GoogleEndpoint = oauth2.Endpoint{
	AuthURL:  "https://accounts.google.com/o/oauth2/auth",
	TokenURL: "https://accounts.google.com/o/oauth2/token",
}
var _ Provider = &Google{}

// Google is an oauth2 provider supporting google.
type Google struct {
	ClientID     string
	ClientSecret string
	RedirectURL  string
	Domains      []string // Optional google email domain checking
	Logger       chronograf.Logger
}

// Name is the name of the provider
func (g *Google) Name() string {
	return "google"
}

// ID returns the google application client id
func (g *Google) ID() string {
	return g.ClientID
}

// Secret returns the google application client secret
func (g *Google) Secret() string {
	return g.ClientSecret
}

// Scopes for google is only the email address
// Documentation is here: https://developers.google.com/+/web/api/rest/oauth#email
func (g *Google) Scopes() []string {
	return []string{
		goauth2.UserinfoEmailScope,
		goauth2.UserinfoProfileScope,
	}
}

// Config is the Google OAuth2 exchange information and endpoints
func (g *Google) Config() *oauth2.Config {
	return &oauth2.Config{
		ClientID:     g.ID(),
		ClientSecret: g.Secret(),
		Scopes:       g.Scopes(),
		Endpoint:     GoogleEndpoint,
		RedirectURL:  g.RedirectURL,
	}
}

// PrincipalID returns the google email address of the user.
func (g *Google) PrincipalID(provider *http.Client) (string, error) {
	srv, err := goauth2.NewService(context.TODO(), option.WithHTTPClient(provider))
	if err != nil {
		g.Logger.Error("Unable to communicate with Google ", err.Error())
		return "", err
	}
	info, err := srv.Userinfo.Get().Do()
	if err != nil {
		g.Logger.Error("Unable to retrieve Google email ", err.Error())
		return "", err
	}
	// No domain filtering required, so, the user is autenticated.
	if len(g.Domains) == 0 {
		return info.Email, nil
	}

	// Check if the account domain is acceptable
	for _, requiredDomain := range g.Domains {
		if info.Hd == requiredDomain {
			return info.Email, nil
		}
	}
	g.Logger.Error("Domain '", info.Hd, "' is not a member of required Google domain(s): ", g.Domains)
	return "", fmt.Errorf("Not in required domain")
}

// Group returns the string of domain a user belongs to in Google
func (g *Google) Group(provider *http.Client) (string, error) {
	srv, err := goauth2.NewService(context.TODO(), option.WithHTTPClient(provider))
	if err != nil {
		g.Logger.Error("Unable to communicate with Google ", err.Error())
		return "", err
	}
	info, err := srv.Userinfo.Get().Do()
	if err != nil {
		g.Logger.Error("Unable to retrieve Google email ", err.Error())
		return "", err
	}

	return info.Hd, nil
}
