package examples_test

import (
	"crypto/rand"
	"crypto/rsa"
	"fmt"
	"log"
	"time"

	"github.com/lestrrat-go/jwx/v3/internal/json"
	"github.com/lestrrat-go/jwx/v3/jwk"
	"github.com/lestrrat-go/jwx/v3/jws"
	"github.com/lestrrat-go/jwx/v3/jwt/openid"

	"github.com/lestrrat-go/jwx/v3/jwa"
	"github.com/lestrrat-go/jwx/v3/jwt"
)

const aLongLongTimeAgo = 233431200

const exampleJWTSignedHMAC = `eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk`
const exampleJWTSignedRSA = `eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjbKBYNX4BAynRFdiuB--f_nZLgrnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHlb1L07Qe7K0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZESc6BfI7noOPqvhJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrBp0igcN_IoypGlUPQGe77Rw`
const exampleJWTSignedECDSA = `eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8ISlSA`

//nolint:govet
func Example_jwt_parse_with_jwks() {
	privKey, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		fmt.Printf("failed to generate private key: %s\n", err)
		return
	}

	{
		// Case 2: For whatever reason, we don't have a "kid" specified.
		//   Normally, this is an error, because we don't know how to select a key.
		//   But if we have only one key in the KeySet, you can explicitly ask
		//   jwt.Parse to "trust" the KeySet, and use the single key in the
		//   key set. It would be an error if you have multiple keys in the KeySet.

		var payload []byte
		var keyset jwk.Set
		{ // Preparation:
			// Unlike our previous example, we DO NOT want to sign the payload.
			// Therefore we do NOT set the "kid" value
			realKey, err := jwk.Import(privKey)
			if err != nil {
				fmt.Printf("failed to create JWK: %s\n", err)
				return
			}

			// Create the token
			token := jwt.New()
			token.Set(`foo`, `bar`)

			// Sign the token and generate a payload
			signed, err := jwt.Sign(token, jwt.WithKey(jwa.RS256(), realKey))
			if err != nil {
				fmt.Printf("failed to generate signed payload: %s\n", err)
				return
			}

			// This is what you typically get as a signed JWT from a server
			payload = signed

			// Now create a key set that users will use to verity the signed payload against
			// Normally these keys are available somewhere like https://www.googleapis.com/oauth2/v3/certs
			pubKey, err := jwk.Import(privKey.PublicKey)
			if err != nil {
				fmt.Printf("failed to create JWK: %s\n", err)
				return
			}
			pubKey.Set(jwk.AlgorithmKey, jwa.RS256())

			// This JWKS can *only* have 1 key.
			keyset = jwk.NewSet()
			keyset.AddKey(pubKey)
		}

		{
			token, err := jwt.Parse(
				payload,
				// Tell the parser that you want to use this keyset
				jwt.WithKeySet(keyset,
					// Tell the parser that you can trust this KeySet, and that
					// you want to use the sole key in it
					jws.WithUseDefault(true),
				),
			)
			if err != nil {
				fmt.Printf("failed to parse payload: %s\n", err)
			}
			_ = token
		}
	}

	// OUTPUT:
}

