package jwt

import (
	"fmt"
	"os"
	"path"
	"testing"

	jwt "github.com/nats-io/jwt/v2/v1compat"
	"github.com/nats-io/nkeys"
)

func TestExample(t *testing.T) {
	// create an operator key pair (private key)
	okp, err := nkeys.CreateOperator()
	if err != nil {
		t.Fatal(err)
	}
	// extract the public key
	opk, err := okp.PublicKey()
	if err != nil {
		t.Fatal(err)
	}

	// create an operator claim using the public key for the identifier
	oc := jwt.NewOperatorClaims(opk)
	oc.Name = "O"
	// add an operator signing key to sign accounts
	oskp, err := nkeys.CreateOperator()
	if err != nil {
		t.Fatal(err)
	}
	// get the public key for the signing key
	ospk, err := oskp.PublicKey()
	if err != nil {
		t.Fatal(err)
	}
	// add the signing key to the operator - this makes any account
	// issued by the signing key to be valid for the operator
	oc.SigningKeys.Add(ospk)

	// self-sign the operator JWT - the operator trusts itself
	operatorJWT, err := oc.Encode(okp)
	if err != nil {
		t.Fatal(err)
	}

	// create an account keypair
	akp, err := nkeys.CreateAccount()
	if err != nil {
		t.Fatal(err)
	}
	// extract the public key for the account
	apk, err := akp.PublicKey()
	if err != nil {
		t.Fatal(err)
	}
	// create the claim for the account using the public key of the account
	ac := jwt.NewAccountClaims(apk)
	ac.Name = "A"
	// create a signing key that we can use for issuing users
	askp, err := nkeys.CreateAccount()
	if err != nil {
		t.Fatal(err)
	}
	// extract the public key
	aspk, err := askp.PublicKey()
	if err != nil {
		t.Fatal(err)
	}
	// add the signing key (public) to the account
	ac.SigningKeys.Add(aspk)

	// now we could encode an issue the account using the operator
	// key that we generated above, but this will illustrate that
	// the account could be self-signed, and given to the operator
	// who can then re-sign it
	accountJWT, err := ac.Encode(akp)
	if err != nil {
		t.Fatal(err)
	}

	// the operator would decode the provided token, if the token
	// is not self-signed or signed by an operator or tampered with
	// the decoding would fail
	ac, err = jwt.DecodeAccountClaims(accountJWT)
	if err != nil {
		t.Fatal(err)
	}
	// here the operator is going to use its private signing key to
	// re-issue the account
	accountJWT, err = ac.Encode(oskp)
	if err != nil {
		t.Fatal(err)
	}

	// now back to the account, the account can issue users
	// need not be known to the operator - the users are trusted
	// because they will be signed by the account. The server will
	// look up the account get a list of keys the account has and
	// verify that the user was issued by one of those keys
	ukp, err := nkeys.CreateUser()
	if err != nil {
		t.Fatal(err)
	}
	upk, err := ukp.PublicKey()
	if err != nil {
		t.Fatal(err)
	}
	uc := jwt.NewUserClaims(upk)
	// since the jwt will be issued by a signing key, the issuer account
	// must be set to the public ID of the account
	uc.IssuerAccount = apk
	userJwt, err := uc.Encode(askp)
	if err != nil {
		t.Fatal(err)
	}
	// the seed is a version of the keypair that is stored as text
	useed, err := ukp.Seed()
	if err != nil {
		t.Fatal(err)
	}
	// generate a creds formatted file that can be used by a NATS client
	creds, err := jwt.FormatUserConfig(userJwt, useed)
	if err != nil {
		t.Fatal(err)
	}

	// now we are going to put it together into something that can be run
	// we create a directory to store the server configuration, the creds
	// file and a small go program that uses the creds file
	dir, err := os.MkdirTemp(os.TempDir(), "jwt_example")
	if err != nil {
		t.Fatal(err)
	}
	// print where we generated the file
	t.Logf("generated example %s", dir)
	t.Log("to run this example:")
	t.Logf("> cd %s", dir)
	t.Log("> go mod init example")
	t.Log("> go mod tidy")
	t.Logf("> nats-server -c %s/resolver.conf &", dir)
	t.Log("> go run main.go")

	// we are generating a memory resolver server configuration
	// it lists the operator and all account jwts the server should
	// know about
	resolver := fmt.Sprintf(`operator: %s

resolver: MEMORY
resolver_preload: {
	%s: %s
}
`, operatorJWT, apk, accountJWT)
	if err := os.WriteFile(path.Join(dir, "resolver.conf"),
		[]byte(resolver), 0644); err != nil {
		t.Fatal(err)
	}

	// store the creds
	credsPath := path.Join(dir, "u.creds")
	if err := os.WriteFile(credsPath, creds, 0644); err != nil {
		t.Fatal(err)
	}

	// here we generate as small go program that connects using the creds file
	// subscribes, and publishes a message
	connect := fmt.Sprintf(`
package main

import (
  "fmt"
  "sync"

  "github.com/nats-io/nats.go"
)

func main() {
  var wg sync.WaitGroup
  wg.Add(1)
  nc, err := nats.Connect(nats.DefaultURL, nats.UserCredentials(%q))
  if err != nil {
	panic(err)
  }
  nc.Subscribe("hello.world", func(m *nats.Msg) {
    fmt.Println(m.Subject)
	wg.Done()
  })
  nc.Publish("hello.world", []byte("hello"))
  nc.Flush()
  wg.Wait()
  nc.Close()
}

`, credsPath)
	if err := os.WriteFile(path.Join(dir, "main.go"), []byte(connect), 0644); err != nil {
		t.Fatal(err)
	}
}
