package repository

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

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

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

	// Ensure User table exists and clean for tests
	_ = testDB.AutoMigrate(&domain.User{})

	t.Run("Create and FindByEmail", func(t *testing.T) {
		user := &domain.User{
			Email: "test@example.com",
			Name:  "Test User",
			Role:  "user",
		}

		err := repo.Create(ctx, user)
		assert.NoError(t, err)
		assert.NotEmpty(t, user.ID)

		found, err := repo.FindByEmail(ctx, "test@example.com")
		assert.NoError(t, err)
		assert.Equal(t, user.ID, found.ID)
		assert.Equal(t, "Test User", found.Name)
	})

	t.Run("Update User Info", func(t *testing.T) {
		user := &domain.User{
			Email: "update@example.com",
			Name:  "Before Update",
			Role:  "user",
		}
		_ = repo.Create(ctx, user)

		user.Name = "After Update"
		user.Phone = "010-1234-5678"
		err := repo.Update(ctx, user)
		assert.NoError(t, err)

		found, err := repo.FindByEmail(ctx, "update@example.com")
		assert.NoError(t, err)
		assert.Equal(t, "After Update", found.Name)
		assert.Equal(t, "010-1234-5678", found.Phone)
	})

	t.Run("Update preserves archived email reservation", func(t *testing.T) {
		testDB.Exec("DELETE FROM user_login_ids")
		testDB.Exec("DELETE FROM users")

		archived := &domain.User{
			ID:     "00000000-0000-0000-0000-00000000a001",
			Email:  "reserved@example.com",
			Name:   "Archived User",
			Role:   domain.RoleUser,
			Status: domain.UserStatusArchived,
		}
		replacement := &domain.User{
			ID:     "00000000-0000-0000-0000-00000000a002",
			Email:  "reserved@example.com",
			Name:   "Replacement User",
			Role:   domain.RoleUser,
			Status: domain.UserStatusActive,
		}
		require.NoError(t, repo.Create(ctx, archived))

		err := repo.Update(ctx, replacement)

		require.Error(t, err)
		require.Contains(t, err.Error(), "archived user")
		found, err := repo.FindByEmail(ctx, archived.Email)
		require.NoError(t, err)
		require.Equal(t, archived.ID, found.ID)
		require.Equal(t, domain.UserStatusArchived, found.Status)
	})

	t.Run("List Users with Search", func(t *testing.T) {
		// Add some users
		_ = repo.Create(ctx, &domain.User{Email: "alice@test.com", Name: "Alice", Role: "user"})
		_ = repo.Create(ctx, &domain.User{Email: "bob@test.com", Name: "Bob", Role: "user"})

		users, total, _, err := repo.List(ctx, 0, 10, "Alice", []string{}, "")
		assert.NoError(t, err)
		assert.True(t, total >= 1)
		assert.Equal(t, "Alice", users[0].Name)
	})

	t.Run("Delete User", func(t *testing.T) {
		user := &domain.User{Email: "delete@example.com", Name: "To Delete"}
		_ = repo.Create(ctx, user)

		err := repo.Delete(ctx, user.ID)
		assert.NoError(t, err)

		found, err := repo.FindByEmail(ctx, "delete@example.com")
		assert.Error(t, err) // Should not be found
		assert.Nil(t, found)
	})

	t.Run("CountByCompanyCodes", func(t *testing.T) {
		// Clean start for this subtest
		testDB.Exec("DELETE FROM user_login_ids")
		testDB.Exec("DELETE FROM users")
		testDB.Exec("DELETE FROM tenant_domains")
		tenantA := createUserRepositoryTestTenant(t, "tenant-a")
		tenantB := createUserRepositoryTestTenant(t, "tenant-b")

		users := []domain.User{
			{Email: "u1@a.com", Name: "U1", TenantID: &tenantA.ID},
			{Email: "u2@a.com", Name: "U2", TenantID: &tenantA.ID},
			{Email: "u3@b.com", Name: "U3", TenantID: &tenantB.ID},
			{Email: "u4@none.com", Name: "U4"},
		}
		for _, u := range users {
			_ = repo.Create(ctx, &u)
		}

		counts, err := repo.CountByCompanyCodes(ctx, []string{"tenant-a", "tenant-b", "tenant-c"})
		assert.NoError(t, err)
		assert.Equal(t, int64(2), counts["tenant-a"])
		assert.Equal(t, int64(1), counts["tenant-b"])
		assert.Equal(t, int64(0), counts["tenant-c"])
	})

	t.Run("CountByCompanyCodes excludes soft deleted cache rows", func(t *testing.T) {
		testDB.Exec("DELETE FROM user_login_ids")
		testDB.Exec("DELETE FROM users")
		testDB.Exec("DELETE FROM tenant_domains")
		tenantA := createUserRepositoryTestTenant(t, "tenant-a")

		active := &domain.User{Email: "active@a.com", Name: "Active", TenantID: &tenantA.ID}
		deleted := &domain.User{Email: "deleted@a.com", Name: "Deleted", TenantID: &tenantA.ID}
		secondDeleted := &domain.User{Email: "second-deleted@a.com", Name: "Second Deleted", TenantID: &tenantA.ID}

		assert.NoError(t, repo.Create(ctx, active))
		assert.NoError(t, repo.Create(ctx, deleted))
		assert.NoError(t, repo.Create(ctx, secondDeleted))
		assert.NoError(t, repo.Delete(ctx, deleted.ID))
		assert.NoError(t, repo.Delete(ctx, secondDeleted.ID))

		counts, err := repo.CountByCompanyCodes(ctx, []string{"tenant-a"})

		assert.NoError(t, err)
		assert.Equal(t, int64(1), counts["tenant-a"])
	})

	t.Run("Multi-Identifier Support", func(t *testing.T) {
		_ = testDB.AutoMigrate(&domain.UserLoginID{})
		testDB.Exec("DELETE FROM user_login_ids")
		testDB.Exec("DELETE FROM users")

		user := &domain.User{Email: "multi@test.com", Name: "Multi"}
		_ = repo.Create(ctx, user)

		t1 := "00000000-0000-0000-0000-000000000001"
		t2 := "00000000-0000-0000-0000-000000000002"

		loginIDs := []domain.UserLoginID{
			{UserID: user.ID, TenantID: t1, FieldKey: "emp_id", LoginID: "E001"},
			{UserID: user.ID, TenantID: t2, FieldKey: "student_id", LoginID: "S001"},
		}

		err := repo.UpdateUserLoginIDs(ctx, user.ID, loginIDs)
		assert.NoError(t, err)

		// Get and Verify
		saved, err := repo.GetUserLoginIDs(ctx, user.ID)
		assert.NoError(t, err)
		assert.Len(t, saved, 2)

		// IsLoginIDTaken
		taken, err := repo.IsLoginIDTaken(ctx, "E001")
		assert.NoError(t, err)
		assert.True(t, taken)

		taken, err = repo.IsLoginIDTaken(ctx, "UNKNOWN")
		assert.NoError(t, err)
		assert.False(t, taken)

		// FindTenantIDByLoginID
		tid, err := repo.FindTenantIDByLoginID(ctx, "S001")
		assert.NoError(t, err)
		assert.Equal(t, t2, tid)

		// Update (Replace)
		newList := []domain.UserLoginID{
			{UserID: user.ID, TenantID: t1, FieldKey: "emp_id", LoginID: "E002"},
		}
		err = repo.UpdateUserLoginIDs(ctx, user.ID, newList)
		assert.NoError(t, err)

		saved, _ = repo.GetUserLoginIDs(ctx, user.ID)
		assert.Len(t, saved, 1)
		assert.Equal(t, "E002", saved[0].LoginID)
	})
}

