package repository

import (
	"context"
	"testing"

	"github.com/user-management-system/internal/domain"
)

func containsInt64(values []int64, target int64) bool {
	for _, value := range values {
		if value == target {
			return true
		}
	}
	return false
}

func TestRoleRepositoryLifecycleAndQueries(t *testing.T) {
	db := openTestDB(t)
	repo := NewRoleRepository(db)
	ctx := context.Background()

	admin := &domain.Role{
		Name:        "Admin Test",
		Code:        "admin-test",
		Description: "root role",
		Level:       1,
		IsSystem:    true,
		Status:      domain.RoleStatusEnabled,
	}
	if err := repo.Create(ctx, admin); err != nil {
		t.Fatalf("Create(admin) failed: %v", err)
	}

	parentID := admin.ID
	auditor := &domain.Role{
		Name:        "Auditor Test",
		Code:        "auditor-test",
		Description: "audit role",
		ParentID:    &parentID,
		Level:       2,
		IsDefault:   true,
		Status:      domain.RoleStatusDisabled,
	}
	viewer := &domain.Role{
		Name:        "Viewer Test",
		Code:        "viewer-test",
		Description: "view role",
		Level:       1,
		Status:      domain.RoleStatusEnabled,
	}

	for _, role := range []*domain.Role{auditor, viewer} {
		if err := repo.Create(ctx, role); err != nil {
			t.Fatalf("Create(%s) failed: %v", role.Code, err)
		}
	}

	loadedByID, err := repo.GetByID(ctx, admin.ID)
	if err != nil {
		t.Fatalf("GetByID failed: %v", err)
	}
	if loadedByID.Code != "admin-test" {
		t.Fatalf("expected admin-test, got %q", loadedByID.Code)
	}

	loadedByCode, err := repo.GetByCode(ctx, "auditor-test")
	if err != nil {
		t.Fatalf("GetByCode failed: %v", err)
	}
	if loadedByCode.ID != auditor.ID {
		t.Fatalf("expected auditor id %d, got %d", auditor.ID, loadedByCode.ID)
	}

	allRoles, total, err := repo.List(ctx, 0, 10)
	if err != nil {
		t.Fatalf("List failed: %v", err)
	}
	if total != 3 || len(allRoles) != 3 {
		t.Fatalf("expected 3 roles, got total=%d len=%d", total, len(allRoles))
	}

	enabledRoles, total, err := repo.ListByStatus(ctx, domain.RoleStatusEnabled, 0, 10)
	if err != nil {
		t.Fatalf("ListByStatus failed: %v", err)
	}
	if total != 2 || len(enabledRoles) != 2 {
		t.Fatalf("expected 2 enabled roles, got total=%d len=%d", total, len(enabledRoles))
	}

	defaultRoles, err := repo.GetDefaultRoles(ctx)
	if err != nil {
		t.Fatalf("GetDefaultRoles failed: %v", err)
	}
	if len(defaultRoles) != 1 || defaultRoles[0].ID != auditor.ID {
		t.Fatalf("expected auditor as default role, got %+v", defaultRoles)
	}

	exists, err := repo.ExistsByCode(ctx, "viewer-test")
	if err != nil {
		t.Fatalf("ExistsByCode(viewer-test) failed: %v", err)
	}
	if !exists {
		t.Fatal("expected viewer-test to exist")
	}

	missing, err := repo.ExistsByCode(ctx, "missing-role")
	if err != nil {
		t.Fatalf("ExistsByCode(missing-role) failed: %v", err)
	}
	if missing {
		t.Fatal("expected missing-role to be absent")
	}

	auditor.Description = "audit role updated"
	if err := repo.Update(ctx, auditor); err != nil {
		t.Fatalf("Update failed: %v", err)
	}

	if err := repo.UpdateStatus(ctx, auditor.ID, domain.RoleStatusEnabled); err != nil {
		t.Fatalf("UpdateStatus failed: %v", err)
	}

	searchResults, total, err := repo.Search(ctx, "audit", 0, 10)
	if err != nil {
		t.Fatalf("Search failed: %v", err)
	}
	if total != 1 || len(searchResults) != 1 || searchResults[0].ID != auditor.ID {
		t.Fatalf("expected auditor search hit, got total=%d len=%d", total, len(searchResults))
	}

	childRoles, err := repo.ListByParentID(ctx, admin.ID)
	if err != nil {
		t.Fatalf("ListByParentID failed: %v", err)
	}
	if len(childRoles) != 1 || childRoles[0].ID != auditor.ID {
		t.Fatalf("expected auditor child role, got %+v", childRoles)
	}

	roleSubset, err := repo.GetByIDs(ctx, []int64{admin.ID, auditor.ID})
	if err != nil {
		t.Fatalf("GetByIDs failed: %v", err)
	}
	if len(roleSubset) != 2 {
		t.Fatalf("expected 2 roles from GetByIDs, got %d", len(roleSubset))
	}

	emptySubset, err := repo.GetByIDs(ctx, []int64{})
	if err != nil {
		t.Fatalf("GetByIDs(empty) failed: %v", err)
	}
	if len(emptySubset) != 0 {
		t.Fatalf("expected empty slice for GetByIDs(empty), got %d", len(emptySubset))
	}

	if err := repo.Delete(ctx, viewer.ID); err != nil {
		t.Fatalf("Delete failed: %v", err)
	}

	if _, err := repo.GetByID(ctx, viewer.ID); err == nil {
		t.Fatal("expected deleted role lookup to fail")
	}
}

