package migrate

import (
	"encoding/json"
	"sync"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
	dispatchent "github.com/tidbcloud/aisupport/packages/sdk-go/contracts/entity/ticket-manager/dispatch"
	modbase "github.com/tidbcloud/aisupport/services/ticket-manager/models/base"
	collabmod "github.com/tidbcloud/aisupport/services/ticket-manager/models/collaboration"
	dispatchmodel "github.com/tidbcloud/aisupport/services/ticket-manager/models/dispatch"
	engmodel "github.com/tidbcloud/aisupport/services/ticket-manager/models/engineer"
	ticketmod "github.com/tidbcloud/aisupport/services/ticket-manager/models/ticket"
	workspacemod "github.com/tidbcloud/aisupport/services/ticket-manager/models/workspace"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
	"gorm.io/gorm/schema"
)

func TestTicketSchemaDoesNotHaveWorkloadIndex(t *testing.T) {
	t.Parallel()

	ticketSchema, err := schema.Parse(&ticketmod.Ticket{}, &sync.Map{}, schema.NamingStrategy{})
	require.NoError(t, err)
	require.Nil(t, ticketSchema.LookIndex("idx_ticket_assignee_resolved_at"))
}

func TestMigratorIncludesEngineerMigration(t *testing.T) {
	t.Parallel()

	require.PanicsWithValue(t, "migration version is already added: 202607101200", func() {
		addRecord("202607101200", func(db *gorm.DB) error { return nil })
	})
}

func TestMigratorIncludesTimelineEvidenceWindowIndexMigration(t *testing.T) {
	t.Parallel()

	require.PanicsWithValue(t, "migration version is already added: 202608311200", func() {
		addRecord("202608311200", func(db *gorm.DB) error { return nil })
	})
}

func TestTimelineEvidenceWindowIndexMigration(t *testing.T) {
	t.Parallel()

	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)
	require.NoError(t, db.Exec(`CREATE TABLE workspace_timeline_evidence (
		id INTEGER PRIMARY KEY,
		workspace_id INTEGER NOT NULL,
		source TEXT NOT NULL,
		source_type TEXT NOT NULL,
		occurred_at DATETIME
	)`).Error)
	require.False(t, db.Migrator().HasIndex(&workspacemod.TimelineEvidence{}, timelineEvidenceWindowIndex))

	require.NoError(t, addTimelineEvidenceWindowIndex(db))
	require.True(t, db.Migrator().HasIndex(&workspacemod.TimelineEvidence{}, timelineEvidenceWindowIndex))
	require.NoError(t, addTimelineEvidenceWindowIndex(db))
}

func TestMigratorIncludesEngineerCleanupMigration(t *testing.T) {
	t.Parallel()

	require.PanicsWithValue(t, "migration version is already added: 202607101300", func() {
		addRecord("202607101300", func(db *gorm.DB) error { return nil })
	})
}

func TestMigratorIncludesEngineerFieldRemovalMigration(t *testing.T) {
	t.Parallel()

	require.PanicsWithValue(t, "migration version is already added: 202607101400", func() {
		addRecord("202607101400", func(db *gorm.DB) error { return nil })
	})
}

func TestMigratorIncludesEngineerDispatchStateMigration(t *testing.T) {
	t.Parallel()

	require.PanicsWithValue(t, "migration version is already added: 202607101500", func() {
		addRecord("202607101500", func(db *gorm.DB) error { return nil })
	})
}

func TestMigratorIncludesDispatchModelsMigration(t *testing.T) {
	t.Parallel()

	require.PanicsWithValue(t, "migration version is already added: 202607151100", func() {
		addRecord("202607151100", func(db *gorm.DB) error { return nil })
	})
}

func TestMigratorIncludesDispatchExecutionTriggerCleanupMigration(t *testing.T) {
	t.Parallel()

	require.PanicsWithValue(t, "migration version is already added: 202607151200", func() {
		addRecord("202607151200", func(db *gorm.DB) error { return nil })
	})
}

func TestMigratorIncludesDispatchContextAssigneeMigration(t *testing.T) {
	t.Parallel()

	require.PanicsWithValue(t, "migration version is already added: 202607161100", func() {
		addRecord("202607161100", func(db *gorm.DB) error { return nil })
	})
}

func TestMigratorIncludesDispatchContextAssigneeL2Migration(t *testing.T) {
	t.Parallel()

	require.PanicsWithValue(t, "migration version is already added: 202607231100", func() {
		addRecord("202607231100", func(db *gorm.DB) error { return nil })
	})
}

func TestMigratorIncludesDispatchContextAssignmentNotificationMigration(t *testing.T) {
	t.Parallel()

	require.PanicsWithValue(t, "migration version is already added: 202608131101", func() {
		addRecord("202608131101", func(db *gorm.DB) error { return nil })
	})
}

func TestMigratorIncludesWorkspaceLastTimelineSyncAtMigration(t *testing.T) {
	t.Parallel()

	require.PanicsWithValue(t, "migration version is already added: 202608171100", func() {
		addRecord("202608171100", func(db *gorm.DB) error { return nil })
	})
}

func TestMigratorIncludesDispatchPolicyTriggerIssueStatusMigration(t *testing.T) {
	t.Parallel()

	require.PanicsWithValue(t, "migration version is already added: 202607241100", func() {
		addRecord("202607241100", func(db *gorm.DB) error { return nil })
	})
}

func TestMigratorIncludesDispatchPolicyTriggerJSONMigration(t *testing.T) {
	t.Parallel()

	require.PanicsWithValue(t, "migration version is already added: 202607251100", func() {
		addRecord("202607251100", func(db *gorm.DB) error { return nil })
	})
}

func TestMigratorIncludesDispatchTriggerMigration(t *testing.T) {
	t.Parallel()

	require.PanicsWithValue(t, "migration version is already added: 202607161200", func() {
		addRecord("202607161200", func(db *gorm.DB) error { return nil })
	})
}

func TestMigratorIncludesDispatchTriggerCreatedAtMigration(t *testing.T) {
	t.Parallel()

	require.PanicsWithValue(t, "migration version is already added: 202607171100", func() {
		addRecord("202607171100", func(db *gorm.DB) error { return nil })
	})
}

func TestMigratorIncludesDispatchRuleIssueFieldsMigration(t *testing.T) {
	t.Parallel()

	require.PanicsWithValue(t, "migration version is already added: 202607171200", func() {
		addRecord("202607171200", func(db *gorm.DB) error { return nil })
	})
}

