package entities

import (
	"github.com/DonovanKelly/sugar-n-spice/all"
	"github.com/DonovanKelly/sugar-n-spice/set"
	"github.com/google/uuid"
	"go-nkode/config"
	"go-nkode/internal/models"
	"go-nkode/internal/security"
	"log"
	"sort"
)

type UserSignSession struct {
	Id                  models.SessionId
	CustomerId          models.CustomerID
	LoginUserInterface  UserInterface
	Kp                  KeypadDimension
	SetIdxInterface     models.IdxInterface
	ConfirmIdxInterface models.IdxInterface
	SetKeySelection     models.KeySelection
	UserEmail           models.UserEmail
	Reset               bool
	Expire              int
	Colors              []models.RGBColor
}

func NewSignupResetSessionRigged(userEmail models.UserEmail, kp KeypadDimension, customerId models.CustomerID, svgInterface models.SvgIdInterface, reset bool) (*UserSignSession, error) {
	loginInterface, err := NewUserInterface(&kp, svgInterface)
	if err != nil {
		return nil, err
	}
	setIdxInterface := make(models.IdxInterface, 36)
	for idx := range 36 {
		setIdxInterface[idx] = idx
	}
	session := UserSignSession{
		Id:                  models.SessionId(uuid.New()),
		CustomerId:          customerId,
		LoginUserInterface:  *loginInterface,
		SetIdxInterface:     setIdxInterface,
		ConfirmIdxInterface: nil,
		SetKeySelection:     nil,
		UserEmail:           userEmail,
		Kp:                  kp,
		Reset:               reset,
		Colors:              []models.RGBColor{},
	}
	return &session, nil
}

func NewSignupResetSession(userEmail models.UserEmail, kp KeypadDimension, customerId models.CustomerID, svgInterface models.SvgIdInterface, reset bool) (*UserSignSession, error) {
	loginInterface, err := NewUserInterface(&kp, svgInterface)
	if err != nil {
		return nil, err
	}
	signupInterface, colors, err := signupInterface(*loginInterface, kp)
	if err != nil {
		return nil, err
	}
	session := UserSignSession{
		Id:                  models.SessionId(uuid.New()),
		CustomerId:          customerId,
		LoginUserInterface:  *loginInterface,
		SetIdxInterface:     signupInterface.IdxInterface,
		ConfirmIdxInterface: nil,
		SetKeySelection:     nil,
		UserEmail:           userEmail,
		Kp:                  kp,
		Reset:               reset,
		Colors:              colors,
	}
	return &session, nil
}

func (s *UserSignSession) DeducePasscode(confirmKeyEntry models.KeySelection) ([]int, error) {
	validEntry := all.All[int](confirmKeyEntry, func(i int) bool {
		return 0 <= i && i < s.Kp.NumbOfKeys
	})

	if !validEntry {
		log.Printf("Invalid Key entry. One or more key index: %#v, not in range 0-%d", confirmKeyEntry, s.Kp.NumbOfKeys)
		return nil, config.ErrKeyIndexOutOfRange
	}

	if s.SetIdxInterface == nil {
		log.Print("signup session set interface is nil")
		return nil, config.ErrIncompleteUserSignupSession
	}

	if s.ConfirmIdxInterface == nil {
		log.Print("signup session confirm interface is nil")
		return nil, config.ErrIncompleteUserSignupSession
	}

	if s.SetKeySelection == nil {
		log.Print("signup session set key entry is nil")
		return nil, config.ErrIncompleteUserSignupSession
	}

	if s.UserEmail == "" {
		log.Print("signup session username is nil")
		return nil, config.ErrIncompleteUserSignupSession
	}

	if len(confirmKeyEntry) != len(s.SetKeySelection) {
		log.Printf("confirm and set key entry length mismatch %d != %d", len(confirmKeyEntry), len(s.SetKeySelection))
		return nil, config.ErrSetConfirmSignupMismatch
	}

	passcodeLen := len(confirmKeyEntry)
	setKeyVals, err := s.getSelectedKeyVals(s.SetKeySelection, s.SetIdxInterface)
	if err != nil {
		return nil, err
	}
	confirmKeyVals, err := s.getSelectedKeyVals(confirmKeyEntry, s.ConfirmIdxInterface)
	passcode := make([]int, passcodeLen)

	for idx := 0; idx < passcodeLen; idx++ {
		setKey := set.NewSetFromSlice[int](setKeyVals[idx])
		confirmKey := set.NewSetFromSlice[int](confirmKeyVals[idx])
		intersection := setKey.Intersect(confirmKey)
		if intersection.Size() < 1 {
			log.Printf("set and confirm do not intersect at index %d", idx)
			return nil, config.ErrSetConfirmSignupMismatch
		}
		if intersection.Size() > 1 {
			log.Printf("set and confirm intersect at more than one point at index %d", idx)
			return nil, config.ErrSetConfirmSignupMismatch
		}
		intersectionSlice := intersection.ToSlice()
		passcode[idx] = intersectionSlice[0]
	}
	return passcode, nil
}

