package repository

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

	"github.com/google/uuid"
	"github.com/lib/pq"
	"github.com/stretchr/testify/require"
	"gorm.io/gorm"
)

func TestCheckDataIntegrityDetectsTenantAndUserProblems(t *testing.T) {
	ctx := context.Background()
	suffix := uuid.NewString()

	parent := domain.Tenant{
		ID:     uuid.NewString(),
		Name:   "Deleted Parent " + suffix,
		Slug:   "deleted-parent-" + suffix,
		Type:   domain.TenantTypeCompany,
		Status: domain.TenantStatusActive,
	}
	child := domain.Tenant{
		ID:       uuid.NewString(),
		Name:     "Orphan Child " + suffix,
		Slug:     "orphan-child-" + suffix,
		Type:     domain.TenantTypeOrganization,
		ParentID: &parent.ID,
		Status:   domain.TenantStatusActive,
	}
	dupA := domain.Tenant{
		ID:     uuid.NewString(),
		Name:   "Duplicate A " + suffix,
		Slug:   "Dup-" + suffix,
		Type:   domain.TenantTypeCompany,
		Status: domain.TenantStatusActive,
	}
	dupB := domain.Tenant{
		ID:     uuid.NewString(),
		Name:   "Duplicate B " + suffix,
		Slug:   "dup-" + suffix,
		Type:   domain.TenantTypeCompany,
		Status: domain.TenantStatusActive,
	}

	require.NoError(t, testDB.Create(&parent).Error)
	require.NoError(t, testDB.Create(&child).Error)
	require.NoError(t, testDB.Create(&dupA).Error)
	require.NoError(t, testDB.Create(&dupB).Error)
	require.NoError(t, testDB.Delete(&domain.Tenant{}, "id = ?", parent.ID).Error)

	orphanUser := domain.User{
		ID:        uuid.NewString(),
		Email:     "orphan-" + suffix + "@example.com",
		Name:      "Orphan User",
		Role:      domain.RoleUser,
		TenantID:  &parent.ID,
		Status:    domain.UserStatusActive,
		CreatedAt: time.Now().UTC(),
		UpdatedAt: time.Now().UTC(),
	}
	deletedLoginUser := domain.User{
		ID:        uuid.NewString(),
		Email:     "deleted-login-user-" + suffix + "@example.com",
		Name:      "Deleted Login User",
		Role:      domain.RoleUser,
		TenantID:  &child.ID,
		Status:    domain.UserStatusActive,
		CreatedAt: time.Now().UTC(),
		UpdatedAt: time.Now().UTC(),
	}
	require.NoError(t, testDB.Create(&orphanUser).Error)
	require.NoError(t, testDB.Create(&deletedLoginUser).Error)
	require.NoError(t, testDB.Create(&domain.UserLoginID{
		ID:       uuid.NewString(),
		UserID:   orphanUser.ID,
		TenantID: parent.ID,
		FieldKey: "emp_id",
		LoginID:  "EMP-" + suffix,
	}).Error)
	require.NoError(t, testDB.Create(&domain.UserLoginID{
		ID:       uuid.NewString(),
		UserID:   deletedLoginUser.ID,
		TenantID: child.ID,
		FieldKey: "emp_id",
		LoginID:  "MISSING-" + suffix,
	}).Error)
	require.NoError(t, testDB.Delete(&domain.User{}, "id = ?", deletedLoginUser.ID).Error)

	report, err := CheckDataIntegrity(ctx, testDB)
	require.NoError(t, err)
	require.Equal(t, domain.DataIntegrityStatusFail, report.Status)
	require.Equal(t, int64(5), report.Summary.Failures) // Reverted back to 5 due to successful soft delete simulation

	requireIntegrityCheck(t, report, "tenant_integrity", "duplicate_tenant_slugs", domain.DataIntegrityStatusFail, 1)
	requireIntegrityCheck(t, report, "tenant_integrity", "orphan_tenant_parents", domain.DataIntegrityStatusFail, 1)
	requireIntegrityCheck(t, report, "user_integrity", "orphan_user_tenant_memberships", domain.DataIntegrityStatusFail, 1)
	requireIntegrityCheck(t, report, "user_integrity", "orphan_user_login_id_tenants", domain.DataIntegrityStatusFail, 1)
	requireIntegrityCheck(t, report, "user_integrity", "orphan_user_login_id_users", domain.DataIntegrityStatusFail, 1)
}

