package serviceactor

import (
	"context"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/coredb"
)

func TestInstallationUpsertRejectsIdentityReuseAndForeignCreatorsWithoutMutation(t *testing.T) {
	t.Parallel()

	ctx := context.Background()
	db := startServiceActorRollbackDB(t, ctx)
	now := time.Now().UTC()
	firstFixture := seedServiceActorFixture(t, db, "installation_identity_first", now)
	secondFixture := seedServiceActorFixture(t, db, "installation_identity_second", now)
	store := New(db)
	definition, err := store.LoadDefinitionBySlug(ctx, KindServiceAccount, "service-account")
	require.NoError(t, err)

	first, err := store.UpsertInstallation(ctx, InstallationUpsert{
		ID:                  "svcinst_identity_reuse",
		WorkspaceID:         firstFixture.WorkspaceID,
		ServiceDefinitionID: definition.ID,
		ServiceKind:         definition.ServiceKind,
		InstanceKey:         "sacct_identity_first",
		Status:              StatusActive,
		Config:              []byte(`{"owner":"first"}`),
		CreatedByMemberID:   firstFixture.HumanMemberID,
	})
	require.NoError(t, err)

	_, err = store.UpsertInstallation(ctx, InstallationUpsert{
		ID:                  first.ID,
		WorkspaceID:         secondFixture.WorkspaceID,
		ServiceDefinitionID: definition.ID,
		ServiceKind:         definition.ServiceKind,
		InstanceKey:         "sacct_identity_second",
		Status:              StatusDisabled,
		Config:              []byte(`{"owner":"second"}`),
		CreatedByMemberID:   secondFixture.HumanMemberID,
	})
	require.ErrorIs(t, err, ErrConflict)

	persisted, err := store.LoadInstallation(ctx, first.ID)
	require.NoError(t, err)
	require.Equal(t, firstFixture.WorkspaceID, persisted.WorkspaceID)
	require.Equal(t, "sacct_identity_first", persisted.InstanceKey)
	require.Equal(t, StatusActive, persisted.Status)
	require.JSONEq(t, `{"owner":"first"}`, string(persisted.Config))
	require.Equal(t, firstFixture.HumanMemberID, persisted.CreatedByMemberID)
	_, err = store.LoadInstallationByInstance(ctx, secondFixture.WorkspaceID, definition.ID, "sacct_identity_second")
	require.ErrorIs(t, err, ErrNotFound)

	_, err = store.UpsertInstallation(ctx, InstallationUpsert{
		ID:                  "svcinst_natural_identity_reuse",
		WorkspaceID:         firstFixture.WorkspaceID,
		ServiceDefinitionID: definition.ID,
		ServiceKind:         definition.ServiceKind,
		InstanceKey:         first.InstanceKey,
		CreatedByMemberID:   firstFixture.HumanMemberID,
	})
	require.ErrorIs(t, err, ErrConflict)
	persisted, err = store.LoadInstallation(ctx, first.ID)
	require.NoError(t, err)
	require.Equal(t, StatusActive, persisted.Status)
	require.JSONEq(t, `{"owner":"first"}`, string(persisted.Config))

	_, err = store.UpsertInstallation(ctx, InstallationUpsert{
		ID:                  "svcinst_foreign_creator",
		WorkspaceID:         secondFixture.WorkspaceID,
		ServiceDefinitionID: definition.ID,
		ServiceKind:         definition.ServiceKind,
		InstanceKey:         "sacct_foreign_creator",
		CreatedByMemberID:   firstFixture.HumanMemberID,
	})
	require.ErrorIs(t, err, ErrConflict)
	_, err = store.LoadInstallation(ctx, "svcinst_foreign_creator")
	require.ErrorIs(t, err, ErrNotFound)

	_, err = store.UpsertInstallation(ctx, InstallationUpsert{
		ID:                  "svcinst_service_creator",
		WorkspaceID:         secondFixture.WorkspaceID,
		ServiceDefinitionID: definition.ID,
		ServiceKind:         definition.ServiceKind,
		InstanceKey:         "sacct_service_creator",
		CreatedByMemberID:   secondFixture.ServiceMemberID,
	})
	require.ErrorIs(t, err, ErrConflict)
	_, err = store.LoadInstallation(ctx, "svcinst_service_creator")
	require.ErrorIs(t, err, ErrNotFound)
}

