package core

import (
	"github.com/google/uuid"
	"go-nkode/hashset"
	py "go-nkode/py-builtin"
	"go-nkode/util"
	"log"
	"sort"
)

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

func NewSignupResetSession(userEmail UserEmail, kp KeypadDimension, customerId CustomerId, svgInterface 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:                  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 KeySelection) ([]int, error) {
	validEntry := py.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, ErrKeyIndexOutOfRange
	}

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

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

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

	if s.UserEmail == "" {
		log.Print("signup session username is nil")
		return nil, 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, 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 := hashset.NewSetFromSlice[int](setKeyVals[idx])
		confirmKey := hashset.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, ErrSetConfirmSignupMismatch
		}
		if intersection.Size() > 1 {
			log.Printf("set and confirm intersect at more than one point at index %d", idx)
			return nil, ErrSetConfirmSignupMismatch
		}
		intersectionSlice := intersection.ToSlice()
		passcode[idx] = intersectionSlice[0]
	}
	return passcode, nil
}

func (s *UserSignSession) SetUserNKode(keySelection KeySelection) (IdxInterface, error) {
	validKeySelection := py.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, 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 KeySelection, userInterface []int) ([][]int, error) {
	signupKp := s.SignupKeypad()
	keypadInterface, err := util.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, []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, 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 := util.MatrixTranspose(interfaceMatrix)
	if err != nil {
		return nil, nil, err
	}

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

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

	signupUserInterface := UserInterface{
		IdxInterface: util.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,
	}
}
