package fake_test

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"github.com/9seconds/mtg/v2/mtglib"
	"github.com/9seconds/mtg/v2/mtglib/internal/tls/fake"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
	"github.com/stretchr/testify/require"
	"github.com/stretchr/testify/suite"
)

type clientHelloSnapshot struct {
	Time        int    `json:"time"`
	Random      string `json:"random"`
	SessionID   string `json:"sessionId"`
	Host        string `json:"host"`
	CipherSuite int    `json:"cipherSuite"`
	Full        string `json:"full"`
}

func (c clientHelloSnapshot) GetRandom() []byte {
	data, _ := base64.StdEncoding.DecodeString(c.Random)

	return data
}

func (c clientHelloSnapshot) GetSessionID() []byte {
	data, _ := base64.StdEncoding.DecodeString(c.SessionID)

	return data
}

func (c clientHelloSnapshot) GetCipherSuite() uint16 {
	return uint16(c.CipherSuite)
}

func (c clientHelloSnapshot) GetFull() []byte {
	data, _ := base64.StdEncoding.DecodeString(c.Full)

	return data
}

type ParseClientHelloSnapshotTestSuite struct {
	suite.Suite

	secret mtglib.Secret
}

func (suite *ParseClientHelloSnapshotTestSuite) SetupSuite() {
	parsed, err := mtglib.ParseSecret(
		"ee367a189aee18fa31c190054efd4a8e9573746f726167652e676f6f676c65617069732e636f6d",
	)
	require.NoError(suite.T(), err)

	suite.secret = parsed
}

func (suite *ParseClientHelloSnapshotTestSuite) makeConn(data []byte) *parseClientHelloConnMock {
	readBuf := &bytes.Buffer{}
	readBuf.Write(data)

	connMock := &parseClientHelloConnMock{
		readBuf: readBuf,
	}

	connMock.
		On("SetReadDeadline", mock.AnythingOfType("time.Time")).
		Twice().
		Return(nil)

	return connMock
}

func (suite *ParseClientHelloSnapshotTestSuite) TestSnapshotOk() {
	files, err := os.ReadDir("testdata")
	require.NoError(suite.T(), err)

	for _, v := range files {
		if !strings.HasPrefix(v.Name(), "client-hello-ok") {
			continue
		}

		path := filepath.Join("testdata", v.Name())

		suite.T().Run(v.Name(), func(t *testing.T) {
			fileData, err := os.ReadFile(path)
			assert.NoError(t, err)

			snapshot := &clientHelloSnapshot{}
			assert.NoError(t, json.Unmarshal(fileData, snapshot))

			connMock := suite.makeConn(snapshot.GetFull())
			defer connMock.AssertExpectations(t)

			hello, err := fake.ReadClientHello(
				connMock,
				suite.secret.Key[:],
				suite.secret.Host,
				TolerateTime,
			)
			require.NoError(t, err)

			assert.Equal(t, snapshot.GetRandom(), hello.Random[:])
			assert.Equal(t, snapshot.GetSessionID(), hello.SessionID)
			assert.Equal(t, snapshot.GetCipherSuite(), hello.CipherSuite)
		})
	}
}

func (suite *ParseClientHelloSnapshotTestSuite) TestSnapshotBad() {
	files, err := os.ReadDir("testdata")
	require.NoError(suite.T(), err)

	for _, v := range files {
		if !strings.HasPrefix(v.Name(), "client-hello-bad") {
			continue
		}

		path := filepath.Join("testdata", v.Name())

		suite.T().Run(v.Name(), func(t *testing.T) {
			fileData, err := os.ReadFile(path)
			assert.NoError(t, err)

			snapshot := &clientHelloSnapshot{}
			assert.NoError(t, json.Unmarshal(fileData, snapshot))

			connMock := suite.makeConn(snapshot.GetFull())
			defer connMock.AssertExpectations(t)

			_, err = fake.ReadClientHello(
				connMock,
				suite.secret.Key[:],
				suite.secret.Host,
				TolerateTime,
			)
			assert.ErrorIs(t, err, fake.ErrBadDigest)
		})
	}
}

func TestParseClientHelloSnapshot(t *testing.T) {
	t.Parallel()
	suite.Run(t, &ParseClientHelloSnapshotTestSuite{})
}
