package automation

import (
	"context"
	"fmt"
	"strings"

	"gorm.io/gorm"
)

const (
	// automationRunSearchCandidateLimit caps interactive text search work to a
	// recent run window until automation runs have a dedicated search index.
	automationRunSearchCandidateLimit = 1000
)

var automationTextSearchLikeReplacer = strings.NewReplacer("!", "!!", "%", "!%", "_", "!_")

type ListRunsResult struct {
	Runs    []AutomationRun
	HasMore bool
}

func (s *Store) ListRuns(ctx context.Context, workspaceID string, status string, repositoryFullName string, query string, page int, perPage int) (ListRunsResult, error) {
	if err := s.ensureDB(); err != nil {
		return ListRunsResult{}, err
	}
	offset := (page - 1) * perPage
	workspaceID = strings.TrimSpace(workspaceID)
	status = strings.TrimSpace(status)
	repositoryFullName = strings.TrimSpace(repositoryFullName)
	query = strings.TrimSpace(query)

	base := func(db *gorm.DB) *gorm.DB {
		db = db.Where("automation_runs.workspace_id = ?", workspaceID)
		if status != "" {
			db = db.Where("automation_runs.status = ?", status)
		}
		if repositoryFullName != "" {
			db = db.Where("automation_runs.repository_full_name = ?", repositoryFullName)
		}
		return db
	}

	q := base(s.db.WithContext(ctx).Model(&AutomationRun{}))
	if query != "" {
		candidates := base(s.db.WithContext(ctx).Model(&AutomationRun{}).Select("id")).
			Order("updated_at DESC").
			Order("id DESC").
			Limit(automationRunSearchCandidateLimit)
		like := automationTextSearchLikePattern(query)
		q = q.Joins("JOIN (?) AS run_search_candidates ON run_search_candidates.id = automation_runs.id", candidates)
		q = q.Where("LOWER(automation_runs.id) LIKE ? ESCAPE '!' OR LOWER(automation_runs.name) LIKE ? ESCAPE '!' OR LOWER(automation_runs.run_name) LIKE ? ESCAPE '!' OR LOWER(automation_runs.summary) LIKE ? ESCAPE '!' OR LOWER(automation_runs.repository_full_name) LIKE ? ESCAPE '!' OR LOWER(automation_runs.definition_path) LIKE ? ESCAPE '!' OR LOWER(automation_runs.definition_id) LIKE ? ESCAPE '!' OR LOWER(automation_runs.current_job_id) LIKE ? ESCAPE '!'", like, like, like, like, like, like, like, like)
	}
	// Fetch one extra row to detect if there are more pages.
	var runs []AutomationRun
	if err := q.Order("automation_runs.updated_at DESC").Order("automation_runs.id DESC").Limit(perPage + 1).Offset(offset).Find(&runs).Error; err != nil {
		return ListRunsResult{}, fmt.Errorf("list automation runs: %w", err)
	}
	hasMore := len(runs) > perPage
	if hasMore {
		runs = runs[:perPage]
	}
	return ListRunsResult{Runs: runs, HasMore: hasMore}, nil
}

func automationTextSearchLikePattern(query string) string {
	return "%" + automationTextSearchLikeReplacer.Replace(strings.ToLower(strings.TrimSpace(query))) + "%"
}

func (s *Store) CreateRun(ctx context.Context, run AutomationRun) (AutomationRun, error) {
	if err := s.ensureDB(); err != nil {
		return AutomationRun{}, err
	}
	if err := s.db.WithContext(ctx).Create(&run).Error; err != nil {
		return AutomationRun{}, fmt.Errorf("create automation run: %w", err)
	}
	return run, nil
}

// GetRunByIdempotencyKey loads the run previously created for a workspace key.
func (s *Store) GetRunByIdempotencyKey(ctx context.Context, workspaceID string, idempotencyKey string) (AutomationRun, error) {
	if err := s.ensureDB(); err != nil {
		return AutomationRun{}, err
	}
	workspaceID = strings.TrimSpace(workspaceID)
	idempotencyKey = strings.TrimSpace(idempotencyKey)
	if workspaceID == "" || idempotencyKey == "" {
		return AutomationRun{}, gorm.ErrRecordNotFound
	}
	var run AutomationRun
	err := s.db.WithContext(ctx).Where("workspace_id = ? AND idempotency_key = ?", workspaceID, idempotencyKey).First(&run).Error
	if err != nil {
		return AutomationRun{}, err
	}
	return run, nil
}

func (s *Store) GetRun(ctx context.Context, workspaceID string, runID string) (AutomationRun, error) {
	if err := s.ensureDB(); err != nil {
		return AutomationRun{}, err
	}
	var run AutomationRun
	err := s.db.WithContext(ctx).Where("workspace_id = ? AND id = ?", strings.TrimSpace(workspaceID), strings.TrimSpace(runID)).First(&run).Error
	if err != nil {
		return AutomationRun{}, err
	}
	return run, nil
}

// GetRunEnvContext loads only the run fields needed to resolve job env.
func (s *Store) GetRunEnvContext(ctx context.Context, workspaceID string, runID string) (AutomationRun, error) {
	if err := s.ensureDB(); err != nil {
		return AutomationRun{}, err
	}
	var run AutomationRun
	err := s.db.WithContext(ctx).
		Select("id", "workspace_id", "repository_owner", "repository_name", "input_json").
		Where("workspace_id = ? AND id = ?", strings.TrimSpace(workspaceID), strings.TrimSpace(runID)).
		First(&run).Error
	if err != nil {
		return AutomationRun{}, err
	}
	return run, nil
}

func (s *Store) UpdateRun(ctx context.Context, run AutomationRun) (AutomationRun, error) {
	if err := s.ensureDB(); err != nil {
		return AutomationRun{}, err
	}
	err := s.db.WithContext(ctx).Model(&AutomationRun{}).Where("workspace_id = ? AND id = ?", run.WorkspaceID, run.ID).Updates(map[string]any{
		"source_kind":             run.SourceKind,
		"content_id":              run.ContentID,
		"content_version_id":      run.ContentVersionID,
		"repository_owner":        run.RepositoryOwner,
		"repository_name":         run.RepositoryName,
		"repository_full_name":    run.RepositoryFullName,
		"definition_path":         run.DefinitionPath,
		"git_ref":                 run.GitRef,
		"commit_sha":              run.CommitSHA,
		"definition_id":           run.DefinitionID,
		"input_json":              run.InputJSON,
		"output_json":             run.OutputJSON,
		"error_json":              run.ErrorJSON,
		"name":                    run.Name,
		"run_name":                run.RunName,
		"status":                  run.Status,
		"trigger_kind":            run.TriggerKind,
		"trigger_ref":             run.TriggerRef,
		"target_kind":             run.TargetKind,
		"target_ref":              run.TargetRef,
		"owner_principal_id":      run.OwnerPrincipalID,
		"created_by_principal_id": run.CreatedByPrincipalID,
		"current_job_id":          run.CurrentJobID,
		"summary":                 run.Summary,
		"metadata":                run.Metadata,
		"started_at":              run.StartedAt,
		"completed_at":            run.CompletedAt,
		"updated_at":              run.UpdatedAt,
	}).Error
	if err != nil {
		return AutomationRun{}, fmt.Errorf("update automation run: %w", err)
	}
	return s.GetRun(ctx, run.WorkspaceID, run.ID)
}

func automationRunIdempotencyKey(run AutomationRun) string {
	if run.IdempotencyKey == nil {
		return ""
	}
	return strings.TrimSpace(*run.IdempotencyKey)
}
