package service

import (
	"context"
	"errors"
	"testing"

	"wucher/internal/domain/auth"
)

type roleServiceRepoMock struct {
	createCalled       bool
	updateCalled       bool
	deleteCalled       bool
	getByIDCalled      bool
	getByNameCalled    bool
	listCalled         bool
	getPermByIDCalled  bool
	getPermByKeyCalled bool
	updatePermCalled   bool
	assignPermCalled   bool
	removePermCalled   bool
	listPermCalled     bool

	lastCreate             *auth.Role
	lastUpdate             *auth.Role
	lastDelete             []byte
	lastListFilter         string
	lastListSort           string
	lastListLimit          int
	lastListOffset         int
	lastGetPermID          []byte
	lastGetPermKey         string
	lastAssignRoleID       []byte
	lastAssignPermissionID []byte
	lastRemoveRoleID       []byte
	lastRemovePermissionID []byte
	lastPermFilter         string
	lastPermSort           string
	lastPermLimit          int
	lastPermOffset         int

	roleByID             *auth.Role
	roleByName           *auth.Role
	listRoles            []auth.Role
	listTotal            int64
	permissionByID       *auth.Permission
	permissionByKey      *auth.Permission
	assignedPermission   *auth.RolePermission
	listPermissions      []auth.Permission
	listPermissionsTotal int64

	createErr       error
	updateErr       error
	deleteErr       error
	getByIDErr      error
	getByNameErr    error
	listErr         error
	getPermByIDErr  error
	getPermByKeyErr error
	updatePermErr   error
	assignPermErr   error
	removePermErr   error
	listPermErr     error
}

func (m *roleServiceRepoMock) CreateRole(_ context.Context, role *auth.Role) error {
	m.createCalled = true
	m.lastCreate = role
	return m.createErr
}

func (m *roleServiceRepoMock) UpdateRole(_ context.Context, role *auth.Role) error {
	m.updateCalled = true
	m.lastUpdate = role
	return m.updateErr
}

func (m *roleServiceRepoMock) DeleteRole(_ context.Context, id []byte) error {
	m.deleteCalled = true
	m.lastDelete = id
	return m.deleteErr
}

func (m *roleServiceRepoMock) GetRoleByID(_ context.Context, _ []byte) (*auth.Role, error) {
	m.getByIDCalled = true
	return m.roleByID, m.getByIDErr
}

func (m *roleServiceRepoMock) GetRoleByName(_ context.Context, _ string) (*auth.Role, error) {
	m.getByNameCalled = true
	return m.roleByName, m.getByNameErr
}

func (m *roleServiceRepoMock) ListRoles(_ context.Context, filterName string, sort string, limit, offset int) ([]auth.Role, int64, error) {
	m.listCalled = true
	m.lastListFilter = filterName
	m.lastListSort = sort
	m.lastListLimit = limit
	m.lastListOffset = offset
	return m.listRoles, m.listTotal, m.listErr
}

func (m *roleServiceRepoMock) GetPermissionByID(_ context.Context, id []byte) (*auth.Permission, error) {
	m.getPermByIDCalled = true
	m.lastGetPermID = id
	return m.permissionByID, m.getPermByIDErr
}

func (m *roleServiceRepoMock) GetPermissionByKey(_ context.Context, key string) (*auth.Permission, error) {
	m.getPermByKeyCalled = true
	m.lastGetPermKey = key
	return m.permissionByKey, m.getPermByKeyErr
}

func (m *roleServiceRepoMock) UpdatePermission(_ context.Context, permission *auth.Permission) error {
	m.updatePermCalled = true
	m.permissionByID = permission
	return m.updatePermErr
}

func (m *roleServiceRepoMock) ListPermissions(_ context.Context, filter, sort string, limit, offset int) ([]auth.Permission, int64, error) {
	m.listPermCalled = true
	m.lastPermFilter = filter
	m.lastPermSort = sort
	m.lastPermLimit = limit
	m.lastPermOffset = offset
	return m.listPermissions, m.listPermissionsTotal, m.listPermErr
}
func (m *roleServiceRepoMock) ListPermissionsByRoleID(_ context.Context, _ []byte) ([]auth.Permission, error) {
	return []auth.Permission{}, nil
}

func (m *roleServiceRepoMock) HasRolePermission(_ context.Context, _ []byte, _ string) (bool, error) {
	return true, nil
}

func (m *roleServiceRepoMock) AssignPermission(_ context.Context, roleID, permissionID []byte) (*auth.RolePermission, error) {
	m.assignPermCalled = true
	m.lastAssignRoleID = roleID
	m.lastAssignPermissionID = permissionID
	if m.assignedPermission == nil {
		m.assignedPermission = &auth.RolePermission{RoleID: roleID, PermissionID: permissionID}
	}
	return m.assignedPermission, m.assignPermErr
}

func (m *roleServiceRepoMock) RemovePermission(_ context.Context, roleID []byte, permissionID []byte) error {
	m.removePermCalled = true
	m.lastRemoveRoleID = roleID
	m.lastRemovePermissionID = permissionID
	return m.removePermErr
}

func TestRoleService_Create_Validation(t *testing.T) {
	repo := &roleServiceRepoMock{}
	svc := NewRoleService(repo)

	if err := svc.Create(context.Background(), &auth.Role{Name: "   "}); err == nil {
		t.Fatalf("expected validation error")
	}
	if repo.createCalled {
		t.Fatalf("expected repo.CreateRole not called")
	}
}

func TestRoleService_Create_RepoError(t *testing.T) {
	repo := &roleServiceRepoMock{createErr: errors.New("db error")}
	svc := NewRoleService(repo)

	err := svc.Create(context.Background(), &auth.Role{Name: "admin"})
	if err == nil {
		t.Fatalf("expected error")
	}
	if !repo.createCalled {
		t.Fatalf("expected repo.CreateRole called")
	}
}

func TestRoleService_Update_Validation(t *testing.T) {
	repo := &roleServiceRepoMock{}
	svc := NewRoleService(repo)

	if err := svc.Update(context.Background(), &auth.Role{Name: ""}); err == nil {
		t.Fatalf("expected validation error")
	}
	if repo.updateCalled {
		t.Fatalf("expected repo.UpdateRole not called")
	}
}

func TestRoleService_Update_Success(t *testing.T) {
	repo := &roleServiceRepoMock{}
	svc := NewRoleService(repo)

	err := svc.Update(context.Background(), &auth.Role{Name: "admin"})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !repo.updateCalled {
		t.Fatalf("expected repo.UpdateRole called")
	}
}

func TestRoleService_Delete_Success(t *testing.T) {
	repo := &roleServiceRepoMock{}
	svc := NewRoleService(repo)

	id := []byte("id")
	err := svc.Delete(context.Background(), id)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !repo.deleteCalled {
		t.Fatalf("expected repo.DeleteRole called")
	}
	if string(repo.lastDelete) != string(id) {
		t.Fatalf("expected delete id passed to repo")
	}
}

func TestRoleService_GetByID_Success(t *testing.T) {
	repo := &roleServiceRepoMock{roleByID: &auth.Role{Name: "admin"}}
	svc := NewRoleService(repo)

	role, err := svc.GetByID(context.Background(), []byte("id"))
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if role == nil || role.Name != "admin" {
		t.Fatalf("unexpected role")
	}
	if !repo.getByIDCalled {
		t.Fatalf("expected repo.GetRoleByID called")
	}
}

func TestRoleService_GetByName_Success(t *testing.T) {
	repo := &roleServiceRepoMock{roleByName: &auth.Role{Name: "admin"}}
	svc := NewRoleService(repo)

	role, err := svc.GetByName(context.Background(), "admin")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if role == nil || role.Name != "admin" {
		t.Fatalf("unexpected role")
	}
	if !repo.getByNameCalled {
		t.Fatalf("expected repo.GetRoleByName called")
	}
}