func TestMemberUpsertRejectsForeignSubstrateAndIDReuseWithoutMutation(t *testing.T) {
	t.Parallel()

	ctx := context.Background()
	db := startServiceActorRollbackDB(t, ctx)
	now := time.Now().UTC()
	firstFixture := seedServiceActorFixture(t, db, "member_identity_first", now)
	secondFixture := seedServiceActorFixture(t, db, "member_identity_second", now)
	store := New(db)
	definition, err := store.LoadDefinitionBySlug(ctx, KindServiceAccount, "service-account")
	require.NoError(t, err)

	firstInstallation := mustUpsertNamedInstallation(t, ctx, store, definition, firstFixture, "svcinst_member_identity_first", "sacct_member_identity_first")
	secondInstallation := mustUpsertNamedInstallation(t, ctx, store, definition, secondFixture, "svcinst_member_identity_second", "sacct_member_identity_second")
	thirdInstallation := mustUpsertNamedInstallation(t, ctx, store, definition, firstFixture, "svcinst_member_identity_third", "sacct_member_identity_third")

	firstMember, err := store.UpsertMember(ctx, MemberUpsert{
		ID:                    "svcmem_identity_reuse",
		WorkspaceID:           firstFixture.WorkspaceID,
		ServiceDefinitionID:   definition.ID,
		ServiceInstallationID: firstInstallation.ID,
		MemberID:              firstFixture.ServiceMemberID,
		ServiceKind:           definition.ServiceKind,
		Handle:                "identity-first",
		DisplayName:           "Identity First",
	})
	require.NoError(t, err)

	_, err = store.UpsertMember(ctx, MemberUpsert{
		ID:                    firstMember.ID,
		WorkspaceID:           secondFixture.WorkspaceID,
		ServiceDefinitionID:   definition.ID,
		ServiceInstallationID: secondInstallation.ID,
		MemberID:              secondFixture.ServiceMemberID,
		ServiceKind:           definition.ServiceKind,
		Handle:                "identity-second",
		DisplayName:           "Mutated",
	})
	require.ErrorIs(t, err, ErrConflict)
	persisted, err := store.LoadMemberByInstallation(ctx, firstInstallation.ID)
	require.NoError(t, err)
	require.Equal(t, firstMember.ID, persisted.ID)
	require.Equal(t, firstFixture.WorkspaceID, persisted.WorkspaceID)
	require.Equal(t, firstFixture.ServiceMemberID, persisted.MemberID)
	require.Equal(t, "identity-first", persisted.Handle)
	require.Equal(t, "Identity First", persisted.DisplayName)
	_, err = store.LoadMemberByInstallation(ctx, secondInstallation.ID)
	require.ErrorIs(t, err, ErrNotFound)

	_, err = store.UpsertMember(ctx, MemberUpsert{
		ID:                    "svcmem_natural_identity_reuse",
		WorkspaceID:           firstFixture.WorkspaceID,
		ServiceDefinitionID:   definition.ID,
		ServiceInstallationID: firstInstallation.ID,
		MemberID:              firstFixture.ServiceMemberID,
		ServiceKind:           definition.ServiceKind,
		Handle:                "identity-first",
	})
	require.ErrorIs(t, err, ErrConflict)

	_, err = store.UpsertMember(ctx, MemberUpsert{
		ID:                    "svcmem_foreign_installation",
		WorkspaceID:           firstFixture.WorkspaceID,
		ServiceDefinitionID:   definition.ID,
		ServiceInstallationID: secondInstallation.ID,
		MemberID:              firstFixture.ServiceMemberID,
		ServiceKind:           definition.ServiceKind,
		Handle:                "foreign-installation",
	})
	require.ErrorIs(t, err, ErrConflict)
	_, err = loadMemberByID(ctx, db, "svcmem_foreign_installation")
	require.ErrorIs(t, err, ErrNotFound)

	_, err = store.UpsertMember(ctx, MemberUpsert{
		ID:                    "svcmem_foreign_base",
		WorkspaceID:           firstFixture.WorkspaceID,
		ServiceDefinitionID:   definition.ID,
		ServiceInstallationID: thirdInstallation.ID,
		MemberID:              secondFixture.ServiceMemberID,
		ServiceKind:           definition.ServiceKind,
		Handle:                "foreign-base",
	})
	require.ErrorIs(t, err, ErrConflict)
	_, err = loadMemberByID(ctx, db, "svcmem_foreign_base")
	require.ErrorIs(t, err, ErrNotFound)

	_, err = store.UpsertMember(ctx, MemberUpsert{
		ID:                    "svcmem_human_base",
		WorkspaceID:           firstFixture.WorkspaceID,
		ServiceDefinitionID:   definition.ID,
		ServiceInstallationID: thirdInstallation.ID,
		MemberID:              firstFixture.HumanMemberID,
		ServiceKind:           definition.ServiceKind,
		Handle:                "human-base",
	})
	require.ErrorIs(t, err, ErrConflict)
	_, err = loadMemberByID(ctx, db, "svcmem_human_base")
	require.ErrorIs(t, err, ErrNotFound)

	_, err = store.UpsertMember(ctx, MemberUpsert{
		ID:                    "svcmem_foreign_definition",
		WorkspaceID:           firstFixture.WorkspaceID,
		ServiceDefinitionID:   "app_github",
		ServiceInstallationID: thirdInstallation.ID,
		MemberID:              firstFixture.ServiceMemberID,
		ServiceKind:           definition.ServiceKind,
		Handle:                "foreign-definition",
	})
	require.ErrorIs(t, err, ErrConflict)
	_, err = loadMemberByID(ctx, db, "svcmem_foreign_definition")
	require.ErrorIs(t, err, ErrNotFound)

	_, err = store.UpsertMember(ctx, MemberUpsert{
		ID:                    "svcmem_foreign_kind",
		WorkspaceID:           firstFixture.WorkspaceID,
		ServiceDefinitionID:   definition.ID,
		ServiceInstallationID: thirdInstallation.ID,
		MemberID:              firstFixture.ServiceMemberID,
		ServiceKind:           KindApp,
		Handle:                "foreign-kind",
	})
	require.ErrorIs(t, err, ErrConflict)
	_, err = loadMemberByID(ctx, db, "svcmem_foreign_kind")
	require.ErrorIs(t, err, ErrNotFound)
}

