//go:build integration

package migration_test

import (
	"context"
	"testing"

	"github.com/google/uuid"

	"github.com/ultisuite/ulti-backend/internal/integrationtest"
	"github.com/ultisuite/ulti-backend/internal/users"
)

func TestClaimInviteFlexibleEmailMatch(t *testing.T) {
	h := integrationtest.RequireHarness(t)
	ctx := context.Background()

	adminClient, adminClaims := integrationtest.RequireAdminClient(t, h)
	if _, err := users.EnsureUser(ctx, h.Pool, adminClaims); err != nil {
		t.Fatalf("ensure admin: %v", err)
	}
	if err := users.GrantPlatformAdmin(ctx, h.Pool, adminClaims.Sub); err != nil {
		t.Fatalf("grant admin: %v", err)
	}

	createResp, err := adminClient.Post("/api/v1/admin/migration/projects", map[string]any{
		"name":            "Flexible email match",
		"source_provider": "microsoft",
	})
	integrationtest.FailIf(err, t, "create project")
	integrationtest.FailUnlessStatus(t, createResp, 201)

	var created struct {
		ID string `json:"id"`
	}
	integrationtest.DecodeJSON(t, createResp, &created)

	actResp, err := adminClient.Post("/api/v1/admin/migration/projects/"+created.ID+"/activate", nil)
	integrationtest.FailIf(err, t, "activate project")
	integrationtest.FailUnlessStatus(t, actResp, 200)

	inviteEmail := "alice-" + uuid.NewString()[:8] + "@example.com"
	ssoEmail := "alice.sso-" + uuid.NewString()[:8] + "@example.com"
	inviteResp, err := adminClient.Post("/api/v1/admin/migration/projects/"+created.ID+"/invites", map[string]any{
		"email":            inviteEmail,
		"alternate_emails": []string{ssoEmail},
	})
	integrationtest.FailIf(err, t, "create invite")
	integrationtest.FailUnlessStatus(t, inviteResp, 201)

	var invite struct {
		Token string `json:"token"`
	}
	integrationtest.DecodeJSON(t, inviteResp, &invite)

	migrateeClaims := integrationtest.RegularUser(integrationtest.NewExternalID("flex-claim"))
	migrateeClaims.Email = ssoEmail
	migrateeClaims.PreferredUsername = inviteEmail
	migrateeClient, err := h.Client(migrateeClaims)
	integrationtest.FailIf(err, t, "migratee client")

	if _, err := users.EnsureUser(ctx, h.Pool, migrateeClaims); err != nil {
		t.Fatalf("ensure migratee: %v", err)
	}

	claimResp, err := migrateeClient.Post("/api/v1/migration/claim", map[string]string{
		"token":    invite.Token,
		"password": "test-password-123",
	})
	integrationtest.FailIf(err, t, "claim invite")
	integrationtest.FailUnlessStatus(t, claimResp, 200)
}

func TestClaimInviteRejectsEmailMismatch(t *testing.T) {
	h := integrationtest.RequireHarness(t)
	ctx := context.Background()

	adminClient, adminClaims := integrationtest.RequireAdminClient(t, h)
	if _, err := users.EnsureUser(ctx, h.Pool, adminClaims); err != nil {
		t.Fatalf("ensure admin: %v", err)
	}
	if err := users.GrantPlatformAdmin(ctx, h.Pool, adminClaims.Sub); err != nil {
		t.Fatalf("grant admin: %v", err)
	}

	createResp, err := adminClient.Post("/api/v1/admin/migration/projects", map[string]any{
		"name":            "Reject mismatch",
		"source_provider": "google",
	})
	integrationtest.FailIf(err, t, "create project")
	integrationtest.FailUnlessStatus(t, createResp, 201)

	var created struct {
		ID string `json:"id"`
	}
	integrationtest.DecodeJSON(t, createResp, &created)

	actResp, err := adminClient.Post("/api/v1/admin/migration/projects/"+created.ID+"/activate", nil)
	integrationtest.FailIf(err, t, "activate project")
	integrationtest.FailUnlessStatus(t, actResp, 200)

	inviteEmail := "victim-" + uuid.NewString() + "@example.com"
	inviteResp, err := adminClient.Post("/api/v1/admin/migration/projects/"+created.ID+"/invites", map[string]any{
		"email": inviteEmail,
	})
	integrationtest.FailIf(err, t, "create invite")
	integrationtest.FailUnlessStatus(t, inviteResp, 201)

	var invite struct {
		Token string `json:"token"`
	}
	integrationtest.DecodeJSON(t, inviteResp, &invite)

	attackerClaims := integrationtest.RegularUser(integrationtest.NewExternalID("flex-claim-bad"))
	attackerClaims.Email = "attacker-" + uuid.NewString() + "@example.com"
	attackerClient, err := h.Client(attackerClaims)
	integrationtest.FailIf(err, t, "attacker client")

	if _, err := users.EnsureUser(ctx, h.Pool, attackerClaims); err != nil {
		t.Fatalf("ensure attacker: %v", err)
	}

	claimResp, err := attackerClient.Post("/api/v1/migration/claim", map[string]string{
		"token": invite.Token,
	})
	integrationtest.FailIf(err, t, "claim invite")
	integrationtest.AssertErrorCode(t, claimResp, 400, "email_mismatch")
}