func TestRoleService_GetPermissionByID(t *testing.T) {
	t.Run("success", func(t *testing.T) {
		perm := &auth.Permission{Name: "Users Read", Key: "users.read"}
		repo := &roleServiceRepoMock{permissionByID: perm}
		svc := NewRoleService(repo)

		id := []byte("perm-id")
		got, err := svc.GetPermissionByID(context.Background(), id)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if got == nil || got.Key != "users.read" {
			t.Fatalf("unexpected permission: %+v", got)
		}
		if !repo.getPermByIDCalled {
			t.Fatalf("expected repo.GetPermissionByID called")
		}
		if string(repo.lastGetPermID) != string(id) {
			t.Fatalf("expected id forwarded to repo")
		}
	})

	t.Run("repo error", func(t *testing.T) {
		repo := &roleServiceRepoMock{getPermByIDErr: errors.New("db error")}
		svc := NewRoleService(repo)

		if _, err := svc.GetPermissionByID(context.Background(), []byte("perm-id")); err == nil {
			t.Fatalf("expected error")
		}
	})
}

func TestRoleService_GetPermissionByKey(t *testing.T) {
	repo := &roleServiceRepoMock{
		permissionByKey: &auth.Permission{Name: "Delete User", Key: "user.delete"},
	}
	svc := NewRoleService(repo)

	got, err := svc.GetPermissionByKey(context.Background(), "user.delete")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if got == nil || got.Key != "user.delete" {
		t.Fatalf("unexpected permission: %+v", got)
	}
	if !repo.getPermByKeyCalled {
		t.Fatalf("expected repo.GetPermissionByKey called")
	}
	if repo.lastGetPermKey != "user.delete" {
		t.Fatalf("expected key forwarded to repo")
	}
}

func TestRoleService_UpdatePermission(t *testing.T) {
	t.Run("validation", func(t *testing.T) {
		repo := &roleServiceRepoMock{}
		svc := NewRoleService(repo)

		if err := svc.UpdatePermission(context.Background(), &auth.Permission{Name: "   ", Key: "user.delete"}); err == nil {
			t.Fatalf("expected validation error for empty name")
		}
		if err := svc.UpdatePermission(context.Background(), &auth.Permission{Name: "Delete User", Key: "   "}); err == nil {
			t.Fatalf("expected validation error for empty key")
		}
		if repo.updatePermCalled {
			t.Fatalf("expected repo.UpdatePermission not called")
		}
	})

	t.Run("success", func(t *testing.T) {
		repo := &roleServiceRepoMock{}
		svc := NewRoleService(repo)

		perm := &auth.Permission{Name: "Delete User", Key: "user.delete", RequiresPIN: true}
		if err := svc.UpdatePermission(context.Background(), perm); err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if !repo.updatePermCalled {
			t.Fatalf("expected repo.UpdatePermission called")
		}
		if repo.permissionByID == nil || !repo.permissionByID.RequiresPIN {
			t.Fatalf("expected permission forwarded to repo")
		}
	})
}

func TestRoleService_AssignPermission(t *testing.T) {
	t.Run("success", func(t *testing.T) {
		roleID := []byte("role-id")
		permID := []byte("perm-id")
		repo := &roleServiceRepoMock{}
		svc := NewRoleService(repo)

		rp, err := svc.AssignPermission(context.Background(), roleID, permID)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if rp == nil {
			t.Fatalf("expected role permission")
		}
		if !repo.assignPermCalled {
			t.Fatalf("expected repo.AssignPermission called")
		}
		if string(repo.lastAssignRoleID) != string(roleID) || string(repo.lastAssignPermissionID) != string(permID) {
			t.Fatalf("expected roleID/permissionID forwarded to repo")
		}
	})

	t.Run("repo error", func(t *testing.T) {
		repo := &roleServiceRepoMock{assignPermErr: errors.New("assign error")}
		svc := NewRoleService(repo)

		if _, err := svc.AssignPermission(context.Background(), []byte("role-id"), []byte("perm-id")); err == nil {
			t.Fatalf("expected error")
		}
	})
}

