package nkode

import (
	"errors"
	"fmt"
	"github.com/google/uuid"
	m "go-nkode/core/model"
	"go-nkode/hashset"
	py "go-nkode/py-builtin"
	"go-nkode/util"
)

type UserSignSession struct {
	Id                  m.SessionId
	CustomerId          m.CustomerId
	LoginUserInterface  m.UserInterface
	Kp                  m.KeypadDimension
	SetIdxInterface     m.IdxInterface
	ConfirmIdxInterface m.IdxInterface
	SetKeySelection     m.KeySelection
	Username            m.Username
	Expire              int
}

func NewSignupSession(username m.Username, kp m.KeypadDimension, customerId m.CustomerId) (*UserSignSession, error) {
	loginInterface, err := m.NewUserInterface(&kp)
	if err != nil {
		return nil, err
	}
	signupInterface, err := signupInterface(*loginInterface, kp)
	if err != nil {
		return nil, err
	}
	session := UserSignSession{
		Id:                  m.SessionId(uuid.New()),
		CustomerId:          customerId,
		LoginUserInterface:  *loginInterface,
		SetIdxInterface:     signupInterface.IdxInterface,
		ConfirmIdxInterface: nil,
		SetKeySelection:     nil,
		Username:            username,
		Kp:                  kp,
	}

	return &session, nil
}

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

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

	if s.SetIdxInterface == nil {
		return nil, errors.New("signup session set interface is nil")
	}

	if s.ConfirmIdxInterface == nil {
		return nil, errors.New("signup session confirm interface is nil")
	}

	if s.SetKeySelection == nil {
		return nil, errors.New("signup session set key entry is nil")
	}

	if s.Username == "" {
		return nil, errors.New("signup session username is nil")
	}

	if len(confirmKeyEntry) != len(s.SetKeySelection) {
		return nil, errors.New(fmt.Sprintf("confirm and set key entry lenght mismatch %d != %d", len(confirmKeyEntry), len(s.SetKeySelection)))
	}

	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 {
			return nil, errors.New(fmt.Sprintf("set and confirm do not intersect at index %d", idx))
		}
		if intersection.Size() > 1 {
			return nil, errors.New(fmt.Sprintf("set and confirm intersect at more than one point at index %d", idx))
		}
		intersectionSlice := intersection.ToSlice()
		passcode[idx] = intersectionSlice[0]
	}
	return passcode, nil
}

func (s *UserSignSession) SetUserNKode(keySelection m.KeySelection) (m.IdxInterface, error) {
	validKeySelection := py.All[int](keySelection, func(i int) bool {
		return 0 <= i && i < s.Kp.NumbOfKeys
	})
	if !validKeySelection {
		return nil, errors.New(fmt.Sprintf("one or key selection is out of range 0-%d", s.Kp.NumbOfKeys-1))
	}

	s.SetKeySelection = keySelection
	setKp := s.SignupKeypad()
	setInterface := m.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 m.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 m.UserInterface, kp m.KeypadDimension) (*m.UserInterface, error) {
	if kp.IsDispersable() {
		return nil, errors.New("keypad is dispersable, can't use signupInterface")
	}
	err := baseUserInterface.RandomShuffle()
	if err != nil {
		return nil, err
	}
	interfaceMatrix, err := baseUserInterface.InterfaceMatrix()
	if err != nil {
		return nil, err
	}
	attrSetView, err := util.MatrixTranspose(interfaceMatrix)
	if err != nil {
		return nil, err
	}
	err = util.FisherYatesShuffle[[]int](&attrSetView)
	if err != nil {
		return nil, err
	}
	numbOfKeys := kp.NumbOfKeys
	attrSetView = attrSetView[:numbOfKeys]
	attrSetView, err = util.MatrixTranspose(attrSetView)
	if err != nil {
		return nil, err
	}
	signupUserInterface := m.UserInterface{
		IdxInterface: util.MatrixToList(attrSetView),
		Kp: &m.KeypadDimension{
			AttrsPerKey: numbOfKeys,
			NumbOfKeys:  numbOfKeys,
		},
	}
	return &signupUserInterface, nil
}

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