package auth

import (
	"context"
	"fmt"
	"regexp"
	"strconv"

	"github.com/form3tech-oss/jwt-go"
	"github.com/pingcap/errcode"
	"github.com/tidbcloud/core-portal/internal/central/db"
	"github.com/tidbcloud/core-portal/internal/central/db/dbutil"
	entity "github.com/tidbcloud/core-svc/model"
	"github.com/tidbcloud/pkgv2/auth/auth0"
	"gorm.io/gorm"
)

func NewGetClaim() auth0.GetClaim {
	return getClaim
}

func getClaim(ctx context.Context, claims jwt.MapClaims) (interface{}, error) {
	email, err := auth0.GetEmailClaim(claims)
	if err != nil {
		return nil, errcode.NewNotAuthenticatedErr(err)
	}

	subClaim, err := auth0.GetSubjectClaim(claims)
	if err != nil {
		return nil, errcode.NewNotAuthenticatedErr(err)
	}

	httpURI := ctx.Value(auth0.AuthHttpURI).(string)

	var orgID entity.ID
	if IsOldTiDBDashboardPath(httpURI) {
		orgID, err = getOrgIDFromOldPath(httpURI)
		if err != nil {
			return nil, errcode.NewNotAuthenticatedErr(err)
		}
	} else {
		orgID, _, err = ParseOrgIDAndClusterID(httpURI)
		if err != nil {
			return nil, errcode.NewInvalidInputErr(err)
		}
	}

	return GetUser(ctx, email, subClaim, orgID.Uint64())
}

var (
	BaseProxyAPIPrefix             = `^orgs/(\d+)/clusters/(\d+)/`
	TiDBDashboardProxyAPIPrefix    = fmt.Sprintf("%spd/dashboard/", BaseProxyAPIPrefix)
	OldTiDBDashboardProxyAPIPrefix = `^cluster/(\d+)/pd/dashboard/`
)

var (
	RegexpBaseProxyAPI             = regexp.MustCompile(BaseProxyAPIPrefix)
	RegexpTiDBDashboardProxyAPI    = regexp.MustCompile(TiDBDashboardProxyAPIPrefix)
	RegexpOldTiDBDashboardProxyAPI = regexp.MustCompile(OldTiDBDashboardProxyAPIPrefix)
)

func ParseOrgIDAndClusterID(urlPath string) (orgID entity.ID, clusterID entity.ID, err error) {
	matches := RegexpBaseProxyAPI.FindStringSubmatch(urlPath)
	if matches == nil {
		return 0, 0, fmt.Errorf("invalid proxy URL: %s", urlPath)
	}

	orgIDUint64, err := strconv.ParseUint(matches[1], 10, 64)
	if err != nil {
		return 0, 0, fmt.Errorf("invalid proxy URL: %s", urlPath)
	}

	clusterIDUint64, err := strconv.ParseUint(matches[2], 10, 64)
	if err != nil {
		return 0, 0, fmt.Errorf("invalid proxy URL: %s", urlPath)
	}

	orgID = entity.ID(orgIDUint64)
	clusterID = entity.ID(clusterIDUint64)
	return
}

func parseClusterIDFromOldPath(urlPath string) (clusterID entity.ID, err error) {
	matches := RegexpOldTiDBDashboardProxyAPI.FindStringSubmatch(urlPath)
	if matches == nil {
		return 0, fmt.Errorf("invalid old tidb dashboard proxy URL: %s", urlPath)
	}

	clusterIDUint64, err := strconv.ParseUint(matches[1], 10, 64)
	if err != nil {
		return 0, fmt.Errorf("failed to parse cluster id from path: %s", urlPath)
	}
	clusterID = entity.ID(clusterIDUint64)
	return
}

func getOrgIDFromOldPath(urlPath string) (orgID entity.ID, err error) {
	clusterID, err := parseClusterIDFromOldPath(urlPath)
	if err != nil {
		return 0, err
	}

	var cluster entity.Cluster
	err = db.Transaction(func(tx *gorm.DB) error {
		var err error
		cluster, err = dbutil.GetCluster(tx, clusterID)
		if err != nil {
			return err
		}

		return nil
	})
	if err != nil {
		return 0, fmt.Errorf("failed to get orgID from old tidb dashboard proxy URL: %s", urlPath)
	}
	orgID = cluster.TenantID

	return
}

func IsDashboardPath(urlPath string) bool {
	return RegexpTiDBDashboardProxyAPI.MatchString(urlPath)
}

func IsOldTiDBDashboardPath(urlPath string) bool {
	return RegexpOldTiDBDashboardProxyAPI.MatchString(urlPath)
}
