package authorization_test

import (
	"errors"
	"fmt"
	"strconv"
	"strings"
	"testing"

	"code.justin.tv/eventbus/client/encryption"
	"code.justin.tv/eventbus/schema/pkg/clock"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

type MockProvider struct {
	enc *MockEncrypter
}

func (m *MockProvider) Encrypter() encryption.Encrypter {
	return m.enc
}

func (m *MockProvider) Environment() string {
	return "end-to-end-schema-tests"
}

func newMockProvider() *MockProvider {
	return &MockProvider{
		enc: &MockEncrypter{
			shouldError: false,
		},
	}
}

func newFailingMockProvider() *MockProvider {
	return &MockProvider{
		enc: &MockEncrypter{
			shouldError: true,
		},
	}
}

type MockEncrypter struct {
	shouldError bool
}

func (m *MockEncrypter) EncryptString(encCtx map[string]string, plaintext string) ([]byte, error) {
	if m.shouldError {
		return nil, errors.New("error encrypting payload")
	}
	return []byte(fmt.Sprintf("!!!%s!!!", plaintext)), nil
}

func (m *MockEncrypter) EncryptFloat32(encCtx map[string]string, float32ToEnc float32) ([]byte, error) {
	if m.shouldError {
		return nil, errors.New("error encrypting payload")
	}
	return []byte(fmt.Sprintf("!!!%f!!!", float32ToEnc)), nil
}

func (m *MockEncrypter) EncryptFloat64(encCtx map[string]string, float64ToEnc float64) ([]byte, error) {
	if m.shouldError {
		return nil, errors.New("error encrypting payload")
	}
	return []byte(fmt.Sprintf("!!!%f!!!", float64ToEnc)), nil
}

func (m *MockEncrypter) EncryptInt32(encCtx map[string]string, int32ToEnc int32) ([]byte, error) {
	if m.shouldError {
		return nil, errors.New("error encrypting payload")
	}
	return []byte(fmt.Sprintf("!!!%d!!!", int32ToEnc)), nil
}

func (m *MockEncrypter) EncryptInt64(encCtx map[string]string, int64ToEnc int64) ([]byte, error) {
	if m.shouldError {
		return nil, errors.New("error encrypting payload")
	}
	return []byte(fmt.Sprintf("!!!%d!!!", int64ToEnc)), nil
}

type MockDecrypter struct {
	shouldError bool
}

func (m *MockDecrypter) DecryptString(b []byte) (string, error) {
	if m.shouldError {
		return "", errors.New("error decrypting payload")
	}

	if b == nil {
		return "", errors.New("empty payload")
	}

	return strings.TrimPrefix(strings.TrimSuffix(string(b), "!!!"), "!!!"), nil
}

func (m *MockDecrypter) DecryptFloat32(b []byte) (float32, error) {
	if m.shouldError {
		return 0, errors.New("error decrypting payload")
	}

	if b == nil {
		return 0, errors.New("empty payload")
	}

	trimmed := strings.TrimPrefix(strings.TrimSuffix(string(b), "!!!"), "!!!")
	f, err := strconv.ParseFloat(trimmed, 32)
	return float32(f), err
}

func (m *MockDecrypter) DecryptFloat64(b []byte) (float64, error) {
	if m.shouldError {
		return 0, errors.New("error decrypting payload")
	}

	if b == nil {
		return 0, errors.New("empty payload")
	}

	trimmed := strings.TrimPrefix(strings.TrimSuffix(string(b), "!!!"), "!!!")
	return strconv.ParseFloat(trimmed, 64)
}

func (m *MockDecrypter) DecryptInt32(b []byte) (int32, error) {
	if m.shouldError {
		return 0, errors.New("error decrypting payload")
	}

	if b == nil {
		return 0, errors.New("empty payload")
	}

	trimmed := strings.TrimPrefix(strings.TrimSuffix(string(b), "!!!"), "!!!")
	f, err := strconv.ParseInt(trimmed, 10, 32)
	return int32(f), err
}

func (m *MockDecrypter) DecryptInt64(b []byte) (int64, error) {
	if m.shouldError {
		return 0, errors.New("error decrypting payload")
	}

	if b == nil {
		return 0, errors.New("empty payload")
	}

	trimmed := strings.TrimPrefix(strings.TrimSuffix(string(b), "!!!"), "!!!")
	return strconv.ParseInt(trimmed, 10, 64)
}

func newMockDecrypter() *MockDecrypter {
	return &MockDecrypter{
		shouldError: false,
	}
}

func newFailingMockDecrypter() *MockDecrypter {
	return &MockDecrypter{
		shouldError: true,
	}
}

func TestEndToEndAuthorization(t *testing.T) {
	s := "this is a secret message"
	t.Run("Happy path", func(t *testing.T) {
		goodProvider := newMockProvider()
		goodDecrypter := newMockDecrypter()
		event := &clock.Update{}

		t.Run("Encryption", func(t *testing.T) {
			err := event.SetEncryptedSecretTime(goodProvider, s)
			assert.NoError(t, err)
			assert.Equal(t, fmt.Sprintf("!!!%s!!!", s), string(event.GetSecretTime().GetOlePayload()))
		})

		t.Run("Decryption", func(t *testing.T) {
			out, err := event.GetDecryptedSecretTime(goodDecrypter)
			assert.NoError(t, err)
			assert.Equal(t, s, out)
		})

		t.Run("Nil decryption behaves like protobuf", func(t *testing.T) {
			t.Run("String type", func(t *testing.T) {
				t.Run("Nil field", func(t *testing.T) {
					event := &clock.Update{}
					out, err := event.GetDecryptedSecretTime(nil) // doesnt matter that a nil decrypter is used, never calls into it since the paylaod is nil
					assert.NoError(t, err)
					assert.Empty(t, out)
				})
			})
		})
	})

	t.Run("Sad path", func(t *testing.T) {
		t.Run("Bad encrypter", func(t *testing.T) {
			p := newFailingMockProvider()
			event := &clock.Update{}
			err := event.SetEncryptedSecretTime(p, s)
			assert.Error(t, err)
			assert.Nil(t, event.GetSecretTime())
		})

		t.Run("Bad decrypter", func(t *testing.T) {
			p := newMockProvider()
			d := newFailingMockDecrypter()
			event := &clock.Update{}
			err := event.SetEncryptedSecretTime(p, s)
			require.NoError(t, err)

			out, err := event.GetDecryptedSecretTime(d)
			assert.Error(t, err)
			assert.Empty(t, out)
		})

		t.Run("Nil event decryption", func(t *testing.T) {
			var nilEvent *clock.Update
			out, err := nilEvent.GetDecryptedSecretTime(nil)
			assert.Error(t, err)
			assert.Empty(t, out)
		})
	})

}
