package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"github.com/stretchr/testify/assert"
	"go-nkode/internal/api"
	"go-nkode/internal/entities"
	"go-nkode/internal/models"
	"go-nkode/internal/security"
	"io"
	"net/http"
	"strings"
	"testing"
)

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

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

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

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

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

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

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

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

	var refreshTokenResp models.RefreshTokenResp

	testApiGet(t, base+api.RefreshToken, &refreshTokenResp, jwtTokens.RefreshToken)
	accessClaims, err = security.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)
	}
}
