package persistence

import (
	"context"
	"testing"

	"github.com/DATA-DOG/go-sqlmock"
	apperrors "github.com/nexuscrm/backend/pkg/errors"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestGetEffectiveSystemPermissions_SSoTReadPath(t *testing.T) {
	db, mock, err := sqlmock.New()
	require.NoError(t, err)
	defer db.Close()

	repo := NewPermissionRepository(db)
	ctx := context.Background()

	profileID := "profile-finance"
	userID := "user-finance"

	// Layer 1: All PermissionSet permissions from SSoT (_System_PermissionSetPermission)
	mock.ExpectQuery("(?s)SELECT psp\\..*FROM _System_PermissionSetPermission psp.*WHERE.*PermissionSet.*ProfileId.*IsOwnedByProfile.*PermissionSetAssignment.*").
		WithArgs(profileID, userID).
		WillReturnRows(
			sqlmock.NewRows([]string{"PermissionAPIName", "IsEnabled"}).
				AddRow("ViewSetup", true).
				AddRow("RunReports", true).
				AddRow("CustomizeApplication", true),
		)

	// Layer 2: PSG component (normalized rows with per-PSG grouping)
	mock.ExpectQuery("(?s)SELECT.*GroupId.*FROM _System_PermissionSetPermission psp.*PermissionSetGroupComponent.*").
		WithArgs(userID).
		WillReturnRows(sqlmock.NewRows([]string{"GroupId", "PermissionAPIName", "IsEnabled"}))

	// Muting query (per-PSG muting)
	mock.ExpectQuery("(?s)SELECT.*GroupId.*FROM _System_MutingPermissionSetPermission mpp.*").
		WithArgs(userID).
		WillReturnRows(sqlmock.NewRows([]string{"GroupId", "PermissionAPIName"}))

	perms, err := repo.GetEffectiveSystemPermissions(ctx, userID, profileID)
	require.NoError(t, err)

	assert.True(t, perms[string(constants.PermViewSetup)])
	assert.True(t, perms[string(constants.PermRunReports)])
	assert.True(t, perms[string(constants.PermCustomizeApplication)])
	assert.False(t, perms[string(constants.PermManageUsers)])

	require.NoError(t, mock.ExpectationsWereMet())
}

func TestGetPermissionSetGrants_ReturnsNotFoundWhenPermissionSetMissing(t *testing.T) {
	db, mock, err := sqlmock.New()
	require.NoError(t, err)
	defer db.Close()

	repo := NewPermissionRepository(db)
	ctx := context.Background()

	mock.ExpectQuery("SELECT `PermissionApiName`, `IsEnabled` FROM `_System_PermissionSetPermission` WHERE `PermissionSetId` = \\?").
		WithArgs("ps-missing").
		WillReturnRows(sqlmock.NewRows([]string{"PermissionAPIName", "IsEnabled"}))
	mock.ExpectQuery("SELECT `Id` FROM `PermissionSet` WHERE `Id` = \\? AND `IsDeleted` = 0 LIMIT 1").
		WithArgs("ps-missing").
		WillReturnRows(sqlmock.NewRows([]string{"Id"}))

	grants, err := repo.GetPermissionSetGrants(ctx, "ps-missing")
	require.Nil(t, grants)
	var notFoundErr *apperrors.NotFoundError
	require.ErrorAs(t, err, &notFoundErr)
	assert.Equal(t, "PermissionSet", notFoundErr.Resource)
	assert.Equal(t, "ps-missing", notFoundErr.ID)
	require.NoError(t, mock.ExpectationsWereMet())
}

func TestGetPermissionSetGrants_ReturnsEmptyMapWhenPermissionSetExistsWithoutGrantRows(t *testing.T) {
	db, mock, err := sqlmock.New()
	require.NoError(t, err)
	defer db.Close()

	repo := NewPermissionRepository(db)
	ctx := context.Background()

	mock.ExpectQuery("SELECT `PermissionApiName`, `IsEnabled` FROM `_System_PermissionSetPermission` WHERE `PermissionSetId` = \\?").
		WithArgs("ps-empty").
		WillReturnRows(sqlmock.NewRows([]string{"PermissionAPIName", "IsEnabled"}))
	mock.ExpectQuery("SELECT `Id` FROM `PermissionSet` WHERE `Id` = \\? AND `IsDeleted` = 0 LIMIT 1").
		WithArgs("ps-empty").
		WillReturnRows(sqlmock.NewRows([]string{"Id"}).AddRow("ps-empty"))

	grants, err := repo.GetPermissionSetGrants(ctx, "ps-empty")
	require.NoError(t, err)
	assert.Equal(t, map[string]bool{}, grants)
	require.NoError(t, mock.ExpectationsWereMet())
}

func TestLockPermissionSet_ReturnsFalseWhenPermissionSetIsMissing(t *testing.T) {
	db, mock, err := sqlmock.New()
	require.NoError(t, err)
	defer db.Close()

	repo := NewPermissionRepository(db)
	ctx := context.Background()

	mock.ExpectQuery("SELECT `Id` FROM `PermissionSet` WHERE `Id` = \\? AND `IsDeleted` = 0 FOR UPDATE").
		WithArgs("ps-missing").
		WillReturnRows(sqlmock.NewRows([]string{"Id"}))

	exists, err := repo.LockPermissionSet(ctx, "ps-missing")
	require.NoError(t, err)
	assert.False(t, exists)
	require.NoError(t, mock.ExpectationsWereMet())
}