func TestMigratorIncludesDispatchRuleWeeklyTimeWindowMigration(t *testing.T) {
	t.Parallel()

	require.PanicsWithValue(t, "migration version is already added: 202607171300", func() {
		addRecord("202607171300", func(db *gorm.DB) error { return nil })
	})
}

func TestMigratorIncludesDispatchRuleCustomerFieldsMigration(t *testing.T) {
	t.Parallel()

	require.PanicsWithValue(t, "migration version is already added: 202607171400", func() {
		addRecord("202607171400", func(db *gorm.DB) error { return nil })
	})
}

func TestMigratorIncludesDispatchRuleComponentFieldMigration(t *testing.T) {
	t.Parallel()

	require.PanicsWithValue(t, "migration version is already added: 202607171500", func() {
		addRecord("202607171500", func(db *gorm.DB) error { return nil })
	})
}

func TestMigratorIncludesDispatchPolicyFamilyIDMigration(t *testing.T) {
	t.Parallel()

	require.PanicsWithValue(t, "migration version is already added: 202607201100", func() {
		addRecord("202607201100", func(db *gorm.DB) error { return nil })
	})
}

func TestMigratorIncludesDispatchContextTriageMigration(t *testing.T) {
	t.Parallel()

	require.PanicsWithValue(t, "migration version is already added: 202607211100", func() {
		addRecord("202607211100", func(db *gorm.DB) error { return nil })
	})
}

func TestMigratorIncludesLegacyTicketTriageCleanupMigration(t *testing.T) {
	t.Parallel()

	require.PanicsWithValue(t, "migration version is already added: 202607211200", func() {
		addRecord("202607211200", func(db *gorm.DB) error { return nil })
	})
}

func TestMigratorIncludesDispatchContextTriageLanguageMigration(t *testing.T) {
	t.Parallel()

	require.PanicsWithValue(t, "migration version is already added: 202607271100", func() {
		addRecord("202607271100", func(db *gorm.DB) error { return nil })
	})
}

func TestMigrateDispatchContextTriageLanguageBackfillsExistingTriage(t *testing.T) {
	t.Parallel()

	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)
	require.NoError(t, db.Exec(`CREATE TABLE dispatch_contexts (
		issue_key TEXT PRIMARY KEY,
		current_support_tier TEXT NOT NULL,
		assignee TEXT NOT NULL,
		triage_issue_type TEXT
	)`).Error)
	require.NoError(t, db.Exec(`INSERT INTO dispatch_contexts
		(issue_key, current_support_tier, assignee, triage_issue_type)
		VALUES ('CASE-001', 'L1', '', 'technical'), ('CASE-002', 'L1', '', NULL)`).Error)

	require.NoError(t, migrateDispatchContextTriageLanguage(db))

	var triaged dispatchmodel.Context
	require.NoError(t, db.First(&triaged, "issue_key = ?", "CASE-001").Error)
	require.NotNil(t, triaged.TriageLanguage)
	require.Equal(t, "other", *triaged.TriageLanguage)

	var untriaged dispatchmodel.Context
	require.NoError(t, db.First(&untriaged, "issue_key = ?", "CASE-002").Error)
	require.Nil(t, untriaged.TriageLanguage)
}

func TestMigrateDropLegacyTicketTriageColumns(t *testing.T) {
	t.Parallel()

	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)
	require.NoError(t, db.Exec(`CREATE TABLE tickets (
		"id" INTEGER PRIMARY KEY,
		"jira_issue_key" TEXT NOT NULL,
		"triage_priority" TEXT NOT NULL,
		"triage_type" TEXT NOT NULL,
		"triage_module" TEXT NOT NULL,
		"triage_tags_json" JSON NOT NULL
	)`).Error)

	require.NoError(t, migrateDropLegacyTicketTriageColumns(db))
	var columns []struct {
		Name string `gorm:"column:name"`
	}
	require.NoError(t, db.Raw("PRAGMA table_info(tickets)").Scan(&columns).Error)
	columnNames := make([]string, 0, len(columns))
	for _, column := range columns {
		columnNames = append(columnNames, column.Name)
	}
	require.Contains(t, columnNames, "jira_issue_key")
	for _, column := range []string{"triage_priority", "triage_type", "triage_module", "triage_tags_json"} {
		require.NotContains(t, columnNames, column)
	}
}

func TestMigrateDispatchRuleIssueFields(t *testing.T) {
	t.Parallel()

	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)
	require.NoError(t, db.Exec(`CREATE TABLE dispatch_rules (
		id INTEGER PRIMARY KEY,
		condition_json JSON NOT NULL
	)`).Error)
	require.NoError(t, db.Exec(`INSERT INTO dispatch_rules (id, condition_json) VALUES (1, '{
		"all": [
			{"field": "project_key", "operator": "eq", "value": "TCID"},
			{"any": [
				{"field": "summary", "operator": "contains", "value": "timeout"},
				{"not": {"field": "environment", "operator": "eq", "value": "on_premise"}}
			]}
		]
	}')`).Error)

	require.NoError(t, migrateDispatchRuleIssueFields(db))

	var rule dispatchmodel.Rule
	require.NoError(t, db.Select("id", "condition_json").First(&rule, 1).Error)
	var condition dispatchent.ConditionExpr
	require.NoError(t, json.Unmarshal(rule.ConditionJSON, &condition))
	require.Equal(t, dispatchent.FieldOptionIssueProjectKey, *condition.All[0].Field)
	require.Equal(t, dispatchent.FieldOptionIssueSummary, *condition.All[1].Any[0].Field)
	require.Equal(t, "issue.environment", *condition.All[1].Any[1].Not.Field)
}