func TestCheckDataIntegrityDetectsHardOrphanUserLoginIDRows(t *testing.T) {
	ctx := context.Background()
	suffix := uuid.NewString()
	rollback := errors.New("rollback hard orphan fixture")

	err := testDB.Transaction(func(tx *gorm.DB) error {
		var constraintNames []string
		if err := tx.Raw(`
SELECT conname
FROM pg_constraint
WHERE conrelid = 'user_login_ids'::regclass
  AND contype = 'f'
`).Scan(&constraintNames).Error; err != nil {
			return err
		}

		for _, constraintName := range constraintNames {
			statement := fmt.Sprintf("ALTER TABLE user_login_ids DROP CONSTRAINT %s", pq.QuoteIdentifier(constraintName))
			if err := tx.Exec(statement).Error; err != nil {
				return err
			}
		}

		before, err := CheckDataIntegrity(ctx, tx)
		if err != nil {
			return err
		}
		beforeTenantCount, err := integrityCheckCount(before, "user_integrity", "orphan_user_login_id_tenants")
		if err != nil {
			return err
		}
		beforeUserCount, err := integrityCheckCount(before, "user_integrity", "orphan_user_login_id_users")
		if err != nil {
			return err
		}

		if err := tx.Create(&domain.UserLoginID{
			ID:       uuid.NewString(),
			UserID:   uuid.NewString(),
			TenantID: uuid.NewString(),
			FieldKey: "emp_id",
			LoginID:  "HARD-ORPHAN-" + suffix,
		}).Error; err != nil {
			return err
		}

		report, err := CheckDataIntegrity(ctx, tx)
		if err != nil {
			return err
		}
		if err := expectIntegrityCheck(report, "user_integrity", "orphan_user_login_id_tenants", domain.DataIntegrityStatusFail, beforeTenantCount+1); err != nil {
			return err
		}
		if err := expectIntegrityCheck(report, "user_integrity", "orphan_user_login_id_users", domain.DataIntegrityStatusFail, beforeUserCount+1); err != nil {
			return err
		}

		return rollback
	})
	require.ErrorIs(t, err, rollback)
}