func TestUserRepository_ListIncludesAdditionalTenantAppointments(t *testing.T) {
	repo := NewUserRepository(testDB)
	ctx := context.Background()
	require.NoError(t, testDB.Exec("DELETE FROM user_login_ids").Error)
	require.NoError(t, testDB.Exec("DELETE FROM users").Error)

	primaryTenant := createUserRepositoryTestTenant(t, "repo-primary-tenant")
	additionalTenant := createUserRepositoryTestTenant(t, "repo-additional-tenant")
	primaryTenantID := primaryTenant.ID
	additionalTenantID := additionalTenant.ID
	users := []domain.User{
		{
			ID:       uuid.NewString(),
			Email:    "primary-member@example.com",
			Name:     "Primary Member",
			Role:     domain.RoleUser,
			TenantID: &additionalTenantID,
		},
		{
			ID:       uuid.NewString(),
			Email:    "additional-member@example.com",
			Name:     "Additional Member",
			Role:     domain.RoleUser,
			TenantID: &primaryTenantID,
			Metadata: domain.JSONMap{
				"additionalAppointments": []any{
					map[string]any{
						"tenantId":   additionalTenant.ID,
						"tenantSlug": additionalTenant.Slug,
						"tenantName": additionalTenant.Name,
						"isPrimary":  false,
					},
				},
			},
		},
	}
	for i := range users {
		require.NoError(t, repo.Create(ctx, &users[i]))
	}

	listed, total, _, err := repo.List(ctx, 0, 20, "", []string{additionalTenant.ID}, "")

	require.NoError(t, err)
	require.Equal(t, int64(2), total)
	require.Len(t, listed, 2)
	emails := []string{listed[0].Email, listed[1].Email}
	assert.Contains(t, emails, "primary-member@example.com")
	assert.Contains(t, emails, "additional-member@example.com")

	counts, err := repo.CountByTenantIDs(ctx, []string{additionalTenant.ID})
	require.NoError(t, err)
	assert.Equal(t, int64(2), counts[additionalTenant.ID])
}

func createUserRepositoryTestTenant(t *testing.T, slug string) domain.Tenant {
	t.Helper()
	require.NoError(t, testDB.Unscoped().Where("slug = ?", slug).Delete(&domain.Tenant{}).Error)
	tenant := domain.Tenant{
		ID:     uuid.NewString(),
		Name:   "Tenant " + slug,
		Slug:   slug,
		Type:   domain.TenantTypeCompany,
		Status: domain.TenantStatusActive,
	}
	require.NoError(t, testDB.Create(&tenant).Error)
	return tenant
}
