package events

import (
	"context"
	"encoding/json"
	"testing"

	"github.com/nexuscrm/shared/pkg/constants"
	sharedevents "github.com/nexuscrm/shared/pkg/events"
	"github.com/nexuscrm/shared/pkg/models"
)

type recordPayloadLoaderFunc func(context.Context, string, string, *models.UserSession) (models.SObject, error)

func (f recordPayloadLoaderFunc) LoadOutboxRecordPayload(ctx context.Context, objectAPIName, recordID string, user *models.UserSession) (models.SObject, error) {
	return f(ctx, objectAPIName, recordID, user)
}

func minimalRecordCreatedPayloadJSON(t *testing.T) string {
	t.Helper()
	payloadJSON, err := json.Marshal(InternalRecordEventEnvelope{
		PublicPayload: RecordEventPayload{
			ObjectAPIName: "Lead",
			Record:        models.SObject{"Id": "00Q-minimal"},
			CurrentUser:   &models.UserSession{ID: "005-user", ProfileID: "profile-1"},
		},
		InternalMetadata: map[string]string{
			internalMetadataKeyMinimalRecordPayload: minimalRecordPayloadValue,
		},
	})
	if err != nil {
		t.Fatalf("json.Marshal payload: %v", err)
	}
	return string(payloadJSON)
}

func TestPrepareRecordCreatedOutboxPayloadDefaultsToFullRecord(t *testing.T) {
	record := models.SObject{"Id": "00Q-full", "Email": "ada@example.com"}
	payload := RecordEventPayload{
		ObjectAPIName: "Lead",
		Record:        record,
	}

	ctx, got := PrepareRecordCreatedOutboxPayload(context.Background(), payload)

	if got.Record.GetString("Email") != "ada@example.com" {
		t.Fatalf("Email = %q, want full record payload", got.Record.GetString("Email"))
	}
	if hasMinimalRecordPayloadMarker(ctx) {
		t.Fatal("minimal payload marker was captured with rollout flag disabled")
	}
}

func TestPrepareRecordCreatedOutboxPayloadCanUseMinimalMarker(t *testing.T) {
	t.Setenv(minimalRecordCreatedOutboxPayloadEnv, "true")
	payload := RecordEventPayload{
		ObjectAPIName: "Lead",
		Record:        models.SObject{"Id": "00Q-minimal", "Email": "ada@example.com"},
		CurrentUser:   &models.UserSession{ID: "005-user"},
	}

	ctx, got := PrepareRecordCreatedOutboxPayload(context.Background(), payload)

	if got.Record.GetString("Id") != "00Q-minimal" {
		t.Fatalf("Id = %q, want 00Q-minimal", got.Record.GetString("Id"))
	}
	if _, ok := got.Record["Email"]; ok {
		t.Fatalf("minimal payload kept Email: %#v", got.Record)
	}
	if got.ObjectAPIName != "Lead" {
		t.Fatalf("ObjectAPIName = %q, want Lead", got.ObjectAPIName)
	}
	if got.CurrentUser == nil || got.CurrentUser.ID != "005-user" {
		t.Fatalf("CurrentUser = %#v, want 005-user", got.CurrentUser)
	}
	if !hasMinimalRecordPayloadMarker(ctx) {
		t.Fatal("minimal payload marker was not captured")
	}
}

func TestPrepareRecordCreatedOutboxPayloadKeepsFullRecordWithoutID(t *testing.T) {
	t.Setenv(minimalRecordCreatedOutboxPayloadEnv, "true")
	payload := RecordEventPayload{
		ObjectAPIName: "Lead",
		Record:        models.SObject{"Email": "ada@example.com"},
	}

	ctx, got := PrepareRecordCreatedOutboxPayload(context.Background(), payload)

	if got.Record.GetString("Email") != "ada@example.com" {
		t.Fatalf("Email = %q, want full record payload", got.Record.GetString("Email"))
	}
	if _, ok := got.Record[constants.FieldID]; ok {
		t.Fatalf("unexpected Id in payload: %#v", got.Record)
	}
	if hasMinimalRecordPayloadMarker(ctx) {
		t.Fatal("minimal payload marker was captured without a record Id")
	}
}

func TestPublishClaimedEventRehydratesMinimalRecordCreatedPayload(t *testing.T) {
	bus := &contextCapturingEventBus{}
	loaderCalls := 0
	svc := NewOutboxService(nil, bus, nil).WithRecordPayloadLoader(recordPayloadLoaderFunc(
		func(_ context.Context, objectAPIName, recordID string, user *models.UserSession) (models.SObject, error) {
			loaderCalls++
			if objectAPIName != "Lead" {
				t.Fatalf("objectAPIName = %q, want Lead", objectAPIName)
			}
			if recordID != "00Q-minimal" {
				t.Fatalf("recordID = %q, want 00Q-minimal", recordID)
			}
			if user == nil || user.ID != "005-user" {
				t.Fatalf("user = %#v, want 005-user", user)
			}
			return models.SObject{"Id": recordID, "Email": "ada@example.com"}, nil
		},
	))

	if err := svc.publishClaimedEvent(context.Background(), "evt-minimal", string(sharedevents.RecordCreated), minimalRecordCreatedPayloadJSON(t)); err != nil {
		t.Fatalf("publishClaimedEvent: %v", err)
	}

	if loaderCalls != 1 {
		t.Fatalf("loader calls = %d, want 1", loaderCalls)
	}
	published, ok := bus.capturedPayload.(RecordEventPayload)
	if !ok {
		t.Fatalf("published payload type = %T, want RecordEventPayload", bus.capturedPayload)
	}
	if got := published.Record.GetString("Email"); got != "ada@example.com" {
		t.Fatalf("published Email = %q, want ada@example.com", got)
	}
	if got := published.Record.GetString("Id"); got != "00Q-minimal" {
		t.Fatalf("published Id = %q, want 00Q-minimal", got)
	}
}

func TestPublishClaimedEventMinimalRecordCreatedPayloadRequiresLoader(t *testing.T) {
	bus := &contextCapturingEventBus{}
	svc := NewOutboxService(nil, bus, nil)

	err := svc.publishClaimedEvent(context.Background(), "evt-minimal", string(sharedevents.RecordCreated), minimalRecordCreatedPayloadJSON(t))
	if err == nil {
		t.Fatal("publishClaimedEvent error = nil, want missing loader error")
	}
	if bus.capturedPayload != nil {
		t.Fatalf("published payload = %#v, want none", bus.capturedPayload)
	}
}

func hasMinimalRecordPayloadMarker(ctx context.Context) bool {
	internalMetadata := map[string]string{}
	captureMinimalRecordPayloadInternalMetadata(ctx, nil, internalMetadata)
	return minimalRecordPayloadMarked(internalMetadata)
}