func TestMigrateDispatchRuleWeeklyTimeWindow(t *testing.T) {
	t.Parallel()

	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)
	require.NoError(t, db.Exec(`CREATE TABLE dispatch_rules (
		id INTEGER PRIMARY KEY,
		condition_json JSON NOT NULL
	)`).Error)
	require.NoError(t, db.Exec(`INSERT INTO dispatch_rules (id, condition_json) VALUES (1, '{
		"all": [
			{"field": "issue.project_key", "operator": "eq", "value": "TCID"},
			{"field": "issue.created_at", "operator": "business_hours", "business_day_value": {
				"timezone": "Asia/Shanghai", "weekdays": ["MON", "FRI"], "start": "09:00", "end": "18:00"
			}}
		]
	}')`).Error)

	require.NoError(t, migrateDispatchRuleWeeklyTimeWindow(db))

	var rule dispatchmodel.Rule
	require.NoError(t, db.Select("id", "condition_json").First(&rule, 1).Error)
	var condition dispatchent.ConditionExpr
	require.NoError(t, json.Unmarshal(rule.ConditionJSON, &condition))
	windowCondition := condition.All[1]
	require.Equal(t, dispatchent.ConditionOperatorWithinWeeklyTimeWindow, *windowCondition.Operator)
	require.Equal(t, "Asia/Shanghai", windowCondition.WeeklyTimeWindow.Timezone)
	require.Equal(t, []dispatchent.Weekday{dispatchent.WeekdayMonday, dispatchent.WeekdayFriday}, windowCondition.WeeklyTimeWindow.Weekdays)
}

func TestMigrateDispatchRuleCustomerFields(t *testing.T) {
	t.Parallel()

	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)
	require.NoError(t, db.Exec(`CREATE TABLE dispatch_rules (
		id INTEGER PRIMARY KEY,
		condition_json JSON NOT NULL
	)`).Error)
	require.NoError(t, db.Exec(`INSERT INTO dispatch_rules (id, condition_json) VALUES (1, '{
		"all": [
			{"field": "issue.project_key", "operator": "eq", "value": "TCID"},
			{"any": [
				{"field": "issue.summary", "operator": "contains", "value": "timeout"},
				{"not": {"field": "issue.environment", "operator": "eq", "value": "on_premise"}}
			]}
		]
	}')`).Error)

	require.NoError(t, migrateDispatchRuleCustomerFields(db))

	var rule dispatchmodel.Rule
	require.NoError(t, db.Select("id", "condition_json").First(&rule, 1).Error)
	var condition dispatchent.ConditionExpr
	require.NoError(t, json.Unmarshal(rule.ConditionJSON, &condition))
	require.Equal(t, dispatchent.FieldOptionIssueProjectKey, *condition.All[0].Field)
	require.Equal(t, dispatchent.FieldOptionIssueSummary, *condition.All[1].Any[0].Field)
	require.Equal(t, dispatchent.FieldOptionIssueCustomerDeploymentModel, *condition.All[1].Any[1].Not.Field)
}

func TestMigrateDispatchRuleComponentField(t *testing.T) {
	t.Parallel()

	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)
	require.NoError(t, db.Exec(`CREATE TABLE dispatch_rules (
		id INTEGER PRIMARY KEY,
		condition_json JSON NOT NULL
	)`).Error)
	require.NoError(t, db.Exec(`INSERT INTO dispatch_rules (id, condition_json) VALUES (1, '{
		"all": [
			{"field": "issue.component", "operator": "eq", "value": "TiDB"},
			{"not": {"field": "issue.summary", "operator": "contains", "value": "test"}}
		]
	}')`).Error)

	require.NoError(t, migrateDispatchRuleComponentField(db))

	var rule dispatchmodel.Rule
	require.NoError(t, db.Select("id", "condition_json").First(&rule, 1).Error)
	var condition dispatchent.ConditionExpr
	require.NoError(t, json.Unmarshal(rule.ConditionJSON, &condition))
	require.Equal(t, dispatchent.FieldOptionIssueComponents, *condition.All[0].Field)
	require.Equal(t, dispatchent.FieldOptionIssueSummary, *condition.All[1].Not.Field)
}

func TestDispatchModelsMigration(t *testing.T) {
	t.Parallel()

	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)
	require.NoError(t, migrateDispatchModels(db))

	for _, model := range []any{
		&dispatchmodel.Policy{},
		&legacyDispatchPolicyTrigger{},
		&legacyDispatchPolicyTriggerProject{},
		&dispatchmodel.Rule{},
		&dispatchmodel.Context{},
		&dispatchmodel.Execution{},
		&dispatchmodel.RuleExecution{},
	} {
		require.True(t, db.Migrator().HasTable(model))
	}
}

func TestDispatchPolicyTriggerIssueStatusMigration(t *testing.T) {
	t.Parallel()

	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)
	require.NoError(t, migrateDispatchPolicyTriggerIssueStatus(db))
	require.True(t, db.Migrator().HasTable(&legacyDispatchPolicyTriggerIssueStatus{}))
	require.True(t, db.Migrator().HasIndex(&legacyDispatchPolicyTriggerIssueStatus{}, "idx_dispatch_policy_trigger_issue_status"))
}

func TestDispatchPolicyTriggerJSONMigration(t *testing.T) {
	t.Parallel()

	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)
	require.NoError(t, db.Exec("CREATE TABLE dispatch_policies (id INTEGER PRIMARY KEY)").Error)
	require.NoError(t, db.AutoMigrate(
		&legacyDispatchPolicyTrigger{},
		&legacyDispatchPolicyTriggerProject{},
		&legacyDispatchPolicyTriggerIssueStatus{},
	))
	require.NoError(t, db.Exec("INSERT INTO dispatch_policies (id) VALUES (1), (2)").Error)
	require.NoError(t, db.Create(&legacyDispatchPolicyTrigger{PolicyID: 1, Enable: true}).Error)
	require.NoError(t, db.Create([]legacyDispatchPolicyTriggerProject{
		{PolicyID: 1, ProjectKey: "TCCN"},
		{PolicyID: 1, ProjectKey: "TCID"},
	}).Error)
	require.NoError(t, db.Create([]legacyDispatchPolicyTriggerIssueStatus{
		{PolicyID: 1, IssueStatus: "WAITING FOR SUPPORT"},
		{PolicyID: 1, IssueStatus: "RESOLVED"},
	}).Error)

	require.NoError(t, migrateDispatchPolicyTriggerJSON(db))

	require.True(t, db.Migrator().HasColumn(&dispatchmodel.Policy{}, "TriggerJSON"))
	require.False(t, db.Migrator().HasTable(&legacyDispatchPolicyTrigger{}))
	require.False(t, db.Migrator().HasTable(&legacyDispatchPolicyTriggerProject{}))
	require.False(t, db.Migrator().HasTable(&legacyDispatchPolicyTriggerIssueStatus{}))

	var triggerJSON string
	require.NoError(t, db.Table("dispatch_policies").Select("trigger_json").Where("id = ?", 1).Scan(&triggerJSON).Error)
	var trigger dispatchmodel.PolicyTrigger
	require.NoError(t, json.Unmarshal([]byte(triggerJSON), &trigger))
	require.Equal(t, dispatchmodel.PolicyTrigger{
		Enable:        true,
		IssueStatusIn: []string{"RESOLVED", "WAITING FOR SUPPORT"},
		ProjectKeyIn:  []string{"TCCN", "TCID"},
	}, trigger)

	require.NoError(t, db.Table("dispatch_policies").Select("trigger_json").Where("id = ?", 2).Scan(&triggerJSON).Error)
	trigger = dispatchmodel.PolicyTrigger{}
	require.NoError(t, json.Unmarshal([]byte(triggerJSON), &trigger))
	require.Equal(t, dispatchmodel.PolicyTrigger{}, trigger)
}

