//go:build integration

package migration_test

import (
	"bytes"
	"context"
	"encoding/json"
	"io"
	"net/http"
	"testing"

	"github.com/google/uuid"

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

const testProvisionSecret = "test-provision-secret"

func postProvision(t *testing.T, h *integrationtest.Harness, body map[string]any) *integrationtest.Response {
	t.Helper()
	data, err := json.Marshal(body)
	integrationtest.FailIf(err, t, "marshal provision body")
	req, err := http.NewRequest(http.MethodPost, h.Server.URL+"/internal/provision/user", bytes.NewReader(data))
	integrationtest.FailIf(err, t, "provision request")
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-Provision-Secret", testProvisionSecret)
	resp, err := http.DefaultClient.Do(req)
	integrationtest.FailIf(err, t, "provision call")
	defer resp.Body.Close()
	bodyBytes, err := io.ReadAll(resp.Body)
	integrationtest.FailIf(err, t, "read provision response")
	return &integrationtest.Response{Status: resp.StatusCode, Body: bodyBytes, Header: resp.Header}
}

func TestProvisionEnrollThenClaim(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)
	}

	domainName := "enroll-claim-" + uuid.NewString()[:8] + ".test"
	var domainID string
	err := h.Pool.QueryRow(ctx, `
		INSERT INTO mail_domains (name, status, is_platform_domain)
		VALUES ($1, 'active', false)
		RETURNING id::text
	`, domainName).Scan(&domainID)
	integrationtest.FailIf(err, t, "insert domain")

	createResp, err := adminClient.Post("/api/v1/admin/migration/projects", map[string]any{
		"name":            "Enroll then claim",
		"source_provider": "google",
		"domain_id":       domainID,
	})
	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)

	migrateeEmail := "user@" + domainName
	inviteResp, err := adminClient.Post("/api/v1/admin/migration/projects/"+created.ID+"/invites", map[string]string{
		"email": migrateeEmail,
	})
	integrationtest.FailIf(err, t, "create invite")
	integrationtest.FailUnlessStatus(t, inviteResp, 201)

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

	externalID := integrationtest.NewExternalID("enroll-claim")
	provisionResp := postProvision(t, h, map[string]any{
		"email":       migrateeEmail,
		"name":        "Migratee",
		"password":    "enroll-password-123",
		"external_id": externalID,
	})
	if provisionResp.Status != 200 {
		t.Fatalf("provision status = %d, want 200; body=%s", provisionResp.Status, string(provisionResp.Body))
	}

	migrateeClaims := integrationtest.RegularUser(externalID)
	migrateeClaims.Email = migrateeEmail
	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": "claim-password-123",
	})
	integrationtest.FailIf(err, t, "claim invite")
	integrationtest.FailUnlessStatus(t, claimResp, 200)

	audit, err := migration.AuditProvisionByEmail(ctx, h.Pool, migrateeEmail)
	integrationtest.FailIf(err, t, "audit provision")
	if audit.Users != 1 {
		t.Fatalf("users = %d, want 1", audit.Users)
	}
	if audit.Mailboxes != 1 {
		t.Fatalf("mailboxes = %d, want 1", audit.Mailboxes)
	}
	if audit.MailAccounts != 1 {
		t.Fatalf("mail_accounts = %d, want 1", audit.MailAccounts)
	}
}

func TestProvisionClaimThenEnroll(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)
	}

	domainName := "claim-enroll-" + uuid.NewString()[:8] + ".test"
	var domainID string
	err := h.Pool.QueryRow(ctx, `
		INSERT INTO mail_domains (name, status, is_platform_domain)
		VALUES ($1, 'active', false)
		RETURNING id::text
	`, domainName).Scan(&domainID)
	integrationtest.FailIf(err, t, "insert domain")

	createResp, err := adminClient.Post("/api/v1/admin/migration/projects", map[string]any{
		"name":            "Claim then enroll",
		"source_provider": "google",
		"domain_id":       domainID,
	})
	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)

	migrateeEmail := "user@" + domainName
	inviteResp, err := adminClient.Post("/api/v1/admin/migration/projects/"+created.ID+"/invites", map[string]string{
		"email": migrateeEmail,
	})
	integrationtest.FailIf(err, t, "create invite")
	integrationtest.FailUnlessStatus(t, inviteResp, 201)

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

	externalID := integrationtest.NewExternalID("claim-enroll")
	migrateeClaims := integrationtest.RegularUser(externalID)
	migrateeClaims.Email = migrateeEmail
	migrateeClient, err := h.Client(migrateeClaims)
	integrationtest.FailIf(err, t, "migratee client")

	userID, err := users.EnsureUser(ctx, h.Pool, migrateeClaims)
	integrationtest.FailIf(err, t, "ensure migratee")

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

	provisionResp := postProvision(t, h, map[string]any{
		"email":       migrateeEmail,
		"name":        "Migratee",
		"password":    "enroll-password-123",
		"external_id": externalID,
	})
	if provisionResp.Status != 200 {
		t.Fatalf("provision status = %d, want 200; body=%s", provisionResp.Status, string(provisionResp.Body))
	}

	var provisionBody struct {
		UserID string `json:"user_id"`
	}
	integrationtest.DecodeJSON(t, provisionResp, &provisionBody)
	if provisionBody.UserID != userID {
		t.Fatalf("provision user_id = %q, want %q", provisionBody.UserID, userID)
	}

	audit, err := migration.AuditProvisionByEmail(ctx, h.Pool, migrateeEmail)
	integrationtest.FailIf(err, t, "audit provision")
	if audit.Users != 1 {
		t.Fatalf("users = %d, want 1", audit.Users)
	}
	if audit.Mailboxes != 1 {
		t.Fatalf("mailboxes = %d, want 1", audit.Mailboxes)
	}
	if audit.MailAccounts != 1 {
		t.Fatalf("mail_accounts = %d, want 1", audit.MailAccounts)
	}
}

func TestProvisionEnrollmentDefersMailboxForPendingInvite(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)
	}

	platformEmail := "pending-" + uuid.NewString()[:8] + "@ultisuite.local"
	createResp, err := adminClient.Post("/api/v1/admin/migration/projects", map[string]any{
		"name":            "Pending invite defer",
		"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)

	inviteResp, err := adminClient.Post("/api/v1/admin/migration/projects/"+created.ID+"/invites", map[string]string{
		"email": platformEmail,
	})
	integrationtest.FailIf(err, t, "create invite")
	integrationtest.FailUnlessStatus(t, inviteResp, 201)

	externalID := integrationtest.NewExternalID("pending-invite")
	provisionResp := postProvision(t, h, map[string]any{
		"email":       platformEmail,
		"name":        "Pending User",
		"password":    "enroll-password-123",
		"external_id": externalID,
	})
	if provisionResp.Status != 200 {
		t.Fatalf("provision status = %d, want 200; body=%s", provisionResp.Status, string(provisionResp.Body))
	}

	var provisionBody struct {
		MailboxDeferred bool `json:"mailbox_deferred"`
	}
	integrationtest.DecodeJSON(t, provisionResp, &provisionBody)
	if !provisionBody.MailboxDeferred {
		t.Fatal("expected mailbox_deferred=true for pending invite enrollment")
	}

	audit, err := migration.AuditProvisionByEmail(ctx, h.Pool, platformEmail)
	integrationtest.FailIf(err, t, "audit after deferred enroll")
	if audit.Users != 1 {
		t.Fatalf("users = %d, want 1", audit.Users)
	}
	if audit.Mailboxes != 0 {
		t.Fatalf("mailboxes = %d, want 0 before claim", audit.Mailboxes)
	}
}