// This example return a signed jwt
func Example_jwt_sign_with_import_jwk() {
	// your JWK
	jwkStr := `{
		"kty": "RSA",
		"n": "mmO0OvOPQ53HRxV4eHOkTTxLVfk6zcq8KAD86gbnydYBNO_Si4Q1twyvefd58-BaO4N4NCEA97QrYm57ThKCe8agLGwWPHhxgbu_SAuYQehXxkf4sWy7Q17kGFG5k5AfQGZBqTY-YaawQqLlF6ILVbWab_AoEF4yB7pI3AnNnXs",
		"e": "AQAB",
		"d": "RzsrI2vONJcuIyjPzVslehEQfRkhPWOFTjuudNc8yA25vs_LZ11XXx42M-KvXIqtdvngUsTLan2w6pgowcuecX3t_2wUx0GJJgARfkN7gsWIS3CyXZBEEMjLGVU4vHt5zNE3GJKo3hb1TwEiulpL_Ix6hfcTSJpEaBWrBxjxV-E",
		"p": "5EA0bi6ui1H1wsG85oc7i9O7UH58WPIK_ytzBWXFIwcaSFFBqqNYNnZaHFsMe4cbHSBgShWHO3UueGVgOKmB8Q",
		"q": "rSi7CosQZmj_RFIYW10ef7XTZsdpIdOXV9-1dThAJUvkslKiTfdU7T0IYYsJ2K58ekJqdpcoKAVLB2SZVvdqKw",
		"dp": "S9yjEHPng1qsShzGQgB0ZBbtTOWdQpq_2OuCAStACFJWA-8t2h8MNJ3FeWMxlOTkuBuIpVbeaX6bAV0ATBTaoQ",
		"dq": "ZssMJhkh1jm0d-FoVix0Y4oUAiqUzaDnciH6faiz47AnBnkporEV-HPH2ugII1qJyKZOvzHCg-eIf84HfWoI2w",
		"qi": "lyVz1HI2b1IjzOMENkmUTaVEO6DM6usZi3c3_MobUUM05yyBhnHtPjWzqWn1uJ_Gt5bkJDdcpfvmkPAhKWEU9Q"
	}`

	// create a new jwt
	t := jwt.New()
	t.Set(jwt.SubjectKey, `https://github.com/lestrrat-go/jwx/v3/jwt`)
	t.Set(jwt.AudienceKey, `Golang Users`)
	t.Set(jwt.IssuedAtKey, time.Unix(500, 0))

	buf, err := json.MarshalIndent(t, "", "  ")
	if err != nil {
		fmt.Printf("failed to generate JSON: %s\n", err)
		return
	}

	fmt.Printf("%s\n", buf)

	var pc string
	if err := t.Get(`privateClaimKey`, &pc); err != nil {
		fmt.Printf("failed to fetch private claim\n")
		return
	}
	fmt.Printf("privateClaimKey -> '%s'\n", pc)

	//convert jwk in bytes and return a new key
	jwkey, err := jwk.ParseKey([]byte(jwkStr))
	if err != nil {
		fmt.Printf("failed to parse key: %s\n", err)
		return
	}

	// signed and return a jwt
	signed, _ := jwt.Sign(t, jwt.WithKey(jwa.RS256(), jwkey))

	fmt.Println(string(signed[:]))

	// output
	// a signed jwt based on jwk
}

func Example_jwt_sign() {
	privKey, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		fmt.Printf("failed to generate private key: %s\n", err)
		return
	}

	var payload []byte
	{ // Create signed payload
		token := jwt.New()
		token.Set(`foo`, `bar`)
		payload, err = jwt.Sign(token, jwt.WithKey(jwa.RS256(), privKey))
		if err != nil {
			fmt.Printf("failed to generate signed payload: %s\n", err)
			return
		}
	}

	{ // Parse signed payload, and perform (1) verification of the signature
		// and (2) validation of the JWT token
		// Validation can be performed in a separate step using `jwt.Validate`
		token, err := jwt.Parse(
			payload,
			jwt.WithValidate(true),
			jwt.WithKey(jwa.RS256(), &privKey.PublicKey),
		)
		if err != nil {
			fmt.Printf("failed to parse JWT token: %s\n", err)
			return
		}
		buf, err := json.MarshalIndent(token, "", "  ")
		if err != nil {
			fmt.Printf("failed to generate JSON: %s\n", err)
			return
		}
		fmt.Printf("%s\n", buf)
	}
	// OUTPUT:
	// {
	//   "foo": "bar"
	// }
}