func TestDispatchPolicyFamilyIDMigration(t *testing.T) {
	t.Parallel()

	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)
	require.NoError(t, db.Exec(`CREATE TABLE dispatch_policies (
		id INTEGER PRIMARY KEY AUTOINCREMENT,
		created_at DATETIME NOT NULL,
		updated_at DATETIME,
		name TEXT NOT NULL,
		version INTEGER,
		status TEXT NOT NULL,
		replaces_policy_id INTEGER,
		created_by TEXT NOT NULL,
		published_by TEXT NOT NULL DEFAULT '',
		published_at DATETIME
	)`).Error)
	require.NoError(t, db.Exec(`INSERT INTO dispatch_policies (
		id, created_at, name, version, status, replaces_policy_id, created_by
	) VALUES
		(1, CURRENT_TIMESTAMP, 'family-a-v1', 1, 'retired', NULL, 'owner@example.com'),
		(2, CURRENT_TIMESTAMP, 'family-a-v2', 2, 'active', 1, 'owner@example.com'),
		(3, CURRENT_TIMESTAMP, 'family-a-v3', NULL, 'draft', 2, 'owner@example.com'),
		(4, CURRENT_TIMESTAMP, 'family-b-v1', 1, 'active', NULL, 'owner@example.com')`).Error)

	require.NoError(t, migrateDispatchPolicyFamilyID(db))

	var policies []dispatchmodel.Policy
	require.NoError(t, db.Order("id ASC").Find(&policies).Error)
	require.Len(t, policies, 4)
	require.Regexp(t, `^pol_[0-9A-Za-z]{16}$`, policies[0].FamilyID)
	require.Equal(t, policies[0].FamilyID, policies[1].FamilyID)
	require.Equal(t, policies[0].FamilyID, policies[2].FamilyID)
	require.Regexp(t, `^pol_[0-9A-Za-z]{16}$`, policies[3].FamilyID)
	require.NotEqual(t, policies[0].FamilyID, policies[3].FamilyID)
	require.True(t, db.Migrator().HasIndex(&dispatchmodel.Policy{}, "idx_dispatch_policy_family_id"))
}

func TestDispatchPolicyFamilyIDMigrationRejectsBrokenChains(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name              string
		rows              string
		expectedErrorPart string
	}{
		{name: "missing policy", rows: "(1, 99)", expectedErrorPart: "references missing policy 99"},
		{name: "cycle", rows: "(1, 2), (2, 1)", expectedErrorPart: "contains a cycle"},
	}
	for _, test := range tests {
		test := test
		t.Run(test.name, func(t *testing.T) {
			t.Parallel()
			db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
			require.NoError(t, err)
			require.NoError(t, db.Exec("CREATE TABLE dispatch_policies (id INTEGER PRIMARY KEY, replaces_policy_id INTEGER)").Error)
			require.NoError(t, db.Exec("INSERT INTO dispatch_policies (id, replaces_policy_id) VALUES "+test.rows).Error)

			err = migrateDispatchPolicyFamilyID(db)

			require.ErrorContains(t, err, test.expectedErrorPart)
		})
	}
}

func TestDispatchContextAssigneeMigration(t *testing.T) {
	t.Parallel()

	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)
	require.NoError(t, db.Exec(`CREATE TABLE dispatch_contexts (
		issue_key TEXT PRIMARY KEY,
		current_support_tier TEXT NOT NULL
	)`).Error)
	require.NoError(t, db.Exec(`INSERT INTO dispatch_contexts (issue_key, current_support_tier) VALUES ('TCID-101', 'L1')`).Error)

	require.NoError(t, migrateDispatchContextAssignee(db))
	require.True(t, db.Migrator().HasColumn(&dispatchmodel.Context{}, "assignee"))

	var context dispatchmodel.Context
	require.NoError(t, db.Where("issue_key = ?", "TCID-101").First(&context).Error)
	require.Equal(t, "", context.Assignee)
}

func TestDispatchContextAssigneeL2Migration(t *testing.T) {
	t.Parallel()

	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)
	require.NoError(t, db.Exec(`CREATE TABLE dispatch_contexts (
		issue_key TEXT PRIMARY KEY,
		current_support_tier TEXT NOT NULL,
		assignee TEXT NOT NULL DEFAULT ''
	)`).Error)
	require.NoError(t, db.Exec(`INSERT INTO dispatch_contexts (issue_key, current_support_tier, assignee)
		VALUES ('TCID-101', 'L1', 'alice@example.com')`).Error)

	require.NoError(t, migrateDispatchContextAssigneeL2(db))
	require.True(t, db.Migrator().HasColumn(&dispatchmodel.Context{}, "assignee_l2"))

	var context dispatchmodel.Context
	require.NoError(t, db.Where("issue_key = ?", "TCID-101").First(&context).Error)
	require.Equal(t, "alice@example.com", context.Assignee)
	require.Equal(t, "", context.AssigneeL2)
}

func TestDispatchContextAssignmentNotificationMigration(t *testing.T) {
	t.Parallel()

	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)
	require.NoError(t, db.Exec(`CREATE TABLE dispatch_contexts (
		issue_key TEXT PRIMARY KEY,
		current_support_tier TEXT NOT NULL,
		assignee TEXT NOT NULL DEFAULT '',
		assignee_l2 TEXT NOT NULL DEFAULT ''
	)`).Error)
	require.NoError(t, db.Exec(`INSERT INTO dispatch_contexts (
		issue_key, current_support_tier, assignee, assignee_l2
	) VALUES ('TCOC-101', '', 'owner@example.com', 'l2@example.com')`).Error)

	require.NoError(t, migrateDispatchContextAssignmentNotificationState(db))
	require.True(t, db.Migrator().HasColumn(&dispatchmodel.Context{}, "assignment_notification_state_json"))

	var dispatchContext dispatchmodel.Context
	require.NoError(t, db.First(&dispatchContext, "issue_key = ?", "TCOC-101").Error)
	require.Nil(t, dispatchContext.AssignmentNotificationStateJSON)
}

