package outbox_test

import (
	"context"
	stderrors "errors"
	"testing"

	"gorm.io/gorm"

	"github.com/break/junhong_cmp_fiber/internal/infrastructure/messaging/outbox"
	"github.com/break/junhong_cmp_fiber/internal/testutil"
)

func TestBusinessFactAndOutboxCommitAndRollbackTogether(t *testing.T) {
	db := testutil.NewPostgresTransaction(t)
	createTemporaryOutboxTables(t, db)
	repository := outbox.NewRepository()

	err := db.Transaction(func(tx *gorm.DB) error {
		if err := tx.Exec("INSERT INTO test_foundation_business_fact (business_key) VALUES (?)", "fact-success").Error; err != nil {
			return err
		}
		_, err := repository.Append(context.Background(), tx, newEnvelope("event-success", "fact-success"))
		return err
	})
	if err != nil {
		t.Fatalf("提交业务事实和 Outbox 失败：%v", err)
	}
	assertTableCount(t, db, "test_foundation_business_fact", 1)
	assertTableCount(t, db, "tb_outbox_event", 1)

	err = db.Transaction(func(tx *gorm.DB) error {
		if err := tx.Exec("INSERT INTO test_foundation_business_fact (business_key) VALUES (?)", "fact-rollback").Error; err != nil {
			return err
		}
		if _, err := repository.Append(context.Background(), tx, newEnvelope("event-rollback", "fact-rollback")); err != nil {
			return err
		}
		return stderrors.New("注入业务失败")
	})
	if err == nil {
		t.Fatal("注入业务失败时事务应回滚")
	}
	assertTableCount(t, db, "test_foundation_business_fact", 1)
	assertTableCount(t, db, "tb_outbox_event", 1)
}

func TestOutboxUniqueFailureRollsBackBusinessFact(t *testing.T) {
	db := testutil.NewPostgresTransaction(t)
	createTemporaryOutboxTables(t, db)
	repository := outbox.NewRepository()

	if err := db.Transaction(func(tx *gorm.DB) error {
		_, err := repository.Append(context.Background(), tx, newEnvelope("event-duplicate", "first"))
		return err
	}); err != nil {
		t.Fatalf("准备重复事件失败：%v", err)
	}

	err := db.Transaction(func(tx *gorm.DB) error {
		if err := tx.Exec("INSERT INTO test_foundation_business_fact (business_key) VALUES (?)", "must-rollback").Error; err != nil {
			return err
		}
		_, appendErr := repository.Append(context.Background(), tx, newEnvelope("event-duplicate", "second"))
		return appendErr
	})
	if err == nil {
		t.Fatal("重复事件 ID 必须导致 Outbox 写入失败")
	}
	assertTableCount(t, db, "test_foundation_business_fact", 0)
	assertTableCount(t, db, "tb_outbox_event", 1)
}

func newEnvelope(eventID, businessKey string) outbox.Envelope {
	return outbox.Envelope{
		EventID: eventID, EventType: "foundation.example.created", PayloadVersion: 1,
		AggregateType: "example", AggregateID: businessKey,
		ResourceType: "example", ResourceID: businessKey, BusinessKey: businessKey,
		RequestID: "request-1", CorrelationID: "correlation-1",
		Payload: struct {
			BusinessKey string `json:"business_key"`
		}{BusinessKey: businessKey},
	}
}

func createTemporaryOutboxTables(t *testing.T, db *gorm.DB) {
	t.Helper()
	if err := db.Exec(`CREATE TEMP TABLE test_foundation_business_fact (
		id bigserial PRIMARY KEY, business_key varchar(100) NOT NULL UNIQUE
	) ON COMMIT DROP`).Error; err != nil {
		t.Fatalf("创建业务事实测试表失败：%v", err)
	}
	testutil.CreateTemporaryOutboxTable(t, db)
}

func assertTableCount(t *testing.T, db *gorm.DB, table string, expected int64) {
	t.Helper()
	var count int64
	if err := db.Table(table).Count(&count).Error; err != nil {
		t.Fatalf("统计表 %s 失败：%v", table, err)
	}
	if count != expected {
		t.Fatalf("表 %s 行数错误：得到 %d，期望 %d", table, count, expected)
	}
}
