package srp

import (
	"bytes"
	"errors"
	"math/big"
)

type SRPServer struct {
	Params   *SRPParams
	Verifier *big.Int
	Secret2  *big.Int
	B        *big.Int
	M1       []byte
	M2       []byte
	K        []byte
	u        *big.Int
	s        *big.Int
}

func NewServer(params *SRPParams, Vb []byte, S2b []byte) *SRPServer {
	multiplier := getMultiplier(params)
	V := intFromBytes(Vb)
	secret2 := intFromBytes(S2b)

	Bb := getB(params, multiplier, V, secret2)
	B := intFromBytes(Bb)

	return &SRPServer{
		Params:   params,
		Secret2:  secret2,
		Verifier: V,
		B:        B,
	}
}

func (s *SRPServer) ComputeB() []byte {
	return intToBytes(s.B)
}

func (s *SRPServer) SetA(A []byte) {
	AInt := intFromBytes(A)
	U := getu(s.Params, AInt, s.B)
	S := serverGetS(s.Params, s.Verifier, AInt, s.Secret2, U)

	s.K = getK(s.Params, S)
	s.M1 = getM1(s.Params, A, intToBytes(s.B), S)
	s.M2 = getM2(s.Params, A, s.M1, s.K)

	s.u = U               // only for tests
	s.s = intFromBytes(S) // only for tests
}

func (s *SRPServer) CheckM1(M1 []byte) ([]byte, error) {
	if !bytes.Equal(s.M1, M1) {
		return nil, errors.New("Client did not use the same password")
	} else {
		return s.M2, nil
	}
}

func (s *SRPServer) ComputeK() []byte {
	return s.K
}

// Helpers

func serverGetS(params *SRPParams, V, A, S2, U *big.Int) []byte {
	ALessThan0 := A.Cmp(big.NewInt(0)) <= 0
	NLessThanA := params.N.Cmp(A) <= 0
	if ALessThan0 || NLessThanA {
		panic("invalid client-supplied 'A', must be 1..N-1")
	}

	result1 := new(big.Int)
	result1.Exp(V, U, params.N)

	result2 := new(big.Int)
	result2.Mul(A, result1)

	result3 := new(big.Int)
	result3.Exp(result2, S2, params.N)

	result4 := new(big.Int)
	result4.Mod(result3, params.N)

	return padToN(result4, params)
}

func getB(params *SRPParams, multiplier, V, b *big.Int) []byte {
	gModPowB := new(big.Int)
	gModPowB.Exp(params.G, b, params.N)

	kMulV := new(big.Int)
	kMulV.Mul(multiplier, V)

	leftSide := new(big.Int)
	leftSide.Add(kMulV, gModPowB)

	final := new(big.Int)
	final.Mod(leftSide, params.N)

	return padToN(final, params)
}