func TestDispatchTriggerMigration(t *testing.T) {
	t.Parallel()

	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)
	require.NoError(t, migrateDispatchTrigger(db))
	require.True(t, db.Migrator().HasTable(&dispatchmodel.DispatchTrigger{}))
}

func TestDispatchTriggerCreatedAtMigration(t *testing.T) {
	t.Parallel()

	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)
	require.NoError(t, db.Exec(`CREATE TABLE dispatch_triggers (
		id INTEGER PRIMARY KEY AUTOINCREMENT,
		source TEXT NOT NULL,
		status TEXT NOT NULL,
		available_at DATETIME NOT NULL,
		failure_reason TEXT NOT NULL,
		input_json JSON NOT NULL,
		output_json JSON
	)`).Error)
	require.NoError(t, db.Exec(`INSERT INTO dispatch_triggers (
		source, status, available_at, failure_reason, input_json
	) VALUES
		('jira', 'completed', '2026-07-16 10:00:00', '', '{"issue_key":"TCID-101"}'),
		('internal', 'pending', '2026-07-16 11:00:00', '', '{"issue_key":"TCID-102"}')`).Error)

	require.NoError(t, migrateDispatchTriggerCreatedAt(db))
	require.True(t, db.Migrator().HasColumn(&dispatchmodel.DispatchTrigger{}, "created_at"))

	var triggers []dispatchmodel.DispatchTrigger
	require.NoError(t, db.Order("id").Find(&triggers).Error)
	require.Len(t, triggers, 2)
	require.False(t, triggers[0].CreatedAt.IsZero())
	require.Equal(t, triggers[0].CreatedAt, triggers[1].CreatedAt)
}

func TestDispatchExecutionTriggerCleanupMigration(t *testing.T) {
	t.Parallel()

	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)
	require.NoError(t, db.Exec(`CREATE TABLE dispatch_executions (
		id INTEGER PRIMARY KEY AUTOINCREMENT,
		created_at TIMESTAMP NOT NULL,
		updated_at TIMESTAMP,
		issue_key TEXT NOT NULL,
		support_tier TEXT NOT NULL,
		"trigger_type" TEXT NOT NULL,
		"triggered_at" DATETIME NOT NULL,
		"trigger_id" TEXT NOT NULL,
		mode TEXT NOT NULL,
		policy_id INTEGER,
		status TEXT NOT NULL,
		failure_code TEXT NOT NULL DEFAULT '',
		failure_reason TEXT NOT NULL,
		completed_at DATETIME
	)`).Error)
	require.NoError(t, db.Exec(`CREATE INDEX idx_dispatch_execution_trigger ON dispatch_executions (issue_key, support_tier, trigger_id)`).Error)

	require.NoError(t, removeDispatchExecutionTriggerFields(db))
	require.False(t, db.Migrator().HasColumn(&dispatchmodel.Execution{}, "trigger_id"))
	require.False(t, db.Migrator().HasColumn(&dispatchmodel.Execution{}, "trigger_type"))
	require.False(t, db.Migrator().HasColumn(&dispatchmodel.Execution{}, "triggered_at"))
	require.False(t, db.Migrator().HasIndex(&dispatchmodel.Execution{}, "idx_dispatch_execution_trigger"))
	require.True(t, db.Migrator().HasIndex(&dispatchmodel.Execution{}, "idx_dispatch_execution_running"))
}

func TestDispatchPolicySchemaIndexes(t *testing.T) {
	t.Parallel()

	policySchema, err := schema.Parse(&dispatchmodel.Policy{}, &sync.Map{}, schema.NamingStrategy{})
	require.NoError(t, err)
	require.NotNil(t, policySchema.LookIndex("idx_dispatch_policy_status"))
	require.Nil(t, policySchema.LookUpField("SupportTier"))
	require.Nil(t, policySchema.LookUpField("ProjectKey"))
	require.Nil(t, policySchema.LookUpField("DraftSlot"))
	require.Nil(t, policySchema.LookUpField("ActiveSlot"))
	require.Nil(t, policySchema.LookIndex("idx_dispatch_policy_draft_slot"))
	require.Nil(t, policySchema.LookIndex("idx_dispatch_policy_active_slot"))
	require.Nil(t, policySchema.LookUpField("Mode"))
	require.NotNil(t, policySchema.LookUpField("TriggerJSON"))

	ruleSchema, err := schema.Parse(&dispatchmodel.Rule{}, &sync.Map{}, schema.NamingStrategy{})
	require.NoError(t, err)
	require.NotNil(t, ruleSchema.LookIndex("idx_dispatch_rule_policy_name"))
	require.NotNil(t, ruleSchema.LookIndex("idx_dispatch_rule_policy_priority"))
	require.NotNil(t, ruleSchema.LookUpField("TransitionJSON"))
}

func TestDispatchRuleExecutionSchemaIndexes(t *testing.T) {
	t.Parallel()

	ruleExecutionSchema, err := schema.Parse(&dispatchmodel.RuleExecution{}, &sync.Map{}, schema.NamingStrategy{})
	require.NoError(t, err)
	require.NotNil(t, ruleExecutionSchema.LookIndex("idx_dispatch_rule_execution_execution_sequence"))
	require.NotNil(t, ruleExecutionSchema.LookIndex("idx_dispatch_rule_execution_execution"))
	require.NotNil(t, ruleExecutionSchema.LookIndex("idx_dispatch_rule_execution_rule"))
}

func TestDispatchExecutionSchemaIndexes(t *testing.T) {
	t.Parallel()

	executionSchema, err := schema.Parse(&dispatchmodel.Execution{}, &sync.Map{}, schema.NamingStrategy{})
	require.NoError(t, err)
	require.NotNil(t, executionSchema.LookIndex("idx_dispatch_execution_running"))
	require.Nil(t, executionSchema.LookUpField("InputJSON"))
	require.Nil(t, executionSchema.LookUpField("SupportTier"))
	require.Nil(t, executionSchema.LookUpField("TriggerID"))
	require.Nil(t, executionSchema.LookUpField("TriggerType"))
	require.Nil(t, executionSchema.LookUpField("TriggeredAt"))
	require.NotNil(t, executionSchema.LookUpField("OverwriteDataJSON"))
}