func TestListAndDeleteOrphanUserLoginIDsOnlyDeletesRevalidatedTargets(t *testing.T) {
	ctx := context.Background()
	suffix := uuid.NewString()

	validTenant := domain.Tenant{
		ID:     uuid.NewString(),
		Name:   "Valid Tenant " + suffix,
		Slug:   "valid-tenant-" + suffix,
		Type:   domain.TenantTypeCompany,
		Status: domain.TenantStatusActive,
	}
	deletedTenant := domain.Tenant{
		ID:     uuid.NewString(),
		Name:   "Deleted Tenant " + suffix,
		Slug:   "deleted-tenant-" + suffix,
		Type:   domain.TenantTypeCompany,
		Status: domain.TenantStatusActive,
	}
	require.NoError(t, testDB.Create(&validTenant).Error)
	require.NoError(t, testDB.Create(&deletedTenant).Error)

	validUser := domain.User{
		ID:        uuid.NewString(),
		Email:     "valid-login-" + suffix + "@example.com",
		Name:      "Valid Login User",
		Role:      domain.RoleUser,
		TenantID:  &validTenant.ID,
		Status:    domain.UserStatusActive,
		CreatedAt: time.Now().UTC(),
		UpdatedAt: time.Now().UTC(),
	}
	deletedUser := domain.User{
		ID:        uuid.NewString(),
		Email:     "deleted-login-" + suffix + "@example.com",
		Name:      "Deleted Login User",
		Role:      domain.RoleUser,
		TenantID:  &validTenant.ID,
		Status:    domain.UserStatusActive,
		CreatedAt: time.Now().UTC(),
		UpdatedAt: time.Now().UTC(),
	}
	require.NoError(t, testDB.Create(&validUser).Error)
	require.NoError(t, testDB.Create(&deletedUser).Error)

	validLogin := domain.UserLoginID{
		ID:       uuid.NewString(),
		UserID:   validUser.ID,
		TenantID: validTenant.ID,
		FieldKey: "emp_id",
		LoginID:  "VALID-" + suffix,
	}
	deletedTenantLogin := domain.UserLoginID{
		ID:       uuid.NewString(),
		UserID:   validUser.ID,
		TenantID: deletedTenant.ID,
		FieldKey: "emp_id",
		LoginID:  "DELETED-TENANT-" + suffix,
	}
	deletedUserLogin := domain.UserLoginID{
		ID:       uuid.NewString(),
		UserID:   deletedUser.ID,
		TenantID: validTenant.ID,
		FieldKey: "emp_id",
		LoginID:  "DELETED-USER-" + suffix,
	}
	require.NoError(t, testDB.Create(&validLogin).Error)
	require.NoError(t, testDB.Create(&deletedTenantLogin).Error)
	require.NoError(t, testDB.Create(&deletedUserLogin).Error)
	require.NoError(t, testDB.Delete(&domain.Tenant{}, "id = ?", deletedTenant.ID).Error)
	require.NoError(t, testDB.Delete(&domain.User{}, "id = ?", deletedUser.ID).Error)

	items, err := ListOrphanUserLoginIDs(ctx, testDB, nil)
	require.NoError(t, err)
	orphanReasons := map[string][]string{}
	for _, item := range items {
		orphanReasons[item.ID] = item.Reasons
	}
	require.Equal(t, []string{"deleted_tenant"}, orphanReasons[deletedTenantLogin.ID])
	require.Equal(t, []string{"deleted_user"}, orphanReasons[deletedUserLogin.ID])
	require.NotContains(t, orphanReasons, validLogin.ID)

	result, err := DeleteOrphanUserLoginIDs(ctx, testDB, []string{
		deletedTenantLogin.ID,
		validLogin.ID,
		"00000000-0000-0000-0000-000000000000",
	})
	require.NoError(t, err)
	require.Equal(t, int64(1), result.DeletedCount)
	require.Len(t, result.Deleted, 1)
	require.Equal(t, deletedTenantLogin.ID, result.Deleted[0].ID)
	require.ElementsMatch(t, []string{
		validLogin.ID,
		"00000000-0000-0000-0000-000000000000",
	}, result.SkippedIDs)

	var deletedTenantLoginCount int64
	require.NoError(t, testDB.Model(&domain.UserLoginID{}).Where("id = ?", deletedTenantLogin.ID).Count(&deletedTenantLoginCount).Error)
	require.Equal(t, int64(0), deletedTenantLoginCount)

	var validLoginCount int64
	require.NoError(t, testDB.Model(&domain.UserLoginID{}).Where("id = ?", validLogin.ID).Count(&validLoginCount).Error)
	require.Equal(t, int64(1), validLoginCount)
}

func requireIntegrityCheck(t *testing.T, report domain.DataIntegrityReport, sectionKey, checkKey string, status domain.DataIntegrityStatus, count int64) {
	t.Helper()
	require.NoError(t, expectIntegrityCheck(report, sectionKey, checkKey, status, count))
}

func expectIntegrityCheck(report domain.DataIntegrityReport, sectionKey, checkKey string, status domain.DataIntegrityStatus, count int64) error {
	check, ok := findIntegrityCheck(report, sectionKey, checkKey)
	if !ok {
		return fmt.Errorf("integrity check %s/%s not found", sectionKey, checkKey)
	}
	if check.Status != status {
		return fmt.Errorf("integrity check %s/%s status = %s, want %s", sectionKey, checkKey, check.Status, status)
	}
	if check.Count != count {
		return fmt.Errorf("integrity check %s/%s count = %d, want %d", sectionKey, checkKey, check.Count, count)
	}
	return nil
}

func integrityCheckCount(report domain.DataIntegrityReport, sectionKey, checkKey string) (int64, error) {
	check, ok := findIntegrityCheck(report, sectionKey, checkKey)
	if !ok {
		return 0, fmt.Errorf("integrity check %s/%s not found", sectionKey, checkKey)
	}
	return check.Count, nil
}

func findIntegrityCheck(report domain.DataIntegrityReport, sectionKey, checkKey string) (domain.DataIntegrityCheck, bool) {
	for _, section := range report.Sections {
		if section.Key != sectionKey {
			continue
		}
		for _, check := range section.Checks {
			if check.Key == checkKey {
				return check, true
			}
		}
	}
	return domain.DataIntegrityCheck{}, false
}