func TestPermissionRepositoryLifecycleAndQueries(t *testing.T) {
	db := openTestDB(t)
	repo := NewPermissionRepository(db)
	ctx := context.Background()

	parent := &domain.Permission{
		Name:        "Dashboard",
		Code:        "dashboard:view",
		Type:        domain.PermissionTypeMenu,
		Description: "dashboard menu",
		Path:        "/dashboard",
		Sort:        1,
		Status:      domain.PermissionStatusEnabled,
	}
	if err := repo.Create(ctx, parent); err != nil {
		t.Fatalf("Create(parent) failed: %v", err)
	}

	parentID := parent.ID
	apiPermission := &domain.Permission{
		Name:        "Audit API",
		Code:        "audit:read",
		Type:        domain.PermissionTypeAPI,
		Description: "audit api",
		ParentID:    &parentID,
		Path:        "/api/audit",
		Method:      "GET",
		Sort:        2,
		Status:      domain.PermissionStatusDisabled,
	}
	buttonPermission := &domain.Permission{
		Name:        "Audit Button",
		Code:        "audit:button",
		Type:        domain.PermissionTypeButton,
		Description: "audit action",
		Sort:        3,
		Status:      domain.PermissionStatusEnabled,
	}

	for _, permission := range []*domain.Permission{apiPermission, buttonPermission} {
		if err := repo.Create(ctx, permission); err != nil {
			t.Fatalf("Create(%s) failed: %v", permission.Code, err)
		}
	}

	role := &domain.Role{
		Name:        "Permission Role",
		Code:        "permission-role",
		Description: "role for permission join queries",
		Status:      domain.RoleStatusEnabled,
	}
	if err := db.WithContext(ctx).Create(role).Error; err != nil {
		t.Fatalf("create role for permission joins failed: %v", err)
	}

	for _, rolePermission := range []*domain.RolePermission{
		{RoleID: role.ID, PermissionID: parent.ID},
		{RoleID: role.ID, PermissionID: apiPermission.ID},
	} {
		if err := db.WithContext(ctx).Create(rolePermission).Error; err != nil {
			t.Fatalf("create role_permission failed: %v", err)
		}
	}

	loadedByID, err := repo.GetByID(ctx, parent.ID)
	if err != nil {
		t.Fatalf("GetByID failed: %v", err)
	}
	if loadedByID.Code != "dashboard:view" {
		t.Fatalf("expected dashboard:view, got %q", loadedByID.Code)
	}

	loadedByCode, err := repo.GetByCode(ctx, "audit:read")
	if err != nil {
		t.Fatalf("GetByCode failed: %v", err)
	}
	if loadedByCode.ID != apiPermission.ID {
		t.Fatalf("expected audit:read id %d, got %d", apiPermission.ID, loadedByCode.ID)
	}

	allPermissions, total, err := repo.List(ctx, 0, 10)
	if err != nil {
		t.Fatalf("List failed: %v", err)
	}
	if total != 3 || len(allPermissions) != 3 {
		t.Fatalf("expected 3 permissions, got total=%d len=%d", total, len(allPermissions))
	}

	apiPermissions, total, err := repo.ListByType(ctx, domain.PermissionTypeAPI, 0, 10)
	if err != nil {
		t.Fatalf("ListByType failed: %v", err)
	}
	if total != 1 || len(apiPermissions) != 1 || apiPermissions[0].ID != apiPermission.ID {
		t.Fatalf("expected audit api permission, got total=%d len=%d", total, len(apiPermissions))
	}

	enabledPermissions, total, err := repo.ListByStatus(ctx, domain.PermissionStatusEnabled, 0, 10)
	if err != nil {
		t.Fatalf("ListByStatus failed: %v", err)
	}
	if total != 2 || len(enabledPermissions) != 2 {
		t.Fatalf("expected 2 enabled permissions, got total=%d len=%d", total, len(enabledPermissions))
	}

	rolePermissions, err := repo.GetByRoleIDs(ctx, []int64{role.ID})
	if err != nil {
		t.Fatalf("GetByRoleIDs failed: %v", err)
	}
	if len(rolePermissions) != 1 || rolePermissions[0].ID != parent.ID {
		t.Fatalf("expected only enabled parent permission in join query, got %+v", rolePermissions)
	}

	exists, err := repo.ExistsByCode(ctx, "audit:button")
	if err != nil {
		t.Fatalf("ExistsByCode(audit:button) failed: %v", err)
	}
	if !exists {
		t.Fatal("expected audit:button to exist")
	}

	missing, err := repo.ExistsByCode(ctx, "permission:missing")
	if err != nil {
		t.Fatalf("ExistsByCode(missing) failed: %v", err)
	}
	if missing {
		t.Fatal("expected permission:missing to be absent")
	}

	apiPermission.Description = "audit api updated"
	if err := repo.Update(ctx, apiPermission); err != nil {
		t.Fatalf("Update failed: %v", err)
	}

	if err := repo.UpdateStatus(ctx, apiPermission.ID, domain.PermissionStatusEnabled); err != nil {
		t.Fatalf("UpdateStatus failed: %v", err)
	}

	searchResults, total, err := repo.Search(ctx, "audit", 0, 10)
	if err != nil {
		t.Fatalf("Search failed: %v", err)
	}
	if total != 2 || len(searchResults) != 2 {
		t.Fatalf("expected 2 audit-related permissions, got total=%d len=%d", total, len(searchResults))
	}

	childPermissions, err := repo.ListByParentID(ctx, parent.ID)
	if err != nil {
		t.Fatalf("ListByParentID failed: %v", err)
	}
	if len(childPermissions) != 1 || childPermissions[0].ID != apiPermission.ID {
		t.Fatalf("expected api permission child, got %+v", childPermissions)
	}

	permissionSubset, err := repo.GetByIDs(ctx, []int64{parent.ID, apiPermission.ID})
	if err != nil {
		t.Fatalf("GetByIDs failed: %v", err)
	}
	if len(permissionSubset) != 2 {
		t.Fatalf("expected 2 permissions from GetByIDs, got %d", len(permissionSubset))
	}

	emptySubset, err := repo.GetByIDs(ctx, []int64{})
	if err != nil {
		t.Fatalf("GetByIDs(empty) failed: %v", err)
	}
	if len(emptySubset) != 0 {
		t.Fatalf("expected empty slice for GetByIDs(empty), got %d", len(emptySubset))
	}

	if err := repo.Delete(ctx, buttonPermission.ID); err != nil {
		t.Fatalf("Delete failed: %v", err)
	}

	if _, err := repo.GetByID(ctx, buttonPermission.ID); err == nil {
		t.Fatal("expected deleted permission lookup to fail")
	}
}