func TestDispatchExecutionOverwriteDataMigration(t *testing.T) {
	t.Parallel()

	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)
	require.NoError(t, db.Exec(`CREATE TABLE dispatch_executions (
		id INTEGER PRIMARY KEY AUTOINCREMENT,
		created_at TIMESTAMP NOT NULL,
		updated_at TIMESTAMP,
		issue_key TEXT NOT NULL,
		mode TEXT NOT NULL,
		policy_id INTEGER,
		status TEXT NOT NULL,
		failure_code TEXT NOT NULL DEFAULT '',
		failure_reason TEXT NOT NULL,
		completed_at DATETIME
	)`).Error)
	require.NoError(t, db.Exec(`INSERT INTO dispatch_executions (
		created_at, issue_key, mode, status, failure_reason
	) VALUES ('2026-08-13 10:00:00', 'TCID-101', 'live', 'succeeded', '')`).Error)

	require.NoError(t, migrateDispatchExecutionOverwriteData(db))
	require.True(t, db.Migrator().HasColumn(&dispatchmodel.Execution{}, "overwrite_data_json"))

	var execution dispatchmodel.Execution
	require.NoError(t, db.First(&execution).Error)
	require.Nil(t, execution.OverwriteDataJSON)
}

func TestDispatchExecutionInputCleanupMigration(t *testing.T) {
	t.Parallel()

	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)
	require.NoError(t, db.Exec(`CREATE TABLE dispatch_executions (
		id INTEGER PRIMARY KEY AUTOINCREMENT,
		created_at TIMESTAMP NOT NULL,
		updated_at TIMESTAMP,
		issue_key TEXT NOT NULL,
		input_json JSON NOT NULL,
		mode TEXT NOT NULL,
		policy_id INTEGER,
		status TEXT NOT NULL,
		failure_code TEXT NOT NULL DEFAULT '',
		failure_reason TEXT NOT NULL,
		completed_at DATETIME
	)`).Error)

	require.NoError(t, removeDispatchExecutionInput(db))
	require.False(t, db.Migrator().HasColumn(&dispatchmodel.Execution{}, "input_json"))
	require.True(t, db.Migrator().HasIndex(&dispatchmodel.Execution{}, "idx_dispatch_execution_running"))
}

func TestEngineerCleanupMigrationDropsLegacySchema(t *testing.T) {
	t.Parallel()

	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)
	require.NoError(t, db.Exec(`CREATE TABLE engineer_engineers (
		"email" TEXT PRIMARY KEY,
		"name" TEXT NOT NULL,
		"source_active" BOOLEAN NOT NULL,
		"dispatch_enabled" BOOLEAN NOT NULL,
		"expertise_json" TEXT NOT NULL,
		"source_synced_at" DATETIME NOT NULL
	)`).Error)
	require.NoError(t, db.Exec(`CREATE INDEX idx_engineer_eligibility ON engineer_engineers ("source_active", "dispatch_enabled")`).Error)
	require.NoError(t, db.Exec(`CREATE TABLE engineer_group_memberships (
		engineer_email TEXT NOT NULL,
		group_name TEXT NOT NULL,
		source_active BOOLEAN NOT NULL,
		source_synced_at DATETIME NOT NULL
	)`).Error)
	require.True(t, db.Migrator().HasColumn(&legacyEngineer{}, "name"))

	err = cleanupLegacyEngineerSchema(db)
	require.NoError(t, err)
	require.False(t, db.Migrator().HasTable("engineer_group_memberships"))
	require.False(t, db.Migrator().HasColumn("engineer_engineers", "name"))
	require.False(t, db.Migrator().HasColumn("engineer_engineers", "source_active"))
	require.False(t, db.Migrator().HasColumn("engineer_engineers", "source_synced_at"))
	require.True(t, db.Migrator().HasIndex(&engmodel.Engineer{}, "idx_engineer_eligibility"))
}

func TestEngineerFieldRemovalMigrationDropsExpertiseAndWorkloadIndex(t *testing.T) {
	t.Parallel()

	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)
	require.NoError(t, db.Exec(`CREATE TABLE engineer_engineers (
		"email" TEXT PRIMARY KEY,
		"dispatch_enabled" BOOLEAN NOT NULL,
		"expertise_json" TEXT NOT NULL
	)`).Error)
	require.NoError(t, db.Exec(`CREATE TABLE tickets (
		"jira_assignee" TEXT NOT NULL,
		"resolved_at" DATETIME
	)`).Error)
	require.NoError(t, db.Exec(`CREATE INDEX idx_ticket_assignee_resolved_at ON tickets ("jira_assignee", "resolved_at")`).Error)

	err = removeEngineerExpertiseAndWorkloadIndex(db)
	require.NoError(t, err)
	require.False(t, db.Migrator().HasColumn("engineer_engineers", "expertise_json"))
	require.False(t, db.Migrator().HasIndex(&ticketmod.Ticket{}, "idx_ticket_assignee_resolved_at"))
	require.NoError(t, removeEngineerExpertiseAndWorkloadIndex(db))
}

func TestEngineerDispatchStateMigrationPreservesLastAssignedAt(t *testing.T) {
	t.Parallel()

	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)
	require.NoError(t, db.Exec(`CREATE TABLE engineer_engineers (
		"email" TEXT PRIMARY KEY,
		"dispatch_enabled" BOOLEAN NOT NULL,
		"last_assigned_at" DATETIME,
		"created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
		"updated_at" TIMESTAMP
	)`).Error)
	dispatchedAt := time.Date(2026, 7, 10, 6, 0, 0, 0, time.UTC)
	require.NoError(t, db.Exec(
		`INSERT INTO engineer_engineers (email, dispatch_enabled, last_assigned_at) VALUES (?, ?, ?)`,
		"alice@example.com", true, dispatchedAt,
	).Error)
	require.NoError(t, db.Exec(`CREATE INDEX idx_engineer_engineers_last_assigned_at ON engineer_engineers (last_assigned_at)`).Error)

	require.NoError(t, migrateEngineerDispatchState(db))
	require.False(t, db.Migrator().HasColumn("engineer_engineers", "last_assigned_at"))
	require.True(t, db.Migrator().HasColumn("engineer_engineers", "last_dispatched_at"))
	require.True(t, db.Migrator().HasColumn("engineer_engineers", "last_dispatched_issue"))
	require.False(t, db.Migrator().HasIndex(&engmodel.Engineer{}, legacyLastAssignedAtIndex))
	require.True(t, db.Migrator().HasIndex(&engmodel.Engineer{}, "idx_engineer_engineers_last_dispatched_at"))

	var engineer engmodel.Engineer
	require.NoError(t, db.Where("email = ?", "alice@example.com").First(&engineer).Error)
	require.Equal(t, dispatchedAt, *engineer.DispatchState.LastDispatchedAt)
	require.Empty(t, engineer.DispatchState.LastDispatchedIssue)
}