func Example_jwt_token() {
	t := jwt.New()
	t.Set(jwt.SubjectKey, `https://github.com/lestrrat-go/jwx/v3/jwt`)
	t.Set(jwt.AudienceKey, `Golang Users`)
	t.Set(jwt.IssuedAtKey, time.Unix(aLongLongTimeAgo, 0))
	t.Set(`privateClaimKey`, `Hello, World!`)

	buf, err := json.MarshalIndent(t, "", "  ")
	if err != nil {
		fmt.Printf("failed to generate JSON: %s\n", err)
		return
	}

	fmt.Printf("%s\n", buf)
	aud, ok := t.Audience()
	if !ok {
		fmt.Printf("failed to fetch audience\n")
		return
	}
	fmt.Printf("aud -> '%s'\n", aud)

	iat, ok := t.IssuedAt()
	if !ok {
		fmt.Printf("failed to fetch issued at\n")
		return
	}
	fmt.Printf("iat -> '%s'\n", iat.Format(time.RFC3339))

	var pc string
	if err := t.Get(`privateClaimKey`, &pc); err != nil {
		fmt.Printf("failed to fetch private claim\n")
		return
	}
	fmt.Printf("privateClaimKey -> '%s'\n", pc)

	sub, ok := t.Subject()
	if !ok {
		fmt.Printf("failed to fetch subject\n")
		return
	}
	fmt.Printf("sub -> '%s'\n", sub)

	// OUTPUT:
	// {
	//   "aud": [
	//     "Golang Users"
	//   ],
	//   "iat": 233431200,
	//   "privateClaimKey": "Hello, World!",
	//   "sub": "https://github.com/lestrrat-go/jwx/v3/jwt"
	// }
	// aud -> '[Golang Users]'
	// iat -> '1977-05-25T18:00:00Z'
	// privateClaimKey -> 'Hello, World!'
	// sub -> 'https://github.com/lestrrat-go/jwx/v3/jwt'
}

func Example_jwt_sign_token() {
	key, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		log.Printf("failed to generate private key: %s", err)
		return
	}

	t := jwt.New()

	{
		// Signing a token (using raw rsa.PrivateKey)
		signed, err := jwt.Sign(t, jwt.WithKey(jwa.RS256(), key))
		if err != nil {
			log.Printf("failed to sign token: %s", err)
			return
		}
		_ = signed
	}

	{
		// Signing a token (using JWK)
		jwkKey, err := jwk.Import(key)
		if err != nil {
			log.Printf("failed to create JWK key: %s", err)
			return
		}

		signed, err := jwt.Sign(t, jwt.WithKey(jwa.RS256(), jwkKey))
		if err != nil {
			log.Printf("failed to sign token: %s", err)
			return
		}
		_ = signed
	}

	// OUTPUT:
}

func Example_jwt_openid_token() {
	t := openid.New()
	t.Set(jwt.SubjectKey, `https://github.com/lestrrat-go/jwx/v3/jwt`)
	t.Set(jwt.AudienceKey, `Golang Users`)
	t.Set(jwt.IssuedAtKey, time.Unix(aLongLongTimeAgo, 0))
	t.Set(`privateClaimKey`, `Hello, World!`)

	addr := openid.NewAddress()
	addr.Set(openid.AddressPostalCodeKey, `105-0011`)
	addr.Set(openid.AddressCountryKey, `日本`)
	addr.Set(openid.AddressRegionKey, `東京都`)
	addr.Set(openid.AddressLocalityKey, `港区`)
	addr.Set(openid.AddressStreetAddressKey, `芝公園 4-2-8`)
	if err := t.Set(openid.AddressKey, addr); err != nil {
		fmt.Printf("failed to set address: %s\n", err)
		return
	}

	buf, err := json.MarshalIndent(t, "", "  ")
	if err != nil {
		fmt.Printf("failed to generate JSON: %s\n", err)
		return
	}
	fmt.Printf("%s\n", buf)

	t2, err := jwt.Parse(buf, jwt.WithToken(openid.New()), jwt.WithVerify(false), jwt.WithValidate(false))
	if err != nil {
		fmt.Printf("failed to parse JSON: %s\n", err)
		return
	}
	if _, ok := t2.(openid.Token); !ok {
		fmt.Printf("using jwt.WithToken(openid.New()) creates an openid.Token instance")
		return
	}
	// OUTPUT:
	// {
	//   "address": {
	//     "country": "日本",
	//     "locality": "港区",
	//     "postal_code": "105-0011",
	//     "region": "東京都",
	//     "street_address": "芝公園 4-2-8"
	//   },
	//   "aud": [
	//     "Golang Users"
	//   ],
	//   "iat": 233431200,
	//   "privateClaimKey": "Hello, World!",
	//   "sub": "https://github.com/lestrrat-go/jwx/v3/jwt"
	// }
}
