package repository

import (
	"baron-sso-backend/internal/domain"
	"context"
	"errors"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestUserProjectionRepository_ReplaceAllFromKratosMarksReadyWithoutDeletingUsersMissingFromPartialList(t *testing.T) {
	ctx := context.Background()
	repo := NewUserProjectionRepository(testDB)

	require.NoError(t, testDB.Exec("DELETE FROM user_projection_states").Error)
	require.NoError(t, testDB.Exec("DELETE FROM user_login_ids").Error)
	require.NoError(t, testDB.Exec("DELETE FROM users").Error)

	tenantID := "10000000-0000-0000-0000-000000000001"
	tenantSlug := "projection-saman"
	require.NoError(t, testDB.Create(&domain.Tenant{
		ID:     tenantID,
		Name:   "Projection Saman",
		Slug:   tenantSlug,
		Type:   domain.TenantTypeCompany,
		Status: domain.TenantStatusActive,
	}).Error)
	existing := &domain.User{
		ID:          "00000000-0000-0000-0000-000000000099",
		Email:       "existing@example.com",
		Name:        "Existing",
		CompanyCode: tenantSlug,
		TenantID:    &tenantID,
	}
	require.NoError(t, NewUserRepository(testDB).Create(ctx, existing))

	users := []domain.User{
		{
			ID:          "00000000-0000-0000-0000-000000000101",
			Email:       "one@example.com",
			Name:        "One",
			CompanyCode: tenantSlug,
			TenantID:    &tenantID,
			CreatedAt:   time.Now(),
			UpdatedAt:   time.Now(),
		},
		{
			ID:        "00000000-0000-0000-0000-000000000102",
			Email:     "two@example.com",
			Name:      "Two",
			TenantID:  &tenantID,
			CreatedAt: time.Now(),
			UpdatedAt: time.Now(),
		},
	}

	require.NoError(t, repo.ReplaceAllFromKratos(ctx, users))

	ready, err := repo.IsReady(ctx)
	require.NoError(t, err)
	assert.True(t, ready)

	counts, err := repo.CountTenantMembers(ctx, []domain.Tenant{
		{ID: tenantID, Slug: tenantSlug},
	})
	require.NoError(t, err)
	assert.Equal(t, int64(3), counts[tenantID])

	var activeCount int64
	require.NoError(t, testDB.Model(&domain.User{}).Count(&activeCount).Error)
	assert.Equal(t, int64(3), activeCount)

	var existingCount int64
	require.NoError(t, testDB.Model(&domain.User{}).Where("id = ?", existing.ID).Count(&existingCount).Error)
	assert.Equal(t, int64(1), existingCount)

	var existingRow domain.User
	require.NoError(t, testDB.Unscoped().First(&existingRow, "id = ?", existing.ID).Error)
	assert.False(t, existingRow.DeletedAt.Valid)
}

func TestUserProjectionRepository_CountTenantMembersRecursiveIncludesDescendantsAndExcludesSoftDeletedUsers(t *testing.T) {
	ctx := context.Background()
	repo := NewUserProjectionRepository(testDB)

	parentID := "20000000-0000-0000-0000-000000000001"
	childID := "20000000-0000-0000-0000-000000000002"
	grandchildID := "20000000-0000-0000-0000-000000000003"
	siblingID := "20000000-0000-0000-0000-000000000004"
	tenantIDs := []string{parentID, childID, grandchildID, siblingID}

	require.NoError(t, testDB.Exec("DELETE FROM user_login_ids").Error)
	require.NoError(t, testDB.Exec("DELETE FROM users").Error)
	require.NoError(t, testDB.Unscoped().Where("id IN ?", tenantIDs).Delete(&domain.Tenant{}).Error)

	require.NoError(t, testDB.Create(&domain.Tenant{
		ID:     parentID,
		Name:   "Recursive Parent",
		Slug:   "recursive-parent",
		Type:   domain.TenantTypeCompany,
		Status: domain.TenantStatusActive,
	}).Error)
	require.NoError(t, testDB.Create(&domain.Tenant{
		ID:       childID,
		Name:     "Recursive Child",
		Slug:     "recursive-child",
		Type:     domain.TenantTypeOrganization,
		Status:   domain.TenantStatusActive,
		ParentID: &parentID,
	}).Error)
	require.NoError(t, testDB.Create(&domain.Tenant{
		ID:       grandchildID,
		Name:     "Recursive Grandchild",
		Slug:     "recursive-grandchild",
		Type:     domain.TenantTypeUserGroup,
		Status:   domain.TenantStatusActive,
		ParentID: &childID,
	}).Error)
	require.NoError(t, testDB.Create(&domain.Tenant{
		ID:     siblingID,
		Name:   "Recursive Sibling",
		Slug:   "recursive-sibling",
		Type:   domain.TenantTypeCompany,
		Status: domain.TenantStatusActive,
	}).Error)

	users := []domain.User{
		{ID: "21000000-0000-0000-0000-000000000001", Email: "parent@example.com", Name: "Parent", TenantID: &parentID},
		{ID: "21000000-0000-0000-0000-000000000002", Email: "child@example.com", Name: "Child", TenantID: &childID},
		{ID: "21000000-0000-0000-0000-000000000003", Email: "grandchild@example.com", Name: "Grandchild", TenantID: &grandchildID},
		{ID: "21000000-0000-0000-0000-000000000004", Email: "deleted-grandchild@example.com", Name: "Deleted Grandchild", TenantID: &grandchildID},
		{ID: "21000000-0000-0000-0000-000000000005", Email: "sibling@example.com", Name: "Sibling", TenantID: &siblingID},
	}
	for i := range users {
		require.NoError(t, testDB.Create(&users[i]).Error)
	}
	require.NoError(t, testDB.Delete(&domain.User{}, "id = ?", users[3].ID).Error)

	directCounts, err := repo.CountTenantMembers(ctx, []domain.Tenant{{ID: parentID}, {ID: childID}, {ID: grandchildID}, {ID: siblingID}})
	require.NoError(t, err)
	assert.Equal(t, int64(1), directCounts[parentID])
	assert.Equal(t, int64(1), directCounts[childID])
	assert.Equal(t, int64(1), directCounts[grandchildID])
	assert.Equal(t, int64(1), directCounts[siblingID])

	recursiveCounts, err := repo.CountTenantMembersRecursive(ctx, []domain.Tenant{{ID: parentID}, {ID: childID}, {ID: grandchildID}, {ID: siblingID}})
	require.NoError(t, err)
	assert.Equal(t, int64(3), recursiveCounts[parentID])
	assert.Equal(t, int64(2), recursiveCounts[childID])
	assert.Equal(t, int64(1), recursiveCounts[grandchildID])
	assert.Equal(t, int64(1), recursiveCounts[siblingID])
}

func TestUserProjectionRepository_MarkFailedMakesProjectionNotReady(t *testing.T) {
	ctx := context.Background()
	repo := NewUserProjectionRepository(testDB)

	require.NoError(t, testDB.Exec("DELETE FROM user_projection_states").Error)

	require.NoError(t, repo.MarkFailed(ctx, errors.New("kratos down")))

	ready, err := repo.IsReady(ctx)
	require.NoError(t, err)
	assert.False(t, ready)
}