func TestCollaborationResourceIdentityUniqueIndex(t *testing.T) {
	t.Parallel()

	resourceSchema, err := schema.Parse(&collabmod.Resource{}, &sync.Map{}, schema.NamingStrategy{})
	require.NoError(t, err)

	index := resourceSchema.LookIndex("idx_collaboration_resource_identity")
	require.NotNil(t, index)
	require.Equal(t, "UNIQUE", index.Class)
	require.Len(t, index.Fields, 3)
	require.Equal(t, "resource_type", index.Fields[0].DBName)
	require.Equal(t, "resource_provider", index.Fields[1].DBName)
	require.Equal(t, "resource_id", index.Fields[2].DBName)
}

func TestWorkspaceCollaborationResourceBindingUniqueIndex(t *testing.T) {
	t.Parallel()

	bindingSchema, err := schema.Parse(&workspacemod.CollaborationResourceBinding{}, &sync.Map{}, schema.NamingStrategy{})
	require.NoError(t, err)

	index := bindingSchema.LookIndex("idx_workspace_collaboration_resource_binding_unique")
	require.NotNil(t, index)
	require.Equal(t, "UNIQUE", index.Class)
	require.Len(t, index.Fields, 2)
	require.Equal(t, "workspace_id", index.Fields[0].DBName)
	require.Equal(t, "resource_id", index.Fields[1].DBName)
}

func TestWorkspaceIssueBindingUniqueIndex(t *testing.T) {
	t.Parallel()

	bindingSchema, err := schema.Parse(&workspacemod.IssueBinding{}, &sync.Map{}, schema.NamingStrategy{})
	require.NoError(t, err)

	index := bindingSchema.LookIndex("idx_workspace_issue_binding_unique")
	require.NotNil(t, index)
	require.Equal(t, "UNIQUE", index.Class)
	require.Len(t, index.Fields, 2)
	require.Equal(t, "workspace_id", index.Fields[0].DBName)
	require.Equal(t, "issue_key", index.Fields[1].DBName)
}

func TestWorkspaceIssueAndAlertBindingDoNotHaveWorkspaceIDIndex(t *testing.T) {
	t.Parallel()

	issueBindingSchema, err := schema.Parse(&workspacemod.IssueBinding{}, &sync.Map{}, schema.NamingStrategy{})
	require.NoError(t, err)
	require.Nil(t, issueBindingSchema.LookIndex("idx_workspace_id"))

	alertBindingSchema, err := schema.Parse(&workspacemod.AlertBinding{}, &sync.Map{}, schema.NamingStrategy{})
	require.NoError(t, err)
	require.Nil(t, alertBindingSchema.LookIndex("idx_workspace_id"))
}

func TestWorkspaceMemberPrimaryKey(t *testing.T) {
	t.Parallel()

	workspaceSchema, err := schema.Parse(&workspacemod.Workspace{}, &sync.Map{}, schema.NamingStrategy{})
	require.NoError(t, err)
	require.Nil(t, workspaceSchema.LookUpField("Owner"))

	memberSchema, err := schema.Parse(&workspacemod.WorkspaceMember{}, &sync.Map{}, schema.NamingStrategy{})
	require.NoError(t, err)

	require.Equal(t, "workspace_members", memberSchema.Table)
	require.Len(t, memberSchema.PrimaryFields, 4)
	require.Equal(t, "workspace_id", memberSchema.PrimaryFields[0].DBName)
	require.Equal(t, "role", memberSchema.PrimaryFields[1].DBName)
	require.Equal(t, "group", memberSchema.PrimaryFields[2].DBName)
	require.Equal(t, "email", memberSchema.PrimaryFields[3].DBName)
}

func TestWorkspaceTypeColumnIsRequiredWithoutDefault(t *testing.T) {
	t.Parallel()

	workspaceSchema, err := schema.Parse(&workspacemod.Workspace{}, &sync.Map{}, schema.NamingStrategy{})
	require.NoError(t, err)

	field := workspaceSchema.LookUpField("Type")
	require.NotNil(t, field)
	require.Equal(t, "type", field.DBName)
	require.True(t, field.NotNull)
	require.Empty(t, field.DefaultValue)
}

func TestWorkspaceSeverityColumnIsRequiredWithoutDefault(t *testing.T) {
	t.Parallel()

	workspaceSchema, err := schema.Parse(&workspacemod.Workspace{}, &sync.Map{}, schema.NamingStrategy{})
	require.NoError(t, err)

	field := workspaceSchema.LookUpField("Severity")
	require.NotNil(t, field)
	require.Equal(t, "severity", field.DBName)
	require.True(t, field.NotNull)
	require.Empty(t, field.DefaultValue)
}

func TestWorkspaceArchivedAtColumn(t *testing.T) {
	t.Parallel()

	workspaceSchema, err := schema.Parse(&workspacemod.Workspace{}, &sync.Map{}, schema.NamingStrategy{})
	require.NoError(t, err)

	field := workspaceSchema.LookUpField("ArchivedAt")
	require.NotNil(t, field)
	require.Equal(t, "archived_at", field.DBName)
	require.Nil(t, workspaceSchema.LookUpField("EndedAt"))
}

func TestWorkspacePrimaryChatGroupOMIDColumnRemoved(t *testing.T) {
	t.Parallel()

	workspaceSchema, err := schema.Parse(&workspacemod.Workspace{}, &sync.Map{}, schema.NamingStrategy{})
	require.NoError(t, err)

	require.Nil(t, workspaceSchema.LookUpField("PrimaryChatGroupOMID"))
}

func TestWorkspaceConfigurationColumn(t *testing.T) {
	t.Parallel()

	workspaceSchema, err := schema.Parse(&workspacemod.Workspace{}, &sync.Map{}, schema.NamingStrategy{})
	require.NoError(t, err)

	field := workspaceSchema.LookUpField("Configuration")
	require.NotNil(t, field)
	require.Equal(t, "configuration", field.DBName)
	require.Equal(t, "json", field.TagSettings["TYPE"])
	require.False(t, field.NotNull)
	require.Empty(t, field.DefaultValue)
}

