package domain

import (
	"context"
	"errors"
	"reflect"
	"strings"
	"testing"

	"git.vdhsn.com/barretthousen/barretthousen/src/auth/internal/data"
)

func TestDomain_CreateAccount(t *testing.T) {
	SetupTest()

	tests := map[string]struct {
		input   CreateAccountCommand
		wantOut AccountCreated
		wantErr bool
	}{
		"new account": {
			input: CreateAccountCommand{
				Email:    "test@example.com",
				Password: "password",
			},
			wantOut: AccountCreated{
				Account: Account{
					ID:           0,
					Email:        "test@example.com",
					PasswordHash: "password",
					Enabled:      true,
					Role:         UserRole,
				},
			},
		},
		"no email": {
			input: CreateAccountCommand{
				Password: "password",
				Role:     UserRole,
			},
			wantErr: true,
		},
		"no password": {
			input: CreateAccountCommand{
				Email: "test@example.com",
				Role:  UserRole,
			},
			wantErr: true,
		},
		"no inputs": {
			wantErr: true,
		},
	}

	c := ioc.Scope("CreateAccountTest")

	Must(c.Provide(func(s Storage, ms *MockStorage) *Domain {
		return &Domain{Storage: s}
	}))

	for name, v := range tests {
		params := v
		t.Run(name, func(t *testing.T) {
			Must(c.Invoke(func(sut *Domain, ms *MockStorage) {
				var a Account
				ms.RegisterAccountFunc = func(ctx context.Context, rai data.RegisterAccountInput) (int, error) {
					a = Account{
						Email:        rai.Email,
						PasswordHash: rai.PasswordHash,
						Role:         Role(rai.Role),
						Enabled:      true,
					}
					return 0, nil
				}

				ms.GetAccountFunc = func(ctx context.Context, gai data.GetAccountInput) (data.AccountResult, error) {
					if gai.Email != a.Email {
						return data.AccountResult{}, errors.New("error")
					}
					return data.AccountResult{
						Email:        a.Email,
						PasswordHash: a.PasswordHash,
						Role:         a.Role.String(),
						Enabled:      true,
					}, nil
				}

				out, err := sut.CreateAccount(context.Background(), params.input)
				if (err != nil) != params.wantErr {
					t.Errorf("Domain.CreateAccount() error = %v, wantErr %v", err, params.wantErr)
					return
				}

				if !reflect.DeepEqual(out, params.wantOut) {
					t.Errorf("Domain.CreateAccount():\n%+v\n, want:\n%+v", out, params.wantOut)
				}
			}))
		})
	}
}

func TestDomain_Login(t *testing.T) {
	tests := map[string]struct {
		input   LoginCommand
		wantOut LoggedIn
		wantErr bool
	}{
		"Login Valid User": {
			input: LoginCommand{
				Email:    "test@example.com",
				Password: "password",
			},
			wantOut: LoggedIn{
				Account: Account{
					ID:           1,
					Email:        "test@example.com",
					PasswordHash: "",
					Enabled:      true,
					Role:         UserRole,
				},
			},
		},
	}

	c := ioc.Scope("LoginTest")

	Must(c.Provide(func(s Storage) *Domain {
		return &Domain{Storage: s}
	}))

	for name, v := range tests {
		params := v
		t.Run(name, func(t *testing.T) {
			Must(c.Invoke(func(sut *Domain, ms *MockStorage) {
				ms.CreateSessionFunc = func(ctx context.Context, in data.CreateSessionInput) (data.Session, error) {
					if strings.EqualFold(in.Email, "test@example.com") {
						return data.Session{AccountID: 1}, nil
					}

					return data.Session{}, data.ErrCouldNotCreateSession
				}

				ms.GetAccountFunc = func(ctx context.Context, gai data.GetAccountInput) (data.AccountResult, error) {
					if strings.EqualFold(gai.Email, "test@example.com") || gai.AccountID == 1 {
						return data.AccountResult{
							ID:      1,
							Email:   "test@example.com",
							Role:    UserRole.String(),
							Enabled: true,
						}, nil
					}
					return data.AccountResult{}, data.ErrSessionNotFound
				}

				out, err := sut.Login(context.Background(), params.input)
				if (err != nil) != params.wantErr {
					t.Errorf("Domain.Login() error = %v, wantErr %v", err, params.wantErr)
					return
				}
				if !reflect.DeepEqual(out, params.wantOut) {
					t.Errorf("Domain.Login() = %v, want %v", out, params.wantOut)
				}
			}))
		})
	}
}
