// Copyright 2024 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 object

import (
	"fmt"
	"strings"

	"github.com/casdoor/casdoor/util"
	"github.com/golang-jwt/jwt/v5"
)

type ClaimsStandard struct {
	*UserStandard
	EmailVerified       bool        `json:"email_verified,omitempty"`
	PhoneNumber         string      `json:"phone_number,omitempty"`
	PhoneNumberVerified bool        `json:"phone_number_verified,omitempty"`
	Gender              string      `json:"gender,omitempty"`
	TokenType           string      `json:"tokenType,omitempty"`
	Nonce               string      `json:"nonce,omitempty"`
	Scope               string      `json:"scope,omitempty"`
	Address             OIDCAddress `json:"address,omitempty"`
	Azp                 string      `json:"azp,omitempty"`
	Provider            string      `json:"provider,omitempty"`

	jwt.RegisteredClaims
}

func getStreetAddress(user *User) string {
	var addrs string
	for _, addr := range user.Address {
		addrs += addr + "\n"
	}
	return addrs
}

func getStandardClaims(claims Claims) ClaimsStandard {
	res := ClaimsStandard{
		UserStandard:     getStandardUser(claims.User),
		EmailVerified:    claims.User.EmailVerified,
		TokenType:        claims.TokenType,
		Nonce:            claims.Nonce,
		Scope:            claims.Scope,
		RegisteredClaims: claims.RegisteredClaims,
		Azp:              claims.Azp,
		Provider:         claims.Provider,
	}

	res.Phone = ""
	var scopes []string

	if strings.Contains(claims.Scope, ",") {
		scopes = strings.Split(claims.Scope, ",")
	} else {
		scopes = strings.Split(claims.Scope, " ")
	}

	for _, scope := range scopes {
		if scope == "address" {
			res.Address = OIDCAddress{StreetAddress: getStreetAddress(claims.User)}
		} else if scope == "profile" {
			res.Gender = claims.User.Gender
		} else if scope == "phone" && claims.User.Phone != "" {
			res.PhoneNumberVerified = true
			phoneNumber, ok := util.GetE164Number(claims.User.Phone, claims.User.CountryCode)
			if !ok {
				res.PhoneNumberVerified = false
			} else {
				res.PhoneNumber = phoneNumber
			}

		}
	}

	return res
}

func ParseStandardJwtToken(token string, cert *Cert) (*ClaimsStandard, error) {
	t, err := jwt.ParseWithClaims(token, &ClaimsStandard{}, func(token *jwt.Token) (interface{}, error) {
		var (
			certificate interface{}
			err         error
		)

		if cert.Certificate == "" {
			return nil, fmt.Errorf("the certificate field should not be empty for the cert: %v", cert)
		}

		if _, ok := token.Method.(*jwt.SigningMethodRSA); ok {
			// RSA certificate
			certificate, err = jwt.ParseRSAPublicKeyFromPEM([]byte(cert.Certificate))
		} else if _, ok := token.Method.(*jwt.SigningMethodECDSA); ok {
			// ES certificate
			certificate, err = jwt.ParseECPublicKeyFromPEM([]byte(cert.Certificate))
		} else {
			return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
		}

		if err != nil {
			return nil, err
		}

		return certificate, nil
	})

	if t != nil {
		if claims, ok := t.Claims.(*ClaimsStandard); ok && t.Valid {
			return claims, nil
		}
	}

	return nil, err
}

func ParseStandardJwtTokenByApplication(token string, application *Application) (*ClaimsStandard, error) {
	cert, err := getCertByApplication(application)
	if err != nil {
		return nil, err
	}

	return ParseStandardJwtToken(token, cert)
}