func TestWorkspaceLastSendOverviewMessageAtColumn(t *testing.T) {
	t.Parallel()

	workspaceSchema, err := schema.Parse(&workspacemod.Workspace{}, &sync.Map{}, schema.NamingStrategy{})
	require.NoError(t, err)

	field := workspaceSchema.LookUpField("LastSendOverviewMessageAt")
	require.NotNil(t, field)
	require.Equal(t, "last_send_overview_message_at", field.DBName)
	require.Equal(t, "TIMESTAMP", field.TagSettings["TYPE"])
	require.False(t, field.NotNull)
}

func TestWorkspaceLastTimelineSyncAtMigration(t *testing.T) {
	t.Parallel()

	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)
	require.NoError(t, db.AutoMigrate(&workspacemod.Workspace{}))
	require.NoError(t, db.Migrator().DropColumn(&workspacemod.Workspace{}, "LastTimelineSyncAt"))

	updatedAt := time.Date(2026, 8, 17, 8, 0, 0, 0, time.UTC)
	workspace := &workspacemod.Workspace{
		Updatable:   modbase.Updatable{Base: modbase.Base{ID: 1001}, UpdatedAt: &updatedAt},
		Name:        "workspace-1",
		Description: "timeline migration",
		Severity:    "P1",
		Type:        "war_room",
		Status:      "firing",
		StartedAt:   updatedAt,
	}
	require.NoError(t, db.Omit("LastTimelineSyncAt").Create(workspace).Error)

	before := time.Now().Add(-time.Second)
	require.NoError(t, migrateWorkspaceLastTimelineSyncAt(db))
	after := time.Now().Add(time.Second)

	var got workspacemod.Workspace
	require.NoError(t, db.First(&got, 1001).Error)
	require.NotNil(t, got.LastTimelineSyncAt)
	require.WithinRange(t, *got.LastTimelineSyncAt, before, after)
	require.NotNil(t, got.UpdatedAt)
	require.Equal(t, updatedAt.Unix(), got.UpdatedAt.Unix())
}

func TestWorkspaceLastTimelineSyncAtColumn(t *testing.T) {
	t.Parallel()

	workspaceSchema, err := schema.Parse(&workspacemod.Workspace{}, &sync.Map{}, schema.NamingStrategy{})
	require.NoError(t, err)

	field := workspaceSchema.LookUpField("LastTimelineSyncAt")
	require.NotNil(t, field)
	require.Equal(t, "last_timeline_sync_at", field.DBName)
	require.Equal(t, "TIMESTAMP", field.TagSettings["TYPE"])
	require.False(t, field.NotNull)
}

func TestMigratorIncludesCollaborationResourceIdentityMigration(t *testing.T) {
	t.Parallel()

	require.PanicsWithValue(t, "migration version is already added: 202605231100", func() {
		addRecord("202605231100", func(db *gorm.DB) error {
			return nil
		})
	})
}

func TestMigratorIncludesWorkspaceMemberMigration(t *testing.T) {
	t.Parallel()

	require.PanicsWithValue(t, "migration version is already added: 202605281100", func() {
		addRecord("202605281100", func(db *gorm.DB) error {
			return nil
		})
	})
}

func TestMigratorIncludesWorkspaceOwnerToMemberMigration(t *testing.T) {
	t.Parallel()

	require.PanicsWithValue(t, "migration version is already added: 202605281200", func() {
		addRecord("202605281200", func(db *gorm.DB) error {
			return nil
		})
	})
}

func TestMigratorIncludesWorkspaceIssueBindingUniqueIndexMigration(t *testing.T) {
	t.Parallel()

	require.PanicsWithValue(t, "migration version is already added: 202605291720", func() {
		addRecord("202605291720", func(db *gorm.DB) error {
			return nil
		})
	})
}

func TestMigratorIncludesWorkspaceTypeMigration(t *testing.T) {
	t.Parallel()

	require.PanicsWithValue(t, "migration version is already added: 202606011100", func() {
		addRecord("202606011100", func(db *gorm.DB) error {
			return nil
		})
	})
}

func TestMigratorIncludesWorkspaceFiringStatusMigration(t *testing.T) {
	t.Parallel()

	require.PanicsWithValue(t, "migration version is already added: 202606011200", func() {
		addRecord("202606011200", func(db *gorm.DB) error {
			return nil
		})
	})
}

func TestMigratorIncludesWorkspaceRecoveredAtMigration(t *testing.T) {
	t.Parallel()

	require.PanicsWithValue(t, "migration version is already added: 202606011300", func() {
		addRecord("202606011300", func(db *gorm.DB) error {
			return nil
		})
	})
}

func TestMigratorIncludesWorkspaceArchivedAtMigration(t *testing.T) {
	t.Parallel()

	require.PanicsWithValue(t, "migration version is already added: 202606011400", func() {
		addRecord("202606011400", func(db *gorm.DB) error {
			return nil
		})
	})
}

func TestMigratorIncludesWorkspaceSeverityMigration(t *testing.T) {
	t.Parallel()

	require.PanicsWithValue(t, "migration version is already added: 202606021100", func() {
		addRecord("202606021100", func(db *gorm.DB) error {
			return nil
		})
	})
}

func TestMigratorIncludesWorkspacePrimaryChatGroupOMIDMigration(t *testing.T) {
	t.Parallel()

	require.PanicsWithValue(t, "migration version is already added: 202606021200", func() {
		addRecord("202606021200", func(db *gorm.DB) error {
			return nil
		})
	})
}

func TestMigratorIncludesWorkspaceConfigurationMigration(t *testing.T) {
	t.Parallel()

	require.PanicsWithValue(t, "migration version is already added: 202606081100", func() {
		addRecord("202606081100", func(db *gorm.DB) error {
			return nil
		})
	})
}

func TestMigratorIncludesWorkspacePrimaryChatGroupOMIDDropMigration(t *testing.T) {
	t.Parallel()

	require.PanicsWithValue(t, "migration version is already added: 202606081200", func() {
		addRecord("202606081200", func(db *gorm.DB) error {
			return nil
		})
	})
}

func TestMigratorIncludesWorkspaceLastSendOverviewMessageAtMigration(t *testing.T) {
	t.Parallel()

	require.PanicsWithValue(t, "migration version is already added: 202606081300", func() {
		addRecord("202606081300", func(db *gorm.DB) error {
			return nil
		})
	})
}
