package client

import (
	"encoding/hex"
	"fmt"
	"io/ioutil"
	"os"
	"runtime"
	"sync"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"gopkg.in/jcmturner/gokrb5.v7/config"
	"gopkg.in/jcmturner/gokrb5.v7/iana/etypeID"
	"gopkg.in/jcmturner/gokrb5.v7/keytab"
	"gopkg.in/jcmturner/gokrb5.v7/test"
	"gopkg.in/jcmturner/gokrb5.v7/test/testdata"
)

func TestMultiThreadedClientSession(t *testing.T) {
	test.Integration(t)

	b, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB)
	kt := keytab.New()
	kt.Unmarshal(b)
	c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF)
	addr := os.Getenv("TEST_KDC_ADDR")
	if addr == "" {
		addr = testdata.TEST_KDC_ADDR
	}
	c.Realms[0].KDC = []string{addr + ":" + testdata.TEST_KDC}
	cl := NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt, c)
	err := cl.Login()
	if err != nil {
		t.Fatalf("failed to log in: %v", err)
	}

	s, ok := cl.sessions.get("TEST.GOKRB5")
	if !ok {
		t.Fatal("error initially getting session")
	}
	go func() {
		for {
			err := cl.renewTGT(s)
			if err != nil {
				t.Logf("error renewing TGT: %v", err)
			}
			time.Sleep(time.Millisecond * 100)
		}
	}()

	var wg sync.WaitGroup
	wg.Add(10)
	for i := 0; i < 10; i++ {
		go func() {
			defer wg.Done()
			tgt, _, err := cl.sessionTGT("TEST.GOKRB5")
			if err != nil || tgt.Realm != "TEST.GOKRB5" {
				t.Logf("error getting session: %v", err)
			}
			_, _, _, r, _ := cl.sessionTimes("TEST.GOKRB5")
			fmt.Fprintf(ioutil.Discard, "%v", r)
		}()
		time.Sleep(time.Second)
	}
	wg.Wait()
}

func TestClient_AutoRenew_Goroutine(t *testing.T) {
	test.Integration(t)

	// Tests that the auto renew of client credentials is not spawning goroutines out of control.
	addr := os.Getenv("TEST_KDC_ADDR")
	if addr == "" {
		addr = testdata.TEST_KDC_ADDR
	}
	b, _ := hex.DecodeString(testdata.TESTUSER2_KEYTAB)
	kt := keytab.New()
	kt.Unmarshal(b)
	c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF)
	c.Realms[0].KDC = []string{addr + ":" + testdata.TEST_KDC_SHORTTICKETS}
	c.LibDefaults.PreferredPreauthTypes = []int{int(etypeID.DES3_CBC_SHA1_KD)} // a preauth etype the KDC does not support. Test this does not cause renewal to fail.
	cl := NewClientWithKeytab("testuser2", "TEST.GOKRB5", kt, c)

	err := cl.Login()
	if err != nil {
		t.Errorf("error on logging in: %v\n", err)
	}
	n := runtime.NumGoroutine()
	for i := 0; i < 24; i++ {
		time.Sleep(time.Second * 5)
		_, endTime, _, _, err := cl.sessionTimes("TEST.GOKRB5")
		if err != nil {
			t.Errorf("could not get client's session: %v", err)
		}
		if time.Now().UTC().After(endTime) {
			t.Fatalf("session auto update failed")
		}
		spn := "HTTP/host.test.gokrb5"
		tkt, key, err := cl.GetServiceTicket(spn)
		if err != nil {
			t.Fatalf("error getting service ticket: %v\n", err)
		}
		b, _ := hex.DecodeString(testdata.HTTP_KEYTAB)
		skt := keytab.New()
		skt.Unmarshal(b)
		tkt.DecryptEncPart(skt, nil)
		assert.Equal(t, spn, tkt.SName.PrincipalNameString())
		assert.Equal(t, int32(18), key.KeyType)
		if runtime.NumGoroutine() > n {
			t.Fatalf("number of goroutines is increasing: should not be more than %d, is %d", n, runtime.NumGoroutine())
		}
	}
}
