package middleware

import (
	"crypto/rand"
	"fmt"
	"math/big"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/pingcap-inc/pchub/pkg/util/ginres"
	"github.com/spf13/viper"

	"github.com/gin-contrib/sessions"
	"github.com/gin-gonic/gin"

	"github.com/pingcap-inc/data-ingestion-tools/internal/pkg/request"
	"github.com/pingcap-inc/data-ingestion-tools/pkg/errors"
	"github.com/pingcap-inc/data-ingestion-tools/pkg/log"
)

func Authorize(c *gin.Context) {
	if strings.HasPrefix(c.Request.URL.Path, "/account") {
		c.Next()
		return
	}
	if strings.HasPrefix(c.Request.URL.Path, "/check_health") {
		c.Next()
		return
	}
	loginAt := sessions.Default(c).Get("login_at")
	if loginAt == nil || time.Since(time.Unix(loginAt.(int64), 0)) > time.Hour*24 {
		c.Redirect(http.StatusTemporaryRedirect, "/account/login")
		c.Abort()
		return
	}

	c.Next()
}

func Login(c *gin.Context) {
	state, _ := generateRandomString(10)
	c.Redirect(http.StatusTemporaryRedirect,
		fmt.Sprintf("%s/auth?%s", viper.GetString("authing.app_provider_uri"), url.Values{
			"response_type": {"code"},
			"client_id":     {viper.GetString("authing.app_id")},
			"redirect_uri":  {viper.GetString("authing.app_redirect_uri")},
			"scope":         {"openid profile offline_access phone email"},
			"state":         {state},
		}.Encode()))
}

func Callback(c *gin.Context) {
	resp, err := request.Post(c,
		fmt.Sprintf("%s/token", viper.GetString("authing.app_provider_uri")),
		request.MapForm{
			"code":          c.Query("code"),
			"grant_type":    "authorization_code",
			"client_id":     viper.GetString("authing.app_id"),
			"client_secret": viper.GetString("authing.app_secret"),
			"redirect_uri":  viper.GetString("authing.app_redirect_uri"),
		},
		request.Headers{
			"Content-Type": "application/x-www-form-urlencoded",
		})

	if resp != nil && resp.Response.StatusCode >= http.StatusBadRequest {
		err = fmt.Errorf("%s %s", resp.Response.Status, resp.String())
	}
	if err != nil {
		log.L(c).Errorf("get authing token err %s", err)

		ginres.WriteResponse(c, errors.New("get authing token err"), nil)
		return
	}

	info := &struct {
		IDToken      string `json:"id_token"`
		AccessToken  string `json:"access_token"`
		RefreshToken string `json:"refresh_token"`
	}{}
	err = resp.ToJSON(info)

	if err != nil {
		ginres.WriteResponse(c, errors.New("get to json err"), nil)

		return
	}

	resp, err = request.Get(c,
		fmt.Sprintf("%s/me?access_token=%s", viper.GetString("authing.app_provider_uri"), info.AccessToken),
	)

	if resp != nil && resp.Response.StatusCode >= http.StatusBadRequest {
		err = fmt.Errorf("%s %s", resp.Response.Status, resp.String())
	}
	if err != nil {
		log.L(c).Errorf("get authing user info err %s", err)
		ginres.WriteResponse(c, errors.New("get authing user info err"), nil)

		return
	}

	user := &struct {
		Sub   string `json:"sub"`
		Name  string `json:"name"`
		Email string `json:"email"`
		Phone string `json:"phone"`
	}{}

	err = resp.ToJSON(user)
	if err != nil {
		log.L(c).Errorf("get to json err %s", err)
		ginres.WriteResponse(c, errors.New("get to json err"), nil)

		return
	}

	session := sessions.Default(c)
	session.Clear()
	session.Set("name", user.Name)
	session.Set("email", user.Email)
	session.Set("phone", user.Phone)
	session.Set("user_id", user.Sub)
	session.Set("login_at", time.Now().Unix())
	err = session.Save()
	if err != nil {
		log.L(c).Errorf("save session err %s", err)
		ginres.WriteResponse(c, errors.New("save session err"), nil)

		return
	}

	c.Redirect(http.StatusTemporaryRedirect, "/")
}

func Logout(c *gin.Context) {
	session := sessions.Default(c)
	session.Clear()
	err := session.Save()
	if err != nil {
		log.L(c).Errorf("save session err %s", err)
		ginres.WriteResponse(c, errors.New("save session err"), nil)

		return
	}

	c.Redirect(http.StatusTemporaryRedirect, viper.GetString("authing.app_logout_uri"))
}

func Profile(c *gin.Context) {
	session := sessions.Default(c)

	c.JSON(http.StatusOK, gin.H{
		"name":     session.Get("name"),
		"login_at": session.Get("login_at"),
	})
}

func generateRandomString(length int) (string, error) {
	const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
	result := make([]byte, length)

	for i := 0; i < length; i++ {
		// rand.Int返回[0, n)的随机数
		num, err := rand.Int(rand.Reader, big.NewInt(int64(len(letters))))
		if err != nil {
			return "", err
		}
		result[i] = letters[num.Int64()]
	}

	return string(result), nil
}

func GetUserInfo(c *gin.Context) {

	session := sessions.Default(c)
	userId := session.Get("user_id")
	nameCn := session.Get("name")
	email := session.Get("email")
	name := strings.Split(email.(string), "@")[0]

	data := map[string]interface{}{
		"user": map[string]interface{}{
			"name":    name,
			"name_cn": nameCn,
			"email":   email,
			"user_id": userId,
		},
	}

	log.L(c).Infof("get user info %s", name)

	ginres.WriteResponse(c, nil, data)
}