func TestInsertServiceMemberSubstrateCreatesActiveMember(t *testing.T) {
	t.Parallel()

	ctx := context.Background()
	db := startServiceActorRollbackDB(t, ctx)
	now := time.Date(2026, 7, 10, 10, 30, 0, 0, time.UTC)
	fixture := seedServiceActorFixture(t, db, "member_substrate_insert", now)
	store := New(db)

	require.NoError(t, coredb.WithTx(ctx, db, func(tx coredb.Tx) error {
		return store.InTx(tx).InsertServiceMemberSubstrate(ctx, ServiceMemberSubstrateInsert{
			ID:          "mem_svcactor_inserted_substrate",
			WorkspaceID: fixture.WorkspaceID,
			DisplayName: "Inserted Service",
			Now:         now,
		})
	}))

	var memberType, displayName, status string
	var userID, workspaceRole *string
	require.NoError(t, db.QueryRow(ctx, `
SELECT type, display_name, status, user_id, workspace_role
FROM members
WHERE id = $1 AND workspace_id = $2
`, "mem_svcactor_inserted_substrate", fixture.WorkspaceID).Scan(&memberType, &displayName, &status, &userID, &workspaceRole))
	require.Equal(t, "service", memberType)
	require.Equal(t, "Inserted Service", displayName)
	require.Equal(t, StatusActive, status)
	require.Nil(t, userID)
	require.Nil(t, workspaceRole)
}