func (s *UserSignSession) SetUserNKode(keySelection models.KeySelection) (models.IdxInterface, error) {
	validKeySelection := all.All[int](keySelection, func(i int) bool {
		return 0 <= i && i < s.Kp.NumbOfKeys
	})
	if !validKeySelection {
		log.Printf("one or key selection is out of range 0-%d", s.Kp.NumbOfKeys-1)
		return nil, config.ErrKeyIndexOutOfRange
	}

	s.SetKeySelection = keySelection
	setKp := s.SignupKeypad()
	setInterface := UserInterface{IdxInterface: s.SetIdxInterface, Kp: &setKp}
	err := setInterface.DisperseInterface()
	if err != nil {
		return nil, err
	}
	s.ConfirmIdxInterface = setInterface.IdxInterface
	return s.ConfirmIdxInterface, nil
}

func (s *UserSignSession) getSelectedKeyVals(keySelections models.KeySelection, userInterface []int) ([][]int, error) {
	signupKp := s.SignupKeypad()
	keypadInterface, err := security.ListToMatrix(userInterface, signupKp.AttrsPerKey)
	if err != nil {
		return nil, err
	}
	keyVals := make([][]int, len(keySelections))

	for idx, keyIdx := range keySelections {
		keyVals[idx] = keypadInterface[keyIdx]
	}
	return keyVals, nil
}

func signupInterface(baseUserInterface UserInterface, kp KeypadDimension) (*UserInterface, []models.RGBColor, error) {
	// This method randomly drops sets from the base user interface so it is a square and dispersable matrix
	if kp.IsDispersable() {
		return nil, nil, config.ErrKeypadIsNotDispersible
	}
	err := baseUserInterface.RandomShuffle()
	if err != nil {
		return nil, nil, err
	}
	// attributes are arranged by key interfaceMatrix
	interfaceMatrix, err := baseUserInterface.InterfaceMatrix()
	if err != nil {
		return nil, nil, err
	}
	// attributes are arranged by set
	attrSetView, err := security.MatrixTranspose(interfaceMatrix)
	if err != nil {
		return nil, nil, err
	}

	setIdxs := security.IdentityArray(kp.AttrsPerKey)
	if err := security.FisherYatesShuffle[int](&setIdxs); err != nil {
		return nil, nil, err
	}
	setIdxs = setIdxs[:kp.NumbOfKeys]
	sort.Ints(setIdxs)
	selectedSets := make([][]int, kp.NumbOfKeys)
	selectedColors := make([]models.RGBColor, kp.NumbOfKeys)

	for idx, setIdx := range setIdxs {
		selectedSets[idx] = attrSetView[setIdx]
		selectedColors[idx] = models.SetColors[setIdx]
	}
	// convert set view back into key view
	selectedSets, err = security.MatrixTranspose(selectedSets)
	if err != nil {
		return nil, nil, err
	}

	signupUserInterface := UserInterface{
		IdxInterface: security.MatrixToList(selectedSets),
		Kp: &KeypadDimension{
			AttrsPerKey: kp.NumbOfKeys,
			NumbOfKeys:  kp.NumbOfKeys,
		},
	}
	return &signupUserInterface, selectedColors, nil
}

func (s *UserSignSession) SignupKeypad() KeypadDimension {
	return KeypadDimension{
		AttrsPerKey: s.Kp.NumbOfKeys,
		NumbOfKeys:  s.Kp.NumbOfKeys,
	}
}
