// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package v1_27

import (
	"context"
	"time"

	"gitea.dev/models/db"
	"gitea.dev/models/migrations/base"
	"gitea.dev/modules/timeutil"

	"xorm.io/xorm"
)

type actionRunAttempt struct {
	ID                int64
	RepoID            int64 `xorm:"index(repo_concurrency_status)"`
	RunID             int64 `xorm:"UNIQUE(run_attempt)"`
	Attempt           int64 `xorm:"UNIQUE(run_attempt)"`
	TriggerUserID     int64
	ConcurrencyGroup  string `xorm:"index(repo_concurrency_status) NOT NULL DEFAULT ''"`
	ConcurrencyCancel bool   `xorm:"NOT NULL DEFAULT FALSE"`
	Status            int    `xorm:"index(repo_concurrency_status)"`
	Started           timeutil.TimeStamp
	Stopped           timeutil.TimeStamp
	Created           timeutil.TimeStamp `xorm:"created"`
	Updated           timeutil.TimeStamp `xorm:"updated"`
}

func (actionRunAttempt) TableName() string {
	return "action_run_attempt"
}

type actionArtifact struct {
	ID                 int64 `xorm:"pk autoincr"`
	RunID              int64 `xorm:"index unique(runid_attempt_name_path)"`
	RunAttemptID       int64 `xorm:"index unique(runid_attempt_name_path) NOT NULL DEFAULT 0"`
	RunnerID           int64
	RepoID             int64 `xorm:"index"`
	OwnerID            int64
	CommitSHA          string
	StoragePath        string
	FileSize           int64
	FileCompressedSize int64
	ContentEncoding    string             `xorm:"content_encoding"`
	ArtifactPath       string             `xorm:"index unique(runid_attempt_name_path)"`
	ArtifactName       string             `xorm:"index unique(runid_attempt_name_path)"`
	Status             int                `xorm:"index"`
	CreatedUnix        timeutil.TimeStamp `xorm:"created"`
	UpdatedUnix        timeutil.TimeStamp `xorm:"updated index"`
	ExpiredUnix        timeutil.TimeStamp `xorm:"index"`
}

func (actionArtifact) TableName() string {
	return "action_artifact"
}

// actionRun mirrors the post-migration action_run schema.
type actionRun struct {
	ID                int64
	Title             string
	RepoID            int64  `xorm:"unique(repo_index)"`
	OwnerID           int64  `xorm:"index"`
	WorkflowID        string `xorm:"index"`
	Index             int64  `xorm:"index unique(repo_index)"`
	TriggerUserID     int64  `xorm:"index"`
	ScheduleID        int64
	Ref               string `xorm:"index"`
	CommitSHA         string
	IsForkPullRequest bool
	NeedApproval      bool
	ApprovedBy        int64 `xorm:"index"`
	Event             string
	EventPayload      string `xorm:"LONGTEXT"`
	TriggerEvent      string
	Status            int `xorm:"index"`
	Version           int `xorm:"version default 0"`
	RawConcurrency    string
	Started           timeutil.TimeStamp
	Stopped           timeutil.TimeStamp
	PreviousDuration  time.Duration
	LatestAttemptID   int64              `xorm:"index NOT NULL DEFAULT 0"`
	Created           timeutil.TimeStamp `xorm:"created"`
	Updated           timeutil.TimeStamp `xorm:"updated"`
}

func (actionRun) TableName() string {
	return "action_run"
}

// AddActionRunAttemptModel adds the ActionRunAttempt table and the supporting ActionRun/ActionRunJob fields.
func AddActionRunAttemptModel(x db.EngineMigration) error {
	// add "action_run_attempt"
	if _, err := x.SyncWithOptions(xorm.SyncOptions{
		IgnoreDropIndices: true,
	}, new(actionRunAttempt)); err != nil {
		return err
	}

	// update "action_run_job"
	type ActionRunJob struct {
		RunAttemptID int64 `xorm:"index NOT NULL DEFAULT 0"`
		AttemptJobID int64 `xorm:"index NOT NULL DEFAULT 0"`
		SourceTaskID int64 `xorm:"NOT NULL DEFAULT 0"`
	}
	if _, err := x.SyncWithOptions(xorm.SyncOptions{
		IgnoreDropIndices: true,
	}, new(ActionRunJob)); err != nil {
		return err
	}

	// update "action_artifact": let xorm sync add the new 4-column unique index (runid_attempt_name_path) and drop the old 3-column unique (runid_name_path)
	if err := x.Sync(new(actionArtifact)); err != nil {
		return err
	}

	// update "action_run"
	//
	// This migration intentionally removes the legacy run-level concurrency columns after
	// introducing attempt-level concurrency on action_run_attempt.
	//
	// Existing values from action_run.concurrency_group / action_run.concurrency_cancel are
	// not backfilled into action_run_attempt:
	//   - the old fields are only meaningful while a run is actively participating in
	//     concurrency scheduling
	//   - for completed legacy runs, keeping or backfilling those values has no practical
	//     effect on future scheduling behavior
	//   - scanning and backfilling old runs would add significant migration cost for little value
	//
	// This means the schema change is destructive for those two legacy columns by design.
	//
	// Let xorm sync add the latest_attempt_id column and drop the now-orphan (repo_id, concurrency_group) index.
	if err := x.Sync(new(actionRun)); err != nil {
		return err
	}
	concurrencyColumns := make([]string, 0, 2)
	for _, col := range []string{"concurrency_group", "concurrency_cancel"} {
		exist, err := x.Dialect().IsColumnExist(x.DB(), context.Background(), "action_run", col)
		if err != nil {
			return err
		}
		if exist {
			concurrencyColumns = append(concurrencyColumns, col)
		}
	}
	if len(concurrencyColumns) == 0 {
		return nil
	}
	sess := x.NewSession()
	defer sess.Close()
	if err := base.DropTableColumns(sess, "action_run", concurrencyColumns...); err != nil {
		return err
	}
	// DropTableColumns rebuilds the table on SQLite, which drops all existing indexes.
	// Re-sync to restore the indexes defined on actionRun.
	return x.Sync(new(actionRun))
}