func TestUserRoleAndRolePermissionRepositoriesLifecycle(t *testing.T) {
	db := openTestDB(t)
	userRoleRepo := NewUserRoleRepository(db)
	rolePermissionRepo := NewRolePermissionRepository(db)
	ctx := context.Background()

	users := []*domain.User{
		{Username: "repo-user-1", Password: "hash", Status: domain.UserStatusActive},
		{Username: "repo-user-2", Password: "hash", Status: domain.UserStatusActive},
	}
	for _, user := range users {
		if err := db.WithContext(ctx).Create(user).Error; err != nil {
			t.Fatalf("create user failed: %v", err)
		}
	}

	roles := []*domain.Role{
		{Name: "Repo Role 1", Code: "repo-role-1", Status: domain.RoleStatusEnabled},
		{Name: "Repo Role 2", Code: "repo-role-2", Status: domain.RoleStatusEnabled},
	}
	for _, role := range roles {
		if err := db.WithContext(ctx).Create(role).Error; err != nil {
			t.Fatalf("create role failed: %v", err)
		}
	}

	permissions := []*domain.Permission{
		{Name: "Repo Permission 1", Code: "repo:permission:1", Type: domain.PermissionTypeAPI, Status: domain.PermissionStatusEnabled},
		{Name: "Repo Permission 2", Code: "repo:permission:2", Type: domain.PermissionTypeAPI, Status: domain.PermissionStatusEnabled},
	}
	for _, permission := range permissions {
		if err := db.WithContext(ctx).Create(permission).Error; err != nil {
			t.Fatalf("create permission failed: %v", err)
		}
	}

	userRolePrimary := &domain.UserRole{UserID: users[0].ID, RoleID: roles[0].ID}
	if err := userRoleRepo.Create(ctx, userRolePrimary); err != nil {
		t.Fatalf("UserRole Create failed: %v", err)
	}

	if err := userRoleRepo.BatchCreate(ctx, []*domain.UserRole{}); err != nil {
		t.Fatalf("UserRole BatchCreate(empty) failed: %v", err)
	}

	userRoleBatch := []*domain.UserRole{
		{UserID: users[0].ID, RoleID: roles[1].ID},
		{UserID: users[1].ID, RoleID: roles[0].ID},
	}
	if err := userRoleRepo.BatchCreate(ctx, userRoleBatch); err != nil {
		t.Fatalf("UserRole BatchCreate failed: %v", err)
	}

	exists, err := userRoleRepo.Exists(ctx, users[0].ID, roles[0].ID)
	if err != nil {
		t.Fatalf("UserRole Exists failed: %v", err)
	}
	if !exists {
		t.Fatal("expected primary user-role relation to exist")
	}

	missing, err := userRoleRepo.Exists(ctx, users[1].ID, roles[1].ID)
	if err != nil {
		t.Fatalf("UserRole Exists(missing) failed: %v", err)
	}
	if missing {
		t.Fatal("expected missing user-role relation to be absent")
	}

	rolesForUserOne, err := userRoleRepo.GetByUserID(ctx, users[0].ID)
	if err != nil {
		t.Fatalf("GetByUserID failed: %v", err)
	}
	if len(rolesForUserOne) != 2 {
		t.Fatalf("expected 2 roles for user one, got %d", len(rolesForUserOne))
	}

	usersForRoleOne, err := userRoleRepo.GetByRoleID(ctx, roles[0].ID)
	if err != nil {
		t.Fatalf("GetByRoleID failed: %v", err)
	}
	if len(usersForRoleOne) != 2 {
		t.Fatalf("expected 2 users for role one, got %d", len(usersForRoleOne))
	}

	roleIDs, err := userRoleRepo.GetRoleIDsByUserID(ctx, users[0].ID)
	if err != nil {
		t.Fatalf("GetRoleIDsByUserID failed: %v", err)
	}
	if len(roleIDs) != 2 || !containsInt64(roleIDs, roles[0].ID) || !containsInt64(roleIDs, roles[1].ID) {
		t.Fatalf("unexpected role IDs for user one: %+v", roleIDs)
	}

	userIDs, err := userRoleRepo.GetUserIDByRoleID(ctx, roles[0].ID)
	if err != nil {
		t.Fatalf("GetUserIDByRoleID failed: %v", err)
	}
	if len(userIDs) != 2 || !containsInt64(userIDs, users[0].ID) || !containsInt64(userIDs, users[1].ID) {
		t.Fatalf("unexpected user IDs for role one: %+v", userIDs)
	}

	if err := userRoleRepo.BatchDelete(ctx, []*domain.UserRole{}); err != nil {
		t.Fatalf("UserRole BatchDelete(empty) failed: %v", err)
	}

	if err := userRoleRepo.BatchDelete(ctx, []*domain.UserRole{userRoleBatch[0]}); err != nil {
		t.Fatalf("UserRole BatchDelete failed: %v", err)
	}

	if err := userRoleRepo.Delete(ctx, userRolePrimary.ID); err != nil {
		t.Fatalf("UserRole Delete failed: %v", err)
	}

	existsAfterDelete, err := userRoleRepo.Exists(ctx, users[0].ID, roles[0].ID)
	if err != nil {
		t.Fatalf("UserRole Exists after Delete failed: %v", err)
	}
	if existsAfterDelete {
		t.Fatal("expected primary user-role relation to be removed")
	}

	if err := userRoleRepo.DeleteByUserID(ctx, users[1].ID); err != nil {
		t.Fatalf("DeleteByUserID failed: %v", err)
	}

	if err := userRoleRepo.Create(ctx, &domain.UserRole{UserID: users[0].ID, RoleID: roles[1].ID}); err != nil {
		t.Fatalf("recreate user-role failed: %v", err)
	}
	if err := userRoleRepo.DeleteByRoleID(ctx, roles[1].ID); err != nil {
		t.Fatalf("DeleteByRoleID failed: %v", err)
	}

	remainingUserRoles, err := userRoleRepo.GetByRoleID(ctx, roles[1].ID)
	if err != nil {
		t.Fatalf("GetByRoleID after DeleteByRoleID failed: %v", err)
	}
	if len(remainingUserRoles) != 0 {
		t.Fatalf("expected no user-role relations for role two, got %d", len(remainingUserRoles))
	}

	rolePermissionPrimary := &domain.RolePermission{RoleID: roles[0].ID, PermissionID: permissions[0].ID}
	if err := rolePermissionRepo.Create(ctx, rolePermissionPrimary); err != nil {
		t.Fatalf("RolePermission Create failed: %v", err)
	}

	if err := rolePermissionRepo.BatchCreate(ctx, []*domain.RolePermission{}); err != nil {
		t.Fatalf("RolePermission BatchCreate(empty) failed: %v", err)
	}

	rolePermissionBatch := []*domain.RolePermission{
		{RoleID: roles[0].ID, PermissionID: permissions[1].ID},
		{RoleID: roles[1].ID, PermissionID: permissions[0].ID},
	}
	if err := rolePermissionRepo.BatchCreate(ctx, rolePermissionBatch); err != nil {
		t.Fatalf("RolePermission BatchCreate failed: %v", err)
	}

	rpExists, err := rolePermissionRepo.Exists(ctx, roles[0].ID, permissions[0].ID)
	if err != nil {
		t.Fatalf("RolePermission Exists failed: %v", err)
	}
	if !rpExists {
		t.Fatal("expected primary role-permission relation to exist")
	}

	rpMissing, err := rolePermissionRepo.Exists(ctx, roles[1].ID, permissions[1].ID)
	if err != nil {
		t.Fatalf("RolePermission Exists(missing) failed: %v", err)
	}
	if rpMissing {
		t.Fatal("expected missing role-permission relation to be absent")
	}

	permissionsForRoleOne, err := rolePermissionRepo.GetByRoleID(ctx, roles[0].ID)
	if err != nil {
		t.Fatalf("GetByRoleID failed: %v", err)
	}
	if len(permissionsForRoleOne) != 2 {
		t.Fatalf("expected 2 permissions for role one, got %d", len(permissionsForRoleOne))
	}

	rolesForPermissionOne, err := rolePermissionRepo.GetByPermissionID(ctx, permissions[0].ID)
	if err != nil {
		t.Fatalf("GetByPermissionID failed: %v", err)
	}
	if len(rolesForPermissionOne) != 2 {
		t.Fatalf("expected 2 roles for permission one, got %d", len(rolesForPermissionOne))
	}

	permissionIDs, err := rolePermissionRepo.GetPermissionIDsByRoleID(ctx, roles[0].ID)
	if err != nil {
		t.Fatalf("GetPermissionIDsByRoleID failed: %v", err)
	}
	if len(permissionIDs) != 2 || !containsInt64(permissionIDs, permissions[0].ID) || !containsInt64(permissionIDs, permissions[1].ID) {
		t.Fatalf("unexpected permission IDs for role one: %+v", permissionIDs)
	}

	roleIDsByPermission, err := rolePermissionRepo.GetRoleIDByPermissionID(ctx, permissions[0].ID)
	if err != nil {
		t.Fatalf("GetRoleIDByPermissionID failed: %v", err)
	}
	if len(roleIDsByPermission) != 2 || !containsInt64(roleIDsByPermission, roles[0].ID) || !containsInt64(roleIDsByPermission, roles[1].ID) {
		t.Fatalf("unexpected role IDs for permission one: %+v", roleIDsByPermission)
	}

	loadedPermission, err := rolePermissionRepo.GetPermissionByID(ctx, permissions[0].ID)
	if err != nil {
		t.Fatalf("GetPermissionByID failed: %v", err)
	}
	if loadedPermission.Code != "repo:permission:1" {
		t.Fatalf("expected repo:permission:1, got %q", loadedPermission.Code)
	}

	permissionIDsByRoleIDs, err := rolePermissionRepo.GetPermissionIDsByRoleIDs(ctx, []int64{roles[0].ID, roles[1].ID})
	if err != nil {
		t.Fatalf("GetPermissionIDsByRoleIDs failed: %v", err)
	}
	if len(permissionIDsByRoleIDs) != 3 {
		t.Fatalf("expected 3 permission IDs from combined roles, got %d", len(permissionIDsByRoleIDs))
	}

	emptyPermissionIDs, err := rolePermissionRepo.GetPermissionIDsByRoleIDs(ctx, []int64{})
	if err != nil {
		t.Fatalf("GetPermissionIDsByRoleIDs(empty) failed: %v", err)
	}
	if len(emptyPermissionIDs) != 0 {
		t.Fatalf("expected empty slice for GetPermissionIDsByRoleIDs(empty), got %d", len(emptyPermissionIDs))
	}

	if err := rolePermissionRepo.BatchDelete(ctx, []*domain.RolePermission{}); err != nil {
		t.Fatalf("RolePermission BatchDelete(empty) failed: %v", err)
	}

	if err := rolePermissionRepo.BatchDelete(ctx, []*domain.RolePermission{rolePermissionBatch[0]}); err != nil {
		t.Fatalf("RolePermission BatchDelete failed: %v", err)
	}

	if err := rolePermissionRepo.Delete(ctx, rolePermissionPrimary.ID); err != nil {
		t.Fatalf("RolePermission Delete failed: %v", err)
	}

	if err := rolePermissionRepo.DeleteByPermissionID(ctx, permissions[0].ID); err != nil {
		t.Fatalf("DeleteByPermissionID failed: %v", err)
	}

	if err := rolePermissionRepo.Create(ctx, &domain.RolePermission{RoleID: roles[0].ID, PermissionID: permissions[1].ID}); err != nil {
		t.Fatalf("recreate role-permission failed: %v", err)
	}
	if err := rolePermissionRepo.DeleteByRoleID(ctx, roles[0].ID); err != nil {
		t.Fatalf("DeleteByRoleID failed: %v", err)
	}

	remainingRolePermissions, err := rolePermissionRepo.GetByRoleID(ctx, roles[0].ID)
	if err != nil {
		t.Fatalf("GetByRoleID after DeleteByRoleID failed: %v", err)
	}
	if len(remainingRolePermissions) != 0 {
		t.Fatalf("expected no role-permission relations for role one, got %d", len(remainingRolePermissions))
	}
}
