package service

import (
	"context"
	"errors"
	"testing"

	"wucher/internal/domain/user"
)

type userRepoMock struct {
	createErr        error
	updateErr        error
	deleteErr        error
	getByIDErr       error
	getByEmailErr    error
	getByUsernameErr error
	listErr          error

	gotCreate        *user.User
	gotUpdate        *user.User
	gotDelete        []byte
	gotGetByID       []byte
	gotGetByEmail    string
	gotGetByUsername string
	gotListFilter    string
	gotListSort      string
	gotListLimit     int
	gotListOffset    int

	retByID       *user.User
	retByEmail    *user.User
	retByUsername *user.User
	retList       []user.User
	retTotal      int64
}

func (m *userRepoMock) Create(_ context.Context, u *user.User) error {
	m.gotCreate = u
	return m.createErr
}
func (m *userRepoMock) Update(_ context.Context, u *user.User) error {
	m.gotUpdate = u
	return m.updateErr
}
func (m *userRepoMock) Delete(_ context.Context, id []byte) error {
	m.gotDelete = id
	return m.deleteErr
}
func (m *userRepoMock) GetByID(_ context.Context, id []byte) (*user.User, error) {
	m.gotGetByID = id
	return m.retByID, m.getByIDErr
}
func (m *userRepoMock) GetByEmail(_ context.Context, email string) (*user.User, error) {
	m.gotGetByEmail = email
	return m.retByEmail, m.getByEmailErr
}
func (m *userRepoMock) GetByUsername(_ context.Context, username string) (*user.User, error) {
	m.gotGetByUsername = username
	return m.retByUsername, m.getByUsernameErr
}
func (m *userRepoMock) List(_ context.Context, filter, sort string, limit, offset int) ([]user.User, int64, error) {
	m.gotListFilter = filter
	m.gotListSort = sort
	m.gotListLimit = limit
	m.gotListOffset = offset
	return m.retList, m.retTotal, m.listErr
}

func (m *userRepoMock) GetLastNameByID(_ context.Context, _ []byte) (string, error) {
	return "", nil
}

func TestUserService_CRUDAndGet(t *testing.T) {
	repo := &userRepoMock{}
	svc := NewUserService(repo)

	u := &user.User{Email: "u@e.com"}
	if err := svc.Create(context.Background(), u); err != nil {
		t.Fatalf("create: %v", err)
	}
	if repo.gotCreate != u {
		t.Fatalf("expected create arg passed")
	}

	if err := svc.Update(context.Background(), u); err != nil {
		t.Fatalf("update: %v", err)
	}
	if repo.gotUpdate != u {
		t.Fatalf("expected update arg passed")
	}

	id := []byte("id")
	if err := svc.Delete(context.Background(), id); err != nil {
		t.Fatalf("delete: %v", err)
	}
	if string(repo.gotDelete) != string(id) {
		t.Fatalf("expected delete id passed")
	}

	repo.retByID = u
	got, err := svc.GetByID(context.Background(), id)
	if err != nil || got != u {
		t.Fatalf("getByID failed")
	}

	repo.retByEmail = u
	got, err = svc.GetByEmail(context.Background(), "u@e.com")
	if err != nil || got != u {
		t.Fatalf("getByEmail failed")
	}

	repo.retByUsername = u
	got, err = svc.GetByUsername(context.Background(), "john")
	if err != nil || got != u {
		t.Fatalf("getByUsername failed")
	}
}

func TestUserService_RepoErrors(t *testing.T) {
	repo := &userRepoMock{
		createErr:        errors.New("e"),
		updateErr:        errors.New("e"),
		deleteErr:        errors.New("e"),
		getByIDErr:       errors.New("e"),
		getByEmailErr:    errors.New("e"),
		getByUsernameErr: errors.New("e"),
		listErr:          errors.New("e"),
	}
	svc := NewUserService(repo)
	if svc.Create(context.Background(), &user.User{}) == nil {
		t.Fatalf("expected create err")
	}
	if svc.Update(context.Background(), &user.User{}) == nil {
		t.Fatalf("expected update err")
	}
	if svc.Delete(context.Background(), []byte("id")) == nil {
		t.Fatalf("expected delete err")
	}
	if _, err := svc.GetByID(context.Background(), []byte("id")); err == nil {
		t.Fatalf("expected getByID err")
	}
	if _, err := svc.GetByEmail(context.Background(), "u@e.com"); err == nil {
		t.Fatalf("expected getByEmail err")
	}
	if _, err := svc.GetByUsername(context.Background(), "john"); err == nil {
		t.Fatalf("expected getByUsername err")
	}
	if _, _, err := svc.List(context.Background(), "", "", 1, 0); err == nil {
		t.Fatalf("expected list err")
	}
}

func TestUserService_ListAndNormalizeSort(t *testing.T) {
	repo := &userRepoMock{retList: []user.User{{Email: "u@e.com"}}, retTotal: 1}
	svc := NewUserService(repo)

	_, _, err := svc.List(context.Background(), "u", "-email", 10, 20)
	if err != nil {
		t.Fatalf("list: %v", err)
	}
	if repo.gotListSort != "users.email DESC" {
		t.Fatalf("unexpected sort: %s", repo.gotListSort)
	}

	cases := map[string]string{
		"email":       "users.email ASC",
		"-email":      "users.email DESC",
		"first_name":  "users.first_name ASC",
		"-first_name": "users.first_name DESC",
		"last_name":   "users.last_name ASC",
		"-last_name":  "users.last_name DESC",
		"created_at":  "users.created_at ASC",
		"-created_at": "users.created_at DESC",
		"updated_at":  "users.updated_at ASC",
		"-updated_at": "users.updated_at DESC",
		"bad":         "",
		"":            "",
	}
	for in, want := range cases {
		if got := normalizeUserSort(in); got != want {
			t.Fatalf("normalizeUserSort(%q)=%q want %q", in, got, want)
		}
	}
}