func TestClaimInviteRejectsMicrosoftTenantMismatch(t *testing.T) {
	h := integrationtest.RequireHarness(t)
	ctx := context.Background()

	adminClient, adminClaims := integrationtest.RequireAdminClient(t, h)
	if _, err := users.EnsureUser(ctx, h.Pool, adminClaims); err != nil {
		t.Fatalf("ensure admin: %v", err)
	}
	if err := users.GrantPlatformAdmin(ctx, h.Pool, adminClaims.Sub); err != nil {
		t.Fatalf("grant admin: %v", err)
	}

	createResp, err := adminClient.Post("/api/v1/admin/migration/projects", map[string]any{
		"name":            "Tenant mismatch",
		"source_provider": "microsoft",
	})
	integrationtest.FailIf(err, t, "create project")
	integrationtest.FailUnlessStatus(t, createResp, 201)

	var created struct {
		ID string `json:"id"`
	}
	integrationtest.DecodeJSON(t, createResp, &created)

	actResp, err := adminClient.Post("/api/v1/admin/migration/projects/"+created.ID+"/activate", nil)
	integrationtest.FailIf(err, t, "activate project")
	integrationtest.FailUnlessStatus(t, actResp, 200)

	expectedTenant := "11111111-2222-3333-4444-555555555555"
	if _, err := h.Pool.Exec(ctx, `
		UPDATE migration_projects SET microsoft_tenant_id = $1 WHERE id = $2::uuid
	`, expectedTenant, created.ID); err != nil {
		t.Fatalf("set tenant: %v", err)
	}

	inviteEmail := "tenant-user-" + uuid.NewString()[:8] + "@example.com"
	inviteResp, err := adminClient.Post("/api/v1/admin/migration/projects/"+created.ID+"/invites", map[string]any{
		"email": inviteEmail,
	})
	integrationtest.FailIf(err, t, "create invite")
	integrationtest.FailUnlessStatus(t, inviteResp, 201)

	var invite struct {
		Token string `json:"token"`
	}
	integrationtest.DecodeJSON(t, inviteResp, &invite)

	wrongTenantClaims := integrationtest.RegularUser(integrationtest.NewExternalID("tenant-mismatch"))
	wrongTenantClaims.Email = inviteEmail
	wrongTenantClaims.TID = "99999999-aaaa-bbbb-cccc-dddddddddddd"
	wrongTenantClient, err := h.Client(wrongTenantClaims)
	integrationtest.FailIf(err, t, "wrong tenant client")

	if _, err := users.EnsureUser(ctx, h.Pool, wrongTenantClaims); err != nil {
		t.Fatalf("ensure user: %v", err)
	}

	claimResp, err := wrongTenantClient.Post("/api/v1/migration/claim", map[string]string{
		"token": invite.Token,
	})
	integrationtest.FailIf(err, t, "claim invite")
	integrationtest.AssertErrorCode(t, claimResp, 400, "tenant_mismatch")
}

func TestClaimInviteGoogleProjectIgnoresTenant(t *testing.T) {
	h := integrationtest.RequireHarness(t)
	ctx := context.Background()

	adminClient, adminClaims := integrationtest.RequireAdminClient(t, h)
	if _, err := users.EnsureUser(ctx, h.Pool, adminClaims); err != nil {
		t.Fatalf("ensure admin: %v", err)
	}
	if err := users.GrantPlatformAdmin(ctx, h.Pool, adminClaims.Sub); err != nil {
		t.Fatalf("grant admin: %v", err)
	}

	createResp, err := adminClient.Post("/api/v1/admin/migration/projects", map[string]any{
		"name":            "Google ignores tenant",
		"source_provider": "google",
	})
	integrationtest.FailIf(err, t, "create project")
	integrationtest.FailUnlessStatus(t, createResp, 201)

	var created struct {
		ID string `json:"id"`
	}
	integrationtest.DecodeJSON(t, createResp, &created)

	actResp, err := adminClient.Post("/api/v1/admin/migration/projects/"+created.ID+"/activate", nil)
	integrationtest.FailIf(err, t, "activate project")
	integrationtest.FailUnlessStatus(t, actResp, 200)

	if _, err := h.Pool.Exec(ctx, `
		UPDATE migration_projects SET microsoft_tenant_id = $1 WHERE id = $2::uuid
	`, "11111111-2222-3333-4444-555555555555", created.ID); err != nil {
		t.Fatalf("set tenant: %v", err)
	}

	inviteEmail := "google-user-" + uuid.NewString()[:8] + "@example.com"
	inviteResp, err := adminClient.Post("/api/v1/admin/migration/projects/"+created.ID+"/invites", map[string]any{
		"email": inviteEmail,
	})
	integrationtest.FailIf(err, t, "create invite")
	integrationtest.FailUnlessStatus(t, inviteResp, 201)

	var invite struct {
		Token string `json:"token"`
	}
	integrationtest.DecodeJSON(t, inviteResp, &invite)

	userClaims := integrationtest.RegularUser(integrationtest.NewExternalID("google-tenant-ignore"))
	userClaims.Email = inviteEmail
	userClaims.TID = "wrong-tenant-id"
	userClient, err := h.Client(userClaims)
	integrationtest.FailIf(err, t, "user client")

	if _, err := users.EnsureUser(ctx, h.Pool, userClaims); err != nil {
		t.Fatalf("ensure user: %v", err)
	}

	claimResp, err := userClient.Post("/api/v1/migration/claim", map[string]string{
		"token":    invite.Token,
		"password": "test-password-123",
	})
	integrationtest.FailIf(err, t, "claim invite")
	integrationtest.FailUnlessStatus(t, claimResp, 200)
}
