package orgsso

import (
	"context"
	"errors"
	"fmt"
	"strconv"
	"strings"

	"github.com/tidbcloud/account/pkg/auth/token"
	"github.com/tidbcloud/pkgv2/auth/auth0"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"gopkg.in/auth0.v4/management"

	"github.com/form3tech-oss/jwt-go"
	"github.com/tidbcloud/account/internel/dao/entity"
)

var (
	//oidcDomainCompile = regexp.MustCompile("https://(.+)/.well-known/openid-configuration")

	// init at server start
	Auth0TokenValidator *token.Validator
	Auth0UserManagement *management.UserManager
)

const SamlConnectNameFormat = "SAML-%d"

func GetSamlConnectionName(orgId uint64) string {
	return fmt.Sprintf(SamlConnectNameFormat, orgId)
}

func GetClaimsFromToken(rawToken string) (jwt.MapClaims, error) {
	return Auth0TokenValidator.RawTokenValidator.ValidateFor(auth0.AuthValidatorIDCentral, rawToken)
}

func GetMethodNameFromClaim(claims jwt.MapClaims) (string, error) {
	claimSub, ok := claims["sub"]
	if !ok {
		return "", errors.New("sub not found in claims")
	}
	sub, ok := claimSub.(string)
	if !ok {
		return "", errors.New("sub not string in claims")
	}
	return GetMethodNameFromSubject(sub)
}

func CheckEmailInAllowDomain(email, allowDomain string) bool {
	if allowDomain == "" {
		return true
	}
	allowDs := strings.Split(allowDomain, ",")
	for _, allowD := range allowDs {
		if strings.HasSuffix(email, allowD) {
			return true
		}
	}
	return false
}

func GetMethodNameFromSubject(sub string) (string, error) {
	// oidc|okta-oidc-test|00u8nmokbjW9RnK6S5d7
	// samlp|auth0-saml-test-connection|iliqingzhu@outlook.com
	// google-oauth2|114025365174170918131
	split := strings.Split(sub, "|")
	l := len(split)
	if l == 2 {
		return split[0], nil
	} else if l == 3 {
		return split[1], nil
	} else {
		return "", errors.New("unknown sub")
	}
}

func CheckHasOrgClaim(claims jwt.MapClaims) (bool, error) {
	claimHasOrg, ok := claims[entity.ClaimsHasOrg]
	if !ok {
		return false, errors.New("has_org not found in claims")
	}
	hasOrg, ok := claimHasOrg.(string)
	if !ok {
		return false, errors.New("has_org not string in claims")
	}
	if hasOrg == UserHasOrg {
		return true, nil
	}
	return false, nil
}

func GetOrgTypeClaim(claims jwt.MapClaims) (string, error) {
	claimOrgType, ok := claims[entity.ClaimsOrgTypeKey]
	if !ok {
		return "", errors.New("org_type not found in claims")
	}
	orgType, ok := claimOrgType.(string)
	if !ok {
		return "", errors.New("orgType not string in claims")
	}
	return orgType, nil
}

func GetUsageClaim(claims jwt.MapClaims) (string, error) {
	claimUsage, ok := claims[entity.ClaimsUsage]
	if !ok {
		return "", errors.New("usage not found in claims")
	}
	usage, ok := claimUsage.(string)
	if !ok {
		return "", errors.New("usage not string in claims")
	}
	return usage, nil
}

func GetOrgIDClaim(claims jwt.MapClaims) (uint64, error) {
	claimOrgId, ok := claims[entity.ClaimsOrgIDKey]
	if !ok {
		return 0, errors.New("org_id not found in claims")
	}
	orgIdStr, ok := claimOrgId.(string)
	if !ok {
		return 0, errors.New("org_id not string in claims")
	}
	if orgIdStr == "" {
		return 0, nil
	}
	orgId, err := strconv.ParseUint(orgIdStr, 10, 64)
	if err != nil {
		return 0, err
	}
	return orgId, nil
}

func GetAccessOrgId(urlOrgID uint64, claims jwt.MapClaims) (uint64, error) {
	var accessOrgId = urlOrgID
	if accessOrgId == 0 {
		orgId, err := GetOrgIDClaim(claims)
		if err != nil {
			return 0, err
		}
		accessOrgId = orgId
	}
	return accessOrgId, nil
}

func GenOrgTokenAuthParam(ctx context.Context, claims jwt.MapClaims, urlOrgID uint64) (*OrgTokenAuthParam, error) {
	accessOrgId, err := GetAccessOrgId(urlOrgID, claims)
	if err != nil {
		tilog.Warn(ctx, "GenOrgTokenAuthParam GetAccessOrgId error", zap.Any("err", err))
		return nil, err
	}

	claimSub, ok := claims["sub"]
	if !ok {
		return nil, errors.New("sub not found in claims")
	}
	sub, ok := claimSub.(string)
	if !ok {
		return nil, errors.New("sub not string in claims")
	}

	claimIat, ok := claims["iat"]
	if !ok {
		return nil, errors.New("iat not found in claims")
	}
	iatFloat, ok := claimIat.(float64)
	if !ok {
		return nil, errors.New("iat not float64 in claims")
	}
	iat := int64(iatFloat)

	claimEmail, ok := claims["email"]
	if !ok {
		return nil, errors.New("email not found in claims")
	}
	email, ok := claimEmail.(string)
	if !ok {
		return nil, errors.New("email not string in claims")
	}

	orgType, err := GetOrgTypeClaim(claims)
	if err != nil {
		return nil, err
	}

	orgId, err := GetOrgIDClaim(claims)
	if err != nil {
		return nil, err
	}

	authParam := &OrgTokenAuthParam{
		Subject:     sub,
		Iat:         iat,
		Email:       email,
		OrgType:     orgType,
		OrgID:       orgId,
		AccessOrgID: accessOrgId,
	}
	return authParam, nil
}

func GetCompanyName(customPath string) string {
	//  https://dev.tidbcloud.com/auth_redirect?usage=enterprise-signin&company=xxx
	if !strings.Contains(customPath, "company=") {
		return ""
	}
	split := strings.Split(customPath, "company=")
	str := split[1]
	if !strings.Contains(str, "&") {
		return str
	}
	split = strings.Split(str, "&")
	return split[0]
}

func GetUsage(customPath string) string {
	//  https://dev.tidbcloud.com/auth_redirect?usage=enterprise-signin&company=xxx
	if !strings.Contains(customPath, "usage=") {
		return ""
	}
	split := strings.Split(customPath, "usage=")
	str := split[1]
	if !strings.Contains(str, "&") {
		return str
	}
	split = strings.Split(str, "&")
	return split[0]
}

/*
	func GetDomainFromIssuerUrl(issuerUrl string) string {
		submatch := oidcDomainCompile.FindStringSubmatch(issuerUrl)
		return submatch[len(submatch)-1]
	}
*/
// delete auth0 user
func DeleteAuth0User(ctx context.Context, email string) error {
	users, err := Auth0UserManagement.ListByEmail(email)
	if err != nil {
		tilog.Info(ctx, "Auth0UserManagement.ListByEmail err,"+fmt.Sprintf("%s, %s", email, err.Error()))
		return nil
	}
	for _, u := range users {
		if err := Auth0UserManagement.Delete(*u.ID); err != nil {
			tilog.Error(ctx, "Auth0UserManagement.Delete(*u.ID) err"+err.Error())
		}

	}
	return nil
}
