package service

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

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

type fakeUserProjectionRepo struct {
	replacedUsers []domain.User
	failedErr     error
	replaceErr    error
}

func (f *fakeUserProjectionRepo) IsReady(ctx context.Context) (bool, error) {
	return false, nil
}

func (f *fakeUserProjectionRepo) GetStatus(ctx context.Context) (domain.UserProjectionStatus, error) {
	return domain.UserProjectionStatus{}, nil
}

func (f *fakeUserProjectionRepo) CountTenantMembers(ctx context.Context, tenants []domain.Tenant) (map[string]int64, error) {
	return nil, nil
}

func (f *fakeUserProjectionRepo) CountTenantMembersRecursive(ctx context.Context, tenants []domain.Tenant) (map[string]int64, error) {
	return nil, nil
}

func (f *fakeUserProjectionRepo) ReplaceAllFromKratos(ctx context.Context, users []domain.User) error {
	f.replacedUsers = append([]domain.User(nil), users...)
	return f.replaceErr
}

func (f *fakeUserProjectionRepo) MarkFailed(ctx context.Context, syncErr error) error {
	f.failedErr = syncErr
	return nil
}

func TestUserProjectionSyncService_ReconcileReplacesProjectionFromKratos(t *testing.T) {
	ctx := context.Background()
	kratos := new(MockKratosAdminServiceShared)
	repo := &fakeUserProjectionRepo{}
	svc := NewUserProjectionSyncService(kratos, repo)

	tenantID := "00000000-0000-0000-0000-000000000001"
	kratos.On("ListIdentities", ctx).Return([]KratosIdentity{
		{
			ID: "00000000-0000-0000-0000-000000000101",
			Traits: map[string]any{
				"email":        "one@example.com",
				"name":         "One",
				"phone_number": "+821012345678",
				"companyCode":  "saman",
				"companyCodes": []any{"saman", "group-a"},
				"tenant_id":    tenantID,
				"department":   "DX",
				"customAttr":   "kept",
			},
			State: "active",
		},
	}, nil).Once()

	count, err := svc.Reconcile(ctx)

	require.NoError(t, err)
	assert.Equal(t, 1, count)
	require.Len(t, repo.replacedUsers, 1)
	assert.Equal(t, "one@example.com", repo.replacedUsers[0].Email)
	assert.Equal(t, "One", repo.replacedUsers[0].Name)
	assert.Equal(t, "+821012345678", repo.replacedUsers[0].Phone)
	assert.Empty(t, repo.replacedUsers[0].CompanyCode)
	assert.Empty(t, repo.replacedUsers[0].CompanyCodes)
	require.NotNil(t, repo.replacedUsers[0].TenantID)
	assert.Equal(t, tenantID, *repo.replacedUsers[0].TenantID)
	assert.Equal(t, "kept", repo.replacedUsers[0].Metadata["customAttr"])
	assert.NoError(t, repo.failedErr)
	kratos.AssertExpectations(t)
}

func TestUserProjectionSyncService_ReconcileDeduplicatesKoreanCountryCodePhone(t *testing.T) {
	ctx := context.Background()
	kratos := new(MockKratosAdminServiceShared)
	repo := &fakeUserProjectionRepo{}
	svc := NewUserProjectionSyncService(kratos, repo)

	kratos.On("ListIdentities", ctx).Return([]KratosIdentity{
		{
			ID: "00000000-0000-0000-0000-000000000102",
			Traits: map[string]any{
				"email":        "two@example.com",
				"name":         "Two",
				"phone_number": "+82 +821091917771",
			},
			State: "active",
		},
	}, nil).Once()

	count, err := svc.Reconcile(ctx)

	require.NoError(t, err)
	assert.Equal(t, 1, count)
	require.Len(t, repo.replacedUsers, 1)
	assert.Equal(t, "+821091917771", repo.replacedUsers[0].Phone)
	kratos.AssertExpectations(t)
}

func TestUserProjectionSyncService_ReconcileMarksFailedWhenKratosFails(t *testing.T) {
	ctx := context.Background()
	kratos := new(MockKratosAdminServiceShared)
	repo := &fakeUserProjectionRepo{}
	svc := NewUserProjectionSyncService(kratos, repo)

	expectedErr := errors.New("kratos down")
	kratos.On("ListIdentities", ctx).Return([]KratosIdentity{}, expectedErr).Once()

	count, err := svc.Reconcile(ctx)

	assert.Equal(t, 0, count)
	assert.ErrorIs(t, err, expectedErr)
	assert.ErrorIs(t, repo.failedErr, expectedErr)
	assert.Empty(t, repo.replacedUsers)
	kratos.AssertExpectations(t)
}

func TestMapKratosIdentityToLocalUserPreservesArchivedStatus(t *testing.T) {
	user := MapKratosIdentityToLocalUser(KratosIdentity{
		ID:    "00000000-0000-0000-0000-000000000201",
		State: domain.UserStatusArchived,
		Traits: map[string]any{
			"email": "archived@example.com",
			"name":  "Archived User",
		},
	})

	assert.Equal(t, domain.UserStatusArchived, user.Status)
}
