// Copyright 2021 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package idp

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strconv"
	"time"

	"golang.org/x/oauth2"
)

type GitlabIdProvider struct {
	Client *http.Client
	Config *oauth2.Config
}

func NewGitlabIdProvider(clientId string, clientSecret string, redirectUrl string) *GitlabIdProvider {
	idp := &GitlabIdProvider{}

	config := idp.getConfig(clientId, clientSecret, redirectUrl)
	idp.Config = config

	return idp
}

func (idp *GitlabIdProvider) SetHttpClient(client *http.Client) {
	idp.Client = client
}

// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
func (idp *GitlabIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
	endpoint := oauth2.Endpoint{
		TokenURL: "https://gitlab.com/oauth/token",
	}

	config := &oauth2.Config{
		Scopes:       []string{"read_user+profile"},
		Endpoint:     endpoint,
		ClientID:     clientId,
		ClientSecret: clientSecret,
		RedirectURL:  redirectUrl,
	}

	return config
}

type GitlabProviderToken struct {
	AccessToken  string `json:"access_token"`
	TokenType    string `json:"token_type"`
	ExpiresIn    int    `json:"expires_in"`
	RefreshToken string `json:"refresh_token"`
	CreatedAt    int    `json:"created_at"`
}

// GetToken use code get access_token (*operation of getting code ought to be done in front)
// get more detail via: https://docs.gitlab.com/ee/api/oauth2.html
func (idp *GitlabIdProvider) GetToken(code string) (*oauth2.Token, error) {
	params := url.Values{}
	params.Add("grant_type", "authorization_code")
	params.Add("client_id", idp.Config.ClientID)
	params.Add("client_secret", idp.Config.ClientSecret)
	params.Add("code", code)
	params.Add("redirect_uri", idp.Config.RedirectURL)

	accessTokenUrl := fmt.Sprintf("%s?%s", idp.Config.Endpoint.TokenURL, params.Encode())
	resp, err := idp.Client.Post(accessTokenUrl, "application/json;charset=UTF-8", nil)
	if err != nil {
		return nil, err
	}

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

	gtoken := &GitlabProviderToken{}
	if err = json.Unmarshal(data, gtoken); err != nil {
		return nil, err
	}

	// gtoken.ExpiresIn always returns 0, so we set Expiry=7200 to avoid verification errors.
	token := &oauth2.Token{
		AccessToken:  gtoken.AccessToken,
		TokenType:    gtoken.TokenType,
		RefreshToken: gtoken.RefreshToken,
		Expiry:       time.Unix(time.Now().Unix()+int64(7200), 0),
	}

	return token, nil
}

/*
{
   "id":5162115,
   "name":"shiluo",
   "username":"shiluo",
   "state":"active",
   "avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/5162115/avatar.png",
   "web_url":"https://gitlab.com/shiluo",
   "created_at":"2019-12-23T02:50:10.348Z",
   "bio":"",
   "bio_html":"",
   "location":"China",
   "public_email":"silo1999@163.com",
   "skype":"",
   "linkedin":"",
   "twitter":"",
   "website_url":"",
   "organization":"",
   "job_title":"",
   "pronouns":null,
   "bot":false,
   "work_information":null,
   "followers":0,
   "following":0,
   "last_sign_in_at":"2019-12-26T13:24:42.941Z",
   "confirmed_at":"2019-12-23T02:52:10.778Z",
   "last_activity_on":"2021-08-19",
   "email":"silo1999@163.com",
   "theme_id":1,
   "color_scheme_id":1,
   "projects_limit":100000,
   "current_sign_in_at":"2021-08-19T09:46:46.004Z",
   "identities":[
      {
         "provider":"github",
         "extern_uid":"51157931",
         "saml_provider_id":null
      }
   ],
   "can_create_group":true,
   "can_create_project":true,
   "two_factor_enabled":false,
   "external":false,
   "private_profile":false,
   "commit_email":"silo1999@163.com",
   "shared_runners_minutes_limit":null,
   "extra_shared_runners_minutes_limit":null
}
*/

type GitlabUserInfo struct {
	Id              int         `json:"id"`
	Name            string      `json:"name"`
	Username        string      `json:"username"`
	State           string      `json:"state"`
	AvatarUrl       string      `json:"avatar_url"`
	WebUrl          string      `json:"web_url"`
	CreatedAt       time.Time   `json:"created_at"`
	Bio             string      `json:"bio"`
	BioHtml         string      `json:"bio_html"`
	Location        string      `json:"location"`
	PublicEmail     string      `json:"public_email"`
	Skype           string      `json:"skype"`
	Linkedin        string      `json:"linkedin"`
	Twitter         string      `json:"twitter"`
	WebsiteUrl      string      `json:"website_url"`
	Organization    string      `json:"organization"`
	JobTitle        string      `json:"job_title"`
	Pronouns        interface{} `json:"pronouns"`
	Bot             bool        `json:"bot"`
	WorkInformation interface{} `json:"work_information"`
	Followers       int         `json:"followers"`
	Following       int         `json:"following"`
	LastSignInAt    time.Time   `json:"last_sign_in_at"`
	ConfirmedAt     time.Time   `json:"confirmed_at"`
	LastActivityOn  string      `json:"last_activity_on"`
	Email           string      `json:"email"`
	ThemeId         int         `json:"theme_id"`
	ColorSchemeId   int         `json:"color_scheme_id"`
	ProjectsLimit   int         `json:"projects_limit"`
	CurrentSignInAt time.Time   `json:"current_sign_in_at"`
	Identities      []struct {
		Provider       string      `json:"provider"`
		ExternUid      string      `json:"extern_uid"`
		SamlProviderId interface{} `json:"saml_provider_id"`
	} `json:"identities"`
	CanCreateGroup                 bool        `json:"can_create_group"`
	CanCreateProject               bool        `json:"can_create_project"`
	TwoFactorEnabled               bool        `json:"two_factor_enabled"`
	External                       bool        `json:"external"`
	PrivateProfile                 bool        `json:"private_profile"`
	CommitEmail                    string      `json:"commit_email"`
	SharedRunnersMinutesLimit      interface{} `json:"shared_runners_minutes_limit"`
	ExtraSharedRunnersMinutesLimit interface{} `json:"extra_shared_runners_minutes_limit"`
}

// GetUserInfo use GitlabProviderToken gotten before return GitlabUserInfo
func (idp *GitlabIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
	resp, err := idp.Client.Get("https://gitlab.com/api/v4/user?access_token=" + token.AccessToken)
	if err != nil {
		return nil, err
	}

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

	guser := GitlabUserInfo{}
	if err = json.Unmarshal(data, &guser); err != nil {
		return nil, err
	}

	userInfo := UserInfo{
		Id:          strconv.Itoa(guser.Id),
		Username:    guser.Username,
		DisplayName: guser.Name,
		AvatarUrl:   guser.AvatarUrl,
		Email:       guser.Email,
	}
	return &userInfo, nil
}