func TestServiceAccountMemberHandlesAreCaseInsensitiveAndAppHandlesRemainRaw(t *testing.T) {
	t.Parallel()

	ctx := context.Background()
	db := startServiceActorRollbackDB(t, ctx)
	now := time.Now().UTC()
	fixture := seedServiceActorFixture(t, db, "member_handle_semantics", now)
	store := New(db)
	serviceAccountDefinition, err := store.LoadDefinitionBySlug(ctx, KindServiceAccount, "service-account")
	require.NoError(t, err)

	const secondServiceMemberID = "mem_svcactor_handle_service_second"
	const thirdServiceMemberID = "mem_svcactor_handle_service_third"
	const fourthServiceMemberID = "mem_svcactor_handle_service_fourth"
	mustExec(t, db, `
INSERT INTO members (id, workspace_id, type, display_name, workspace_role, status, created_at, updated_at)
VALUES
  ($1, $4, 'service', 'Service Second', NULL, 'active', $5, $5),
  ($2, $4, 'service', 'Service Third', NULL, 'active', $5, $5),
  ($3, $4, 'service', 'Service Fourth', NULL, 'active', $5, $5)
`, secondServiceMemberID, thirdServiceMemberID, fourthServiceMemberID, fixture.WorkspaceID, now)

	firstServiceAccountInstallation := mustUpsertNamedInstallation(t, ctx, store, serviceAccountDefinition, fixture, "svcinst_handle_service_first", "sacct_handle_service_first")
	secondServiceAccountInstallation := mustUpsertNamedInstallation(t, ctx, store, serviceAccountDefinition, fixture, "svcinst_handle_service_second", "sacct_handle_service_second")
	firstServiceAccountMember, err := store.UpsertMember(ctx, MemberUpsert{
		ID:                    "svcmem_handle_service_first",
		WorkspaceID:           fixture.WorkspaceID,
		ServiceDefinitionID:   serviceAccountDefinition.ID,
		ServiceInstallationID: firstServiceAccountInstallation.ID,
		MemberID:              fixture.ServiceMemberID,
		ServiceKind:           KindServiceAccount,
		Handle:                "Reviewer",
	})
	require.NoError(t, err)
	_, err = store.UpsertMember(ctx, MemberUpsert{
		ID:                    "svcmem_handle_service_second",
		WorkspaceID:           fixture.WorkspaceID,
		ServiceDefinitionID:   serviceAccountDefinition.ID,
		ServiceInstallationID: secondServiceAccountInstallation.ID,
		MemberID:              secondServiceMemberID,
		ServiceKind:           KindServiceAccount,
		Handle:                " reviewer ",
	})
	require.ErrorIs(t, err, ErrConflict)
	loadedServiceAccountMember, err := store.LoadMemberByWorkspaceHandle(ctx, fixture.WorkspaceID, KindServiceAccount, " REVIEWER ")
	require.NoError(t, err)
	require.Equal(t, firstServiceAccountMember.ID, loadedServiceAccountMember.ID)

	firstAppDefinition, err := store.UpsertDefinition(ctx, DefinitionUpsert{
		ID:          "svcdef_handle_app_first",
		ServiceKind: KindApp,
		Slug:        "handle-app-first",
		Name:        "Handle App First",
	})
	require.NoError(t, err)
	secondAppDefinition, err := store.UpsertDefinition(ctx, DefinitionUpsert{
		ID:          "svcdef_handle_app_second",
		ServiceKind: KindApp,
		Slug:        "handle-app-second",
		Name:        "Handle App Second",
	})
	require.NoError(t, err)
	firstInstallation, err := store.UpsertInstallation(ctx, InstallationUpsert{
		ID:                  "svcinst_handle_app_first",
		WorkspaceID:         fixture.WorkspaceID,
		ServiceDefinitionID: firstAppDefinition.ID,
		ServiceKind:         KindApp,
		CreatedByMemberID:   fixture.HumanMemberID,
	})
	require.NoError(t, err)
	secondInstallation, err := store.UpsertInstallation(ctx, InstallationUpsert{
		ID:                  "svcinst_handle_app_second",
		WorkspaceID:         fixture.WorkspaceID,
		ServiceDefinitionID: secondAppDefinition.ID,
		ServiceKind:         KindApp,
		CreatedByMemberID:   fixture.HumanMemberID,
	})
	require.NoError(t, err)
	firstAppMember, err := store.UpsertMember(ctx, MemberUpsert{
		ID:                    "svcmem_handle_app_first",
		WorkspaceID:           fixture.WorkspaceID,
		ServiceDefinitionID:   firstAppDefinition.ID,
		ServiceInstallationID: firstInstallation.ID,
		MemberID:              thirdServiceMemberID,
		ServiceKind:           KindApp,
		Handle:                "Reviewer",
	})
	require.NoError(t, err)
	replayedAppMember, err := store.UpsertMember(ctx, MemberUpsert{
		ID:                    "svcmem_handle_app_replay",
		WorkspaceID:           fixture.WorkspaceID,
		ServiceDefinitionID:   firstAppDefinition.ID,
		ServiceInstallationID: firstInstallation.ID,
		MemberID:              thirdServiceMemberID,
		ServiceKind:           KindApp,
		Handle:                "Reviewer",
		DisplayName:           "Replayed App",
	})
	require.NoError(t, err)
	require.Equal(t, firstAppMember.ID, replayedAppMember.ID)
	require.Equal(t, "Replayed App", replayedAppMember.DisplayName)
	_, err = loadMemberByID(ctx, db, "svcmem_handle_app_replay")
	require.ErrorIs(t, err, ErrNotFound)
	secondAppMember, err := store.UpsertMember(ctx, MemberUpsert{
		ID:                    "svcmem_handle_app_second",
		WorkspaceID:           fixture.WorkspaceID,
		ServiceDefinitionID:   secondAppDefinition.ID,
		ServiceInstallationID: secondInstallation.ID,
		MemberID:              fourthServiceMemberID,
		ServiceKind:           KindApp,
		Handle:                "reviewer",
	})
	require.NoError(t, err)
	loadedUpperAppMember, err := store.LoadMemberByWorkspaceHandle(ctx, fixture.WorkspaceID, KindApp, "Reviewer")
	require.NoError(t, err)
	require.Equal(t, firstAppMember.ID, loadedUpperAppMember.ID)
	loadedWhitespaceAppMember, err := store.LoadMemberByWorkspaceHandle(ctx, fixture.WorkspaceID, KindApp, " Reviewer ")
	require.NoError(t, err)
	require.Equal(t, firstAppMember.ID, loadedWhitespaceAppMember.ID)
	loadedLowerAppMember, err := store.LoadMemberByWorkspaceHandle(ctx, fixture.WorkspaceID, KindApp, "reviewer")
	require.NoError(t, err)
	require.Equal(t, secondAppMember.ID, loadedLowerAppMember.ID)
}

func mustUpsertNamedInstallation(t testing.TB, ctx context.Context, store *Store, definition Definition, fixture serviceActorFixture, installationID string, instanceKey string) Installation {
	t.Helper()
	installation, err := store.UpsertInstallation(ctx, InstallationUpsert{
		ID:                  installationID,
		WorkspaceID:         fixture.WorkspaceID,
		ServiceDefinitionID: definition.ID,
		ServiceKind:         definition.ServiceKind,
		InstanceKey:         instanceKey,
		CreatedByMemberID:   fixture.HumanMemberID,
	})
	require.NoError(t, err)
	return installation
}
