package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"github.com/stretchr/testify/assert"
	"go-nkode/core"
	"go-nkode/util"
	"io"
	"net/http"
	"strings"
	"testing"
)

func TestApi(t *testing.T) {
	base := "http://localhost:8080"
	newCustomerBody := core.NewCustomerPost{
		NKodePolicy: core.NewDefaultNKodePolicy(),
	}
	kp := core.KeypadDimension{
		AttrsPerKey: 14,
		NumbOfKeys:  10,
	}
	var customerResp core.CreateNewCustomerResp
	testApiPost(t, base+core.CreateNewCustomer, newCustomerBody, &customerResp)

	userEmail := "test_username" + util.GenerateRandomString(12) + "@example.com"
	signupInterfaceBody := core.GenerateSignupRestInterfacePost{
		CustomerId:  customerResp.CustomerId,
		AttrsPerKey: kp.AttrsPerKey,
		NumbOfKeys:  kp.NumbOfKeys,
		UserEmail:   strings.ToUpper(userEmail), // should be case-insensitive
		Reset:       false,
	}
	var signupInterfaceResp core.GenerateSignupResetInterfaceResp
	testApiPost(t, base+core.GenerateSignupResetInterface, signupInterfaceBody, &signupInterfaceResp)
	assert.Len(t, signupInterfaceResp.SvgInterface, kp.TotalAttrs())
	passcodeLen := 4
	setInterface := signupInterfaceResp.UserIdxInterface
	userPasscode := setInterface[:passcodeLen]
	kpSet := core.KeypadDimension{NumbOfKeys: kp.NumbOfKeys, AttrsPerKey: kp.NumbOfKeys}
	setKeySelection, err := core.SelectKeyByAttrIdx(setInterface, userPasscode, kpSet)
	assert.NoError(t, err)
	setNKodeBody := core.SetNKodePost{
		CustomerId:   customerResp.CustomerId,
		SessionId:    signupInterfaceResp.SessionId,
		KeySelection: setKeySelection,
	}
	var setNKodeResp core.SetNKodeResp
	testApiPost(t, base+core.SetNKode, setNKodeBody, &setNKodeResp)
	confirmInterface := setNKodeResp.UserInterface
	confirmKeySelection, err := core.SelectKeyByAttrIdx(confirmInterface, userPasscode, kpSet)
	assert.NoError(t, err)
	confirmNKodeBody := core.ConfirmNKodePost{
		CustomerId:   customerResp.CustomerId,
		KeySelection: confirmKeySelection,
		SessionId:    signupInterfaceResp.SessionId,
	}
	testApiPost(t, base+core.ConfirmNKode, confirmNKodeBody, nil)

	loginInterfaceBody := core.GetLoginInterfacePost{
		CustomerId: customerResp.CustomerId,
		UserEmail:  userEmail,
	}

	var loginInterfaceResp core.GetLoginInterfaceResp
	testApiPost(t, base+core.GetLoginInterface, loginInterfaceBody, &loginInterfaceResp)
	assert.Equal(t, loginInterfaceResp.AttrsPerKey, kp.AttrsPerKey)
	assert.Equal(t, loginInterfaceResp.NumbOfKeys, kp.NumbOfKeys)
	loginKeySelection, err := core.SelectKeyByAttrIdx(loginInterfaceResp.UserIdxInterface, userPasscode, kp)
	assert.NoError(t, err)
	loginBody := core.LoginPost{
		CustomerId:   customerResp.CustomerId,
		UserEmail:    userEmail,
		KeySelection: loginKeySelection,
	}

	var jwtTokens core.AuthenticationTokens
	testApiPost(t, base+core.Login, loginBody, &jwtTokens)
	refreshClaims, err := core.ParseRegisteredClaimToken(jwtTokens.RefreshToken)
	assert.Equal(t, refreshClaims.Subject, userEmail)
	accessClaims, err := core.ParseRegisteredClaimToken(jwtTokens.AccessToken)
	assert.Equal(t, accessClaims.Subject, userEmail)
	renewBody := core.RenewAttributesPost{CustomerId: customerResp.CustomerId}
	testApiPost(t, base+core.RenewAttributes, renewBody, nil)

	loginKeySelection, err = core.SelectKeyByAttrIdx(loginInterfaceResp.UserIdxInterface, userPasscode, kp)
	assert.NoError(t, err)
	loginBody = core.LoginPost{
		CustomerId:   customerResp.CustomerId,
		UserEmail:    userEmail,
		KeySelection: loginKeySelection,
	}

	testApiPost(t, base+core.Login, loginBody, &jwtTokens)

	var randomSvgInterfaceResp core.RandomSvgInterfaceResp
	testApiGet(t, base+core.RandomSvgInterface, &randomSvgInterfaceResp, "")
	assert.Equal(t, core.KeypadMax.TotalAttrs(), len(randomSvgInterfaceResp.Svgs))

	var refreshTokenResp core.RefreshTokenResp

	testApiGet(t, base+core.RefreshToken, &refreshTokenResp, jwtTokens.RefreshToken)
	accessClaims, err = core.ParseRegisteredClaimToken(refreshTokenResp.AccessToken)
	assert.NoError(t, err)
	assert.Equal(t, accessClaims.Subject, userEmail)
}

func Unmarshal(t *testing.T, resp *http.Response, data any) {
	responseBody, err := io.ReadAll(resp.Body)
	assert.NoError(t, err)
	err = json.Unmarshal(responseBody, data)
	assert.NoError(t, err)
}

func Marshal(t *testing.T, data any) *bytes.Reader {
	jsonBytes, err := json.Marshal(data)
	assert.NoError(t, err)
	reader := bytes.NewReader(jsonBytes)
	return reader
}

func testApiPost(t *testing.T, endpointStr string, postBody any, respBody any) {
	reader := Marshal(t, postBody)
	resp, err := http.Post(endpointStr, "application/json", reader)
	assert.NoError(t, err)
	assert.Equal(t, http.StatusOK, resp.StatusCode)
	if respBody != nil {
		Unmarshal(t, resp, respBody)
	}
}

func testApiGet(t *testing.T, endpointStr string, respBody any, bearerToken string) {
	req, err := http.NewRequest("GET", endpointStr, nil)
	if err != nil {
		fmt.Println("Error creating request:", err)
		return
	}

	// Add the Bearer token to the Authorization header
	if bearerToken != "" {
		req.Header.Set("Authorization", "Bearer "+bearerToken)
	}
	// Make the HTTP request
	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		fmt.Println("Error making request:", err)
		return
	}
	defer resp.Body.Close()

	assert.NoError(t, err)
	assert.Equal(t, resp.StatusCode, http.StatusOK)
	if respBody != nil {
		Unmarshal(t, resp, respBody)
	}
}