func TestRoleService_RemovePermission(t *testing.T) {
	t.Run("success", func(t *testing.T) {
		roleID := []byte("role-id")
		permID := []byte("perm-id")
		repo := &roleServiceRepoMock{}
		svc := NewRoleService(repo)

		err := svc.RemovePermission(context.Background(), roleID, permID)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if !repo.removePermCalled {
			t.Fatalf("expected repo.RemovePermission called")
		}
		if string(repo.lastRemoveRoleID) != string(roleID) || string(repo.lastRemovePermissionID) != string(permID) {
			t.Fatalf("expected roleID/permissionID forwarded to repo")
		}
	})

	t.Run("repo error", func(t *testing.T) {
		repo := &roleServiceRepoMock{removePermErr: errors.New("remove error")}
		svc := NewRoleService(repo)

		if err := svc.RemovePermission(context.Background(), []byte("role-id"), []byte("perm-id")); err == nil {
			t.Fatalf("expected error")
		}
	})
}

func TestRoleService_ListPermissions(t *testing.T) {
	t.Run("normalize sort and forward args", func(t *testing.T) {
		repo := &roleServiceRepoMock{
			listPermissions:      []auth.Permission{{Name: "Users Read", Key: "users.read"}},
			listPermissionsTotal: 1,
		}
		svc := NewRoleService(repo)

		perms, total, err := svc.ListPermissions(context.Background(), "users", "-key", 10, 20)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if len(perms) != 1 || perms[0].Key != "users.read" || total != 1 {
			t.Fatalf("unexpected list result: perms=%+v total=%d", perms, total)
		}
		if !repo.listPermCalled {
			t.Fatalf("expected repo.ListPermissions called")
		}
		if repo.lastPermFilter != "users" {
			t.Fatalf("expected filter forwarded")
		}
		if repo.lastPermSort != "`key` DESC" {
			t.Fatalf("expected normalized permission sort, got %q", repo.lastPermSort)
		}
		if repo.lastPermLimit != 10 || repo.lastPermOffset != 20 {
			t.Fatalf("expected limit/offset forwarded")
		}
	})

	t.Run("repo error", func(t *testing.T) {
		repo := &roleServiceRepoMock{listPermErr: errors.New("list error")}
		svc := NewRoleService(repo)

		if _, _, err := svc.ListPermissions(context.Background(), "", "bad", 10, 0); err == nil {
			t.Fatalf("expected error")
		}
		if repo.lastPermSort != "" {
			t.Fatalf("unexpected sort for invalid input, got %q", repo.lastPermSort)
		}
	})
}

func TestRoleService_List_NormalizeSort(t *testing.T) {
	repo := &roleServiceRepoMock{}
	svc := NewRoleService(repo)

	_, _, err := svc.List(context.Background(), "adm", "-created_at", 10, 20)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !repo.listCalled {
		t.Fatalf("expected repo.ListRoles called")
	}
	if repo.lastListFilter != "adm" {
		t.Fatalf("expected filter passed to repo")
	}
	if repo.lastListSort != "created_at DESC" {
		t.Fatalf("expected normalized sort, got %q", repo.lastListSort)
	}
	if repo.lastListLimit != 10 || repo.lastListOffset != 20 {
		t.Fatalf("expected limit/offset passed to repo")
	}
}

func TestNormalizeSort(t *testing.T) {
	cases := map[string]string{
		"name":        "name ASC",
		"-name":       "name DESC",
		"created_at":  "created_at ASC",
		"-created_at": "created_at DESC",
		"updated_at":  "updated_at ASC",
		"-updated_at": "updated_at DESC",
		"":            "",
		"bad":         "",
	}
	for in, want := range cases {
		if got := normalizeSort(in); got != want {
			t.Fatalf("normalizeSort(%q) = %q, want %q", in, got, want)
		}
	}
}

func TestNormalizePermissionSort(t *testing.T) {
	cases := map[string]string{
		"name":        "name ASC",
		"-name":       "name DESC",
		"key":         "`key` ASC",
		"-key":        "`key` DESC",
		"created_at":  "created_at ASC",
		"-created_at": "created_at DESC",
		"updated_at":  "updated_at ASC",
		"-updated_at": "updated_at DESC",
		"":            "",
		"bad":         "",
	}
	for in, want := range cases {
		if got := normalizePermissionSort(in); got != want {
			t.Fatalf("normalizePermissionSort(%q) = %q, want %q", in, got, want)
		}
	}
}
