package jess

import (
	"bytes"
	"encoding/json"
	"fmt"
	"reflect"
	"testing"
)

func TestSerialization(t *testing.T) {
	t.Parallel()

	subject := &Letter{
		Version: 1,
		SuiteID: SuiteComplete,
		Keys: []*Seal{
			{ID: "a"},
			{ID: "b"},
			{ID: "c"},
		},
		Nonce:     []byte{1, 2, 3},
		Data:      []byte{4, 5, 6},
		Mac:       []byte{7, 8, 9},
		ApplyKeys: true,
	}
	testSerialize(t, subject, true)

	subject.Version = 0
	subject.SuiteID = ""
	testSerialize(t, subject, true)

	subject.ApplyKeys = false
	testSerialize(t, subject, true)

	subject.Keys = nil
	testSerialize(t, subject, true)
}

func testSerialize(t *testing.T, letter *Letter, wireFormat bool) { //nolint:unparam
	t.Helper()

	// File Format

	fileData, err := letter.ToFileFormat()
	if err != nil {
		t.Error(err)
		return
	}

	letter2, err := LetterFromFileFormat(fileData)
	if err != nil {
		t.Error(err)
		return
	}

	err = letter.CheckEqual(letter2)
	if err != nil {
		t.Errorf("letters (file format) do not match: %s\n%+v\n%+v\n", err, jsonFormat(letter), jsonFormat(letter2))
		return
	}

	// Wire Format

	if !wireFormat {
		return
	}

	wire, err := letter.ToWire()
	if err != nil {
		t.Error(err)
		return
	}

	letter3, err := LetterFromWire(wire)
	if err != nil {
		t.Error(err)
		return
	}

	err = letter.CheckEqual(letter3)
	if err != nil {
		t.Errorf("letters (wire format) do not match: %s\n%+v\n%+v\n", err, jsonFormat(letter), jsonFormat(letter3))
		return
	}
}

func (letter *Letter) CheckEqual(other *Letter) error {
	letterValue := reflect.ValueOf(*letter)
	otherValue := reflect.ValueOf(*other)

	var ok bool
	numElements := letterValue.NumField()
	for i := 0; i < numElements; i++ {
		name := letterValue.Type().Field(i).Name
		switch name {
		case "Data": // TODO: this required special handling in the past, leave it here for now.
			ok = bytes.Equal(letter.Data, other.Data)
		default:
			ok = reflect.DeepEqual(letterValue.Field(i).Interface(), otherValue.Field(i).Interface())
		}

		if !ok {
			return fmt.Errorf("field %s mismatches", name)
		}
	}

	return nil
}

func jsonFormat(v interface{}) string {
	formatted, err := json.MarshalIndent(v, "", "  ")
	if err != nil {
		return fmt.Sprintf("<JSON format error: %s>", err)
	}
	return string(formatted)
}