package connector_events

import (
	"context"
	"errors"
	"regexp"
	"testing"

	"github.com/DATA-DOG/go-sqlmock"
	connector_workqueue "github.com/pingcap-inc/nexuscrm-connectors/features/connector/workqueue"
)

func TestRequireWorkItemOnlyExecutionCutoverAcceptsStampedControlplaneMigration(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock.New: %v", err)
	}
	defer db.Close()

	mock.ExpectQuery(regexp.QuoteMeta("SELECT Version, Description\n  FROM _System_Migration\n WHERE Id = ?\n LIMIT 1")).
		WithArgs(WorkItemOnlyExecutionCutoverMigrationID).
		WillReturnRows(sqlmock.NewRows([]string{"Version", "Description"}).AddRow(WorkItemOnlyExecutionCutoverMigrationVersion, WorkItemOnlyExecutionCutoverMigrationDescription))

	if err := NewService(db, nil, nil).RequireWorkItemOnlyExecutionCutover(context.Background()); err != nil {
		t.Fatalf("RequireWorkItemOnlyExecutionCutover: %v", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

func TestRequireWorkItemOnlyExecutionCutoverFailsClosedWithoutReceipt(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock.New: %v", err)
	}
	defer db.Close()

	mock.ExpectQuery(regexp.QuoteMeta("SELECT Version, Description\n  FROM _System_Migration\n WHERE Id = ?\n LIMIT 1")).
		WithArgs(WorkItemOnlyExecutionCutoverMigrationID).
		WillReturnRows(sqlmock.NewRows([]string{"Version", "Description"}))

	err = NewService(db, nil, nil).RequireWorkItemOnlyExecutionCutover(context.Background())
	if !errors.Is(err, ErrWorkItemOnlyExecutionCutoverRequired) {
		t.Fatalf("RequireWorkItemOnlyExecutionCutover error = %v, want %v", err, ErrWorkItemOnlyExecutionCutoverRequired)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

func TestRequireWorkItemOnlyExecutionCutoverRejectsStaleAttestation(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock.New: %v", err)
	}
	defer db.Close()

	mock.ExpectQuery(regexp.QuoteMeta("SELECT Version, Description\n  FROM _System_Migration\n WHERE Id = ?\n LIMIT 1")).
		WithArgs(WorkItemOnlyExecutionCutoverMigrationID).
		WillReturnRows(sqlmock.NewRows([]string{"Version", "Description"}).AddRow(1, "older handoff"))

	err = NewService(db, nil, nil).RequireWorkItemOnlyExecutionCutover(context.Background())
	if !errors.Is(err, ErrWorkItemOnlyExecutionCutoverRequired) {
		t.Fatalf("RequireWorkItemOnlyExecutionCutover error = %v, want %v", err, ErrWorkItemOnlyExecutionCutoverRequired)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

func TestRequireWorkItemOnlyExecutionCutoverPropagatesCancellation(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock.New: %v", err)
	}
	defer db.Close()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()
	err = NewService(db, nil, nil).RequireWorkItemOnlyExecutionCutover(ctx)
	if !errors.Is(err, context.Canceled) {
		t.Fatalf("RequireWorkItemOnlyExecutionCutover error = %v, want context.Canceled", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

func TestRejectLegacyEventReplayAfterWorkItemOnlyCutoverAllowsPreCutoverReplay(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock.New: %v", err)
	}
	defer db.Close()

	mock.ExpectBegin()
	tx, err := db.BeginTx(context.Background(), nil)
	if err != nil {
		t.Fatalf("BeginTx: %v", err)
	}
	mock.ExpectQuery(regexp.QuoteMeta("SELECT Version, Description\n  FROM _System_Migration\n WHERE Id = ?\n LIMIT 1")).
		WithArgs(WorkItemOnlyExecutionCutoverMigrationID).
		WillReturnRows(sqlmock.NewRows([]string{"Version", "Description"}))
	mock.ExpectRollback()

	attested, err := rejectLegacyEventReplayAfterWorkItemOnlyCutover(context.Background(), tx, "event-pre-cutover")
	if err != nil {
		t.Fatalf("rejectLegacyEventReplayAfterWorkItemOnlyCutover: %v", err)
	}
	if attested {
		t.Fatal("pre-cutover replay attestation = true, want false")
	}
	if err := tx.Rollback(); err != nil {
		t.Fatalf("Rollback: %v", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

func TestRejectLegacyEventReplayAfterWorkItemOnlyCutoverAllowsMissingMigrationTable(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock.New: %v", err)
	}
	defer db.Close()

	mock.ExpectBegin()
	tx, err := db.BeginTx(context.Background(), nil)
	if err != nil {
		t.Fatalf("BeginTx: %v", err)
	}
	mock.ExpectQuery(regexp.QuoteMeta("SELECT Version, Description\n  FROM _System_Migration\n WHERE Id = ?\n LIMIT 1")).
		WithArgs(WorkItemOnlyExecutionCutoverMigrationID).
		WillReturnError(errors.New("Error 1146 (42S02): Table 'nexuscrm._System_Migration' doesn't exist"))
	mock.ExpectRollback()

	attested, err := rejectLegacyEventReplayAfterWorkItemOnlyCutover(context.Background(), tx, "event-pre-migration-table")
	if err != nil {
		t.Fatalf("rejectLegacyEventReplayAfterWorkItemOnlyCutover: %v", err)
	}
	if attested {
		t.Fatal("missing migration table replay attestation = true, want false")
	}
	if err := tx.Rollback(); err != nil {
		t.Fatalf("Rollback: %v", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

func TestRejectLegacyEventReplayAfterWorkItemOnlyCutoverRejectsAttestedReceipt(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock.New: %v", err)
	}
	defer db.Close()

	mock.ExpectBegin()
	tx, err := db.BeginTx(context.Background(), nil)
	if err != nil {
		t.Fatalf("BeginTx: %v", err)
	}
	mock.ExpectQuery(regexp.QuoteMeta("SELECT Version, Description\n  FROM _System_Migration\n WHERE Id = ?\n LIMIT 1")).
		WithArgs(WorkItemOnlyExecutionCutoverMigrationID).
		WillReturnRows(sqlmock.NewRows([]string{"Version", "Description"}).AddRow(WorkItemOnlyExecutionCutoverMigrationVersion, WorkItemOnlyExecutionCutoverMigrationDescription))

	mock.ExpectQuery(regexp.QuoteMeta("SELECT COUNT(*)\n  FROM _System_ConnectorWorkItem\n WHERE EventId = ?\n   AND WorkType IN (?, ?)\n   AND Attempt > 0")).
		WithArgs("event-legacy", connector_workqueue.WorkTypeConnectorEvent, connector_workqueue.WorkTypeWorkflowActionApply).
		WillReturnRows(sqlmock.NewRows([]string{"COUNT(*)"}).AddRow(0))
	mock.ExpectRollback()
	_, err = rejectLegacyEventReplayAfterWorkItemOnlyCutover(context.Background(), tx, "event-legacy")
	if !errors.Is(err, ErrLegacyEventReplayRetired) {
		t.Fatalf("rejectLegacyEventReplayAfterWorkItemOnlyExecutionCutover error = %v, want %v", err, ErrLegacyEventReplayRetired)
	}
	if err := tx.Rollback(); err != nil {
		t.Fatalf("Rollback: %v", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

func TestRejectLegacyEventReplayAfterWorkItemOnlyCutoverAllowsAttestedWorkItemReplay(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock.New: %v", err)
	}
	defer db.Close()

	mock.ExpectBegin()
	tx, err := db.BeginTx(context.Background(), nil)
	if err != nil {
		t.Fatalf("BeginTx: %v", err)
	}
	mock.ExpectQuery(regexp.QuoteMeta("SELECT Version, Description\n  FROM _System_Migration\n WHERE Id = ?\n LIMIT 1")).
		WithArgs(WorkItemOnlyExecutionCutoverMigrationID).
		WillReturnRows(sqlmock.NewRows([]string{"Version", "Description"}).AddRow(WorkItemOnlyExecutionCutoverMigrationVersion, WorkItemOnlyExecutionCutoverMigrationDescription))

	mock.ExpectQuery(regexp.QuoteMeta("SELECT COUNT(*)\n  FROM _System_ConnectorWorkItem\n WHERE EventId = ?\n   AND WorkType IN (?, ?)\n   AND Attempt > 0")).
		WithArgs("event-workitem", connector_workqueue.WorkTypeConnectorEvent, connector_workqueue.WorkTypeWorkflowActionApply).
		WillReturnRows(sqlmock.NewRows([]string{"COUNT(*)"}).AddRow(1))
	mock.ExpectRollback()
	attested, err := rejectLegacyEventReplayAfterWorkItemOnlyCutover(context.Background(), tx, "event-workitem")
	if err != nil {
		t.Fatalf("rejectLegacyEventReplayAfterWorkItemOnlyCutover: %v", err)
	}
	if !attested {
		t.Fatal("attested WorkItem replay attestation = false, want true")
	}
	if err := tx.Rollback(); err != nil {
		t.Fatalf("Rollback: %v", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}
