package automation

import (
	"context"
	"errors"
	"regexp"
	"sort"
	"strings"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/automation/dsl"
)

const (
	automationPreflightScenarioManualStart          = "manual_start"
	automationPreflightScenarioScheduleTarget       = "schedule_target_validate"
	automationPreflightScenarioEventPushValidate    = "event_push_validate"
	automationPreflightStatusOK                     = "ok"
	automationPreflightStatusError                  = "error"
	automationPreflightStatusSkipped                = "skipped"
	automationPreflightSeverityError                = "error"
	automationPreflightSeverityWarning              = "warning"
	automationPreflightStartModeSavedSourceOnly     = "saved_source_only"
	automationPreflightStartModeDraftValidationOnly = "draft_validation_only"
)

var automationCommitSHAPattern = regexp.MustCompile(`^[0-9a-fA-F]{40}$`)

type AutomationRepositoryAutomationPreflightMutation struct {
	RepositoryOwner     string
	RepositoryName      string
	DefinitionPath      string
	GitRef              string
	Scenario            string
	ContentOverride     string
	InputJSON           string
	Branch              string
	AuthorizationHeader string
}

type AutomationPreflightReport struct {
	Sections  AutomationPreflightSections `json:"sections"`
	Issues    []AutomationPreflightIssue  `json:"issues,omitempty"`
	Runnable  bool                        `json:"runnable"`
	CheckedAt string                      `json:"checked_at"`
	Overall   AutomationPreflightOverall  `json:"overall"`
	StartMode string                      `json:"start_mode,omitempty"`
}

type AutomationPreflightSections struct {
	SourceResolution        AutomationPreflightSourceResolutionSection `json:"source_resolution"`
	AutomationSyntax        AutomationPreflightAutomationSyntaxSection `json:"automation_syntax"`
	TriggerSupport          AutomationPreflightTriggerSupportSection   `json:"trigger_support"`
	RunnerReadiness         AutomationPreflightRunnerReadinessSection  `json:"runner_readiness"`
	RepoCredentialReadiness AutomationPreflightRepoCredentialSection   `json:"repo_credential_readiness"`
	Permissions             AutomationPreflightPermissionsSection      `json:"permissions"`
}

type AutomationPreflightSourceResolutionSection struct {
	Status                 string `json:"status"`
	RepositoryResolved     bool   `json:"repository_resolved"`
	RefResolved            bool   `json:"ref_resolved"`
	DefinitionPathResolved bool   `json:"definition_path_resolved"`
	CommitSHA              string `json:"commit_sha,omitempty"`
	ContentSource          string `json:"content_source,omitempty"`
}

type AutomationPreflightAutomationSyntaxSection struct {
	Status              string   `json:"status"`
	Valid               bool     `json:"valid"`
	UnsupportedFeatures []string `json:"unsupported_features,omitempty"`
}

type AutomationPreflightTriggerSupportSection struct {
	Status      string                             `json:"status"`
	ManualStart AutomationPreflightTriggerScenario `json:"manual_start"`
	Schedule    AutomationPreflightTriggerScenario `json:"schedule_start"`
	EventPush   AutomationPreflightPushScenario    `json:"event_push"`
}

type AutomationPreflightTriggerScenario struct {
	Supported bool `json:"supported"`
}

type AutomationPreflightPushScenario struct {
	Supported          bool `json:"supported"`
	BranchMatch        bool `json:"branch_match"`
	PathFiltersPresent bool `json:"path_filters_present"`
}

type AutomationPreflightPermissionsSection struct {
	Status       string `json:"status"`
	StartAllowed bool   `json:"start_allowed"`
	RequiredRole string `json:"required_role,omitempty"`
}

type AutomationPreflightGenericSection struct {
	Status string `json:"status"`
}

type AutomationPreflightRunnerReadinessSection struct {
	Status string `json:"status"`
	AutomationRunnerReadiness
}

type AutomationPreflightRepoCredentialSection struct {
	Status             string `json:"status"`
	CheckoutSupported  bool   `json:"checkout_supported"`
	RepoTokenAvailable bool   `json:"repo_token_available"`
	RepoTokenScope     string `json:"repo_token_scope,omitempty"`
}

type AutomationPreflightIssue struct {
	Code       string `json:"code"`
	Severity   string `json:"severity"`
	Message    string `json:"message"`
	UserAction string `json:"user_action,omitempty"`
	Section    string `json:"section"`
	Field      string `json:"field,omitempty"`
	Details    any    `json:"details,omitempty"`
}

type AutomationPreflightOverall struct {
	Runnable            bool   `json:"runnable"`
	HighestSeverity     string `json:"highest_severity,omitempty"`
	PrimaryIssueCode    string `json:"primary_issue_code,omitempty"`
	PrimaryIssueMessage string `json:"primary_issue_message,omitempty"`
}

type evaluatedAutomation struct {
	snapshot AutomationSourceSnapshot
	spec     dsl.DefinitionSpec
	report   AutomationPreflightReport
}

func (s *AutomationsService) PreflightRepositoryAutomation(ctx context.Context, workspaceID string, subject string, input AutomationRepositoryAutomationPreflightMutation) (AutomationPreflightReport, error) {
	if err := s.validateScope(workspaceID, subject); err != nil {
		return AutomationPreflightReport{}, err
	}
	input = normalizeAutomationPreflightInput(input)
	if err := validateAutomationPreflightInput(input); err != nil {
		return AutomationPreflightReport{}, err
	}
	if err := s.automationAuthorizer().CanReadAutomationRepo(ctx, s.automationAuthorizationRequest(ctx, workspaceID, subject, AutomationAuthorizationRequest{RepositoryOwner: input.RepositoryOwner, RepositoryName: input.RepositoryName})); err != nil {
		return AutomationPreflightReport{}, err
	}
	evaluated, err := s.evaluateRepositoryAutomation(ctx, workspaceID, subject, input)
	if err != nil {
		return AutomationPreflightReport{}, err
	}
	return evaluated.report, nil
}

func (s *AutomationsService) evaluateRepositoryAutomation(ctx context.Context, workspaceID string, subject string, input AutomationRepositoryAutomationPreflightMutation) (evaluatedAutomation, error) {
	report := AutomationPreflightReport{
		CheckedAt: s.now().UTC().Format(time.RFC3339),
		StartMode: automationPreflightStartModeSavedSourceOnly,
		Sections: AutomationPreflightSections{
			SourceResolution:        AutomationPreflightSourceResolutionSection{Status: automationPreflightStatusSkipped},
			AutomationSyntax:        AutomationPreflightAutomationSyntaxSection{Status: automationPreflightStatusSkipped},
			TriggerSupport:          AutomationPreflightTriggerSupportSection{Status: automationPreflightStatusSkipped},
			RunnerReadiness:         AutomationPreflightRunnerReadinessSection{Status: automationPreflightStatusSkipped},
			RepoCredentialReadiness: AutomationPreflightRepoCredentialSection{Status: automationPreflightStatusSkipped},
			Permissions: AutomationPreflightPermissionsSection{
				Status:       automationPreflightStatusOK,
				StartAllowed: true,
				RequiredRole: "automation:run:start",
			},
		},
	}
	if strings.TrimSpace(input.ContentOverride) != "" {
		report.StartMode = automationPreflightStartModeDraftValidationOnly
	}

	snapshot, sourceSection, sourceIssues, err := s.resolvePreflightSource(ctx, workspaceID, input, report.StartMode)
	if err != nil {
		return evaluatedAutomation{}, err
	}
	report.Sections.SourceResolution = sourceSection
	report.Issues = append(report.Issues, sourceIssues...)
	if sourceSection.Status != automationPreflightStatusOK {
		finalizeAutomationPreflightReport(&report)
		return evaluatedAutomation{snapshot: snapshot, report: report}, nil
	}
	if err := s.bindAutomationRepository(ctx, workspaceID, subject, snapshot.RepositoryOwner, snapshot.RepositoryName, snapshot.GitRef); err != nil {
		return evaluatedAutomation{}, err
	}

	spec, validationReport := s.decodeAndValidateAutomationContent(snapshot.Content, input.DefinitionPath)
	syntaxSection, syntaxIssues := automationSyntaxSectionFromValidation(validationReport)
	report.Sections.AutomationSyntax = syntaxSection
	report.Issues = append(report.Issues, syntaxIssues...)
	if syntaxSection.Status != automationPreflightStatusOK {
		finalizeAutomationPreflightReport(&report)
		return evaluatedAutomation{snapshot: snapshot, spec: spec, report: report}, nil
	}

	triggerSection, triggerIssues := automationTriggerSupportSection(spec, input)
	report.Sections.TriggerSupport = triggerSection
	report.Issues = append(report.Issues, triggerIssues...)

	runnerReadiness, err := s.automationRunnerReadinessForSpec(ctx, strings.TrimSpace(workspaceID), spec)
	if err != nil {
		return evaluatedAutomation{}, err
	}
	runnerSection, runnerIssues := automationPreflightRunnerReadinessSectionFromReadiness(runnerReadiness)
	report.Sections.RunnerReadiness = runnerSection
	report.Issues = append(report.Issues, runnerIssues...)

	credentialSection, credentialIssues, err := s.automationPreflightRepoCredentialSection(ctx, workspaceID, snapshot, spec)
	if err != nil {
		return evaluatedAutomation{}, err
	}
	report.Sections.RepoCredentialReadiness = credentialSection
	report.Issues = append(report.Issues, credentialIssues...)

	permissionIssues, permissionStatus := s.automationPreflightPermissionIssues(ctx, workspaceID, subject, input)
	report.Sections.Permissions.Status = permissionStatus
	report.Sections.Permissions.StartAllowed = len(permissionIssues) == 0
	report.Issues = append(report.Issues, permissionIssues...)

	finalizeAutomationPreflightReport(&report)
	return evaluatedAutomation{snapshot: snapshot, spec: spec, report: report}, nil
}

func (s *AutomationsService) resolvePreflightSource(ctx context.Context, workspaceID string, input AutomationRepositoryAutomationPreflightMutation, startMode string) (AutomationSourceSnapshot, AutomationPreflightSourceResolutionSection, []AutomationPreflightIssue, error) {
	section := AutomationPreflightSourceResolutionSection{
		Status:             automationPreflightStatusOK,
		RepositoryResolved: true,
		RefResolved:        true,
		ContentSource:      "repository",
	}
	request := AutomationSourceRequest{
		WorkspaceID:         workspaceID,
		RepositoryOwner:     input.RepositoryOwner,
		RepositoryName:      input.RepositoryName,
		DefinitionPath:      input.DefinitionPath,
		GitRef:              input.GitRef,
		AuthorizationHeader: input.AuthorizationHeader,
	}
	if s.sourceResolver == nil {
		return AutomationSourceSnapshot{}, section, nil, invalidArgument("Automation source resolver is not configured")
	}
	snapshot, err := s.sourceResolver.ResolveAutomationSource(ctx, request)
	if err != nil {
		if strings.TrimSpace(input.ContentOverride) != "" && errors.Is(err, ErrNotFound) {
			section.DefinitionPathResolved = false
			section.ContentSource = "override"
			synthetic := AutomationSourceSnapshot{
				RepositoryOwner: input.RepositoryOwner,
				RepositoryName:  input.RepositoryName,
				DefinitionPath:  normalizeAutomationSourcePath(input.DefinitionPath),
				GitRef:          input.GitRef,
				CommitSHA:       automationResolvedCommitSHA(input.GitRef),
				Content:         input.ContentOverride,
			}
			section.CommitSHA = synthetic.CommitSHA
			return synthetic, section, nil, nil
		}
		if errors.Is(err, ErrNotFound) {
			section.Status = automationPreflightStatusError
			section.DefinitionPathResolved = false
			issue := AutomationPreflightIssue{
				Code:       automationCodeSourceDefinitionPathNotFound,
				Severity:   automationPreflightSeverityError,
				Message:    err.Error(),
				UserAction: "Confirm the automation path exists at the requested ref.",
				Section:    "source_resolution",
				Field:      "definition_path",
			}
			return AutomationSourceSnapshot{}, section, []AutomationPreflightIssue{issue}, nil
		}
		return AutomationSourceSnapshot{}, section, nil, err
	}
	section.DefinitionPathResolved = true
	section.CommitSHA = snapshot.CommitSHA
	if strings.TrimSpace(input.ContentOverride) != "" {
		snapshot.Content = input.ContentOverride
		section.ContentSource = "override"
	}
	return snapshot, section, nil, nil
}

func (s *AutomationsService) automationPreflightPermissionIssues(ctx context.Context, workspaceID string, subject string, input AutomationRepositoryAutomationPreflightMutation) ([]AutomationPreflightIssue, string) {
	if input.Scenario != automationPreflightScenarioManualStart {
		return nil, automationPreflightStatusOK
	}
	err := s.automationAuthorizer().CanStartAutomationRun(ctx, s.automationAuthorizationRequest(ctx, workspaceID, subject, AutomationAuthorizationRequest{RepositoryOwner: input.RepositoryOwner, RepositoryName: input.RepositoryName}))
	if err == nil {
		return nil, automationPreflightStatusOK
	}
	if errors.Is(err, ErrForbidden) {
		return []AutomationPreflightIssue{{
			Code:       automationCodePermissionDenied,
			Severity:   automationPreflightSeverityError,
			Message:    err.Error(),
			UserAction: "Use an actor with automation start permission for this repository.",
			Section:    "permissions",
			Field:      "scenario",
		}}, automationPreflightStatusError
	}
	return []AutomationPreflightIssue{{
		Code:       automationCodePermissionDenied,
		Severity:   automationPreflightSeverityError,
		Message:    err.Error(),
		UserAction: "Resolve automation start authorization and retry.",
		Section:    "permissions",
		Field:      "scenario",
	}}, automationPreflightStatusError
}

func automationTriggerSupportSection(spec dsl.DefinitionSpec, input AutomationRepositoryAutomationPreflightMutation) (AutomationPreflightTriggerSupportSection, []AutomationPreflightIssue) {
	manualSupported := dsl.HasManualTrigger(spec.On)
	pushSupported := dsl.HasPushTrigger(spec.On)
	branchMatch := false
	if pushSupported && strings.TrimSpace(input.Branch) != "" {
		branchMatch = dsl.PushBranchMatches(spec.On, input.Branch)
	}
	section := AutomationPreflightTriggerSupportSection{
		Status:      automationPreflightStatusOK,
		ManualStart: AutomationPreflightTriggerScenario{Supported: manualSupported},
		Schedule:    AutomationPreflightTriggerScenario{Supported: true},
		EventPush: AutomationPreflightPushScenario{
			Supported:          pushSupported,
			BranchMatch:        branchMatch,
			PathFiltersPresent: dsl.PushPathFiltersPresent(spec.On),
		},
	}
	var issues []AutomationPreflightIssue
	switch input.Scenario {
	case automationPreflightScenarioManualStart:
		if !manualSupported {
			issues = append(issues, AutomationPreflightIssue{
				Code:       automationCodeMissingManual,
				Severity:   automationPreflightSeverityError,
				Message:    "Automation must support manual to be started manually",
				UserAction: "Add manual to the automation or choose a supported automatic trigger.",
				Section:    "trigger_support",
				Field:      "on.manual",
			})
		}
	case automationPreflightScenarioEventPushValidate:
		if !pushSupported {
			issues = append(issues, AutomationPreflightIssue{
				Code:       "trigger_push_not_supported",
				Severity:   automationPreflightSeverityError,
				Message:    "Automation does not declare an on.push trigger for push validation",
				UserAction: "Add an on.push trigger or evaluate a different automation entry path.",
				Section:    "trigger_support",
				Field:      "on.push",
			})
		} else if !branchMatch {
			issues = append(issues, AutomationPreflightIssue{
				Code:       "trigger_push_branch_not_matched",
				Severity:   automationPreflightSeverityError,
				Message:    "Automation push trigger does not match the checked branch",
				UserAction: "Check the push branch rules or evaluate the automation against a matching branch.",
				Section:    "trigger_support",
				Field:      "branch",
			})
		}
	}
	if len(issues) > 0 {
		section.Status = automationPreflightStatusError
	}
	return section, issues
}

func (s *AutomationsService) decodeAndValidateAutomationContent(content string, definitionPath string) (dsl.DefinitionSpec, dsl.ValidationReport) {
	if err := validateAutomationSourceBytes(content, s.automationLimits()); err != nil {
		return dsl.DefinitionSpec{}, dsl.ValidationReport{Valid: false, Errors: []dsl.ValidationError{{Code: "automation_source_limit_exceeded", Path: "content", Message: err.Error()}}}
	}
	spec, err := decodeAutomationSpec(content)
	if err != nil {
		return dsl.DefinitionSpec{}, dsl.ValidationReport{Valid: false, Errors: []dsl.ValidationError{{Code: automationCodeSyntaxInvalidYAML, Path: definitionPath, Message: "automation source must be valid AgentTeam Automation YAML or normalized JSON"}}}
	}
	report := dsl.ValidateDefinitionWithLimits(spec, s.automationLimits().validationLimits())
	if dsl.HasScheduleTrigger(spec.On) {
		report.Errors = append(report.Errors, dsl.ValidationError{Code: "on_schedule_unsupported", Path: "on.schedule", Message: "automation on.schedule is not supported"})
	}
	report.Valid = len(report.Errors) == 0
	return spec, report
}

func automationSyntaxSectionFromValidation(report dsl.ValidationReport) (AutomationPreflightAutomationSyntaxSection, []AutomationPreflightIssue) {
	section := AutomationPreflightAutomationSyntaxSection{Status: automationPreflightStatusOK, Valid: report.Valid}
	if report.Valid {
		return section, nil
	}
	section.Status = automationPreflightStatusError
	unsupported := preflightUnsupportedFeatures(report.Errors)
	if len(unsupported) > 0 {
		section.UnsupportedFeatures = unsupported
	}
	first := report.Errors[0]
	issue := AutomationPreflightIssue{
		Code:       automationCodeUnsupportedSyntax,
		Severity:   automationPreflightSeverityError,
		Message:    "Automation spec is invalid",
		UserAction: "Remove unsupported automation features or fix the automation structure, then retry.",
		Section:    "automation_syntax",
		Field:      strings.TrimSpace(first.Path),
		Details:    map[string]any{"validation_errors": report.Errors},
	}
	if first.Code == automationCodeSyntaxInvalidYAML {
		issue.Code = automationCodeSyntaxInvalidYAML
		issue.UserAction = "Fix the automation YAML or normalized JSON syntax and retry."
	} else if first.Code == "automation_source_limit_exceeded" {
		issue.Code = first.Code
		issue.Message = first.Message
		issue.UserAction = "Reduce the automation source size and retry."
	}
	if len(unsupported) > 0 {
		issue.Details = map[string]any{"unsupported_features": unsupported, "validation_errors": report.Errors}
	}
	return section, []AutomationPreflightIssue{issue}
}

func automationPreflightRunnerReadinessSectionFromReadiness(readiness AutomationRunnerReadiness) (AutomationPreflightRunnerReadinessSection, []AutomationPreflightIssue) {
	section := AutomationPreflightRunnerReadinessSection{
		Status:                    automationPreflightStatusOK,
		AutomationRunnerReadiness: readiness,
	}
	if readiness.Runnable {
		return section, nil
	}
	code := strings.TrimSpace(readiness.ReasonCode)
	if code == "" {
		code = "runner_no_matching_runner"
	}
	section.Status = automationPreflightStatusError
	issue := AutomationPreflightIssue{
		Code:       code,
		Severity:   automationPreflightSeverityError,
		Message:    automationRunnerReadinessIssueMessage(code),
		UserAction: automationRunnerReadinessIssueUserAction(code),
		Section:    "runner_readiness",
		Details: map[string]any{
			"required_labels":       readiness.RequiredLabels,
			"required_group":        readiness.RequiredGroup,
			"matching_runner_count": readiness.MatchingRunnerCount,
			"online_runner_count":   readiness.OnlineRunnerCount,
			"offline_runner_count":  readiness.OfflineRunnerCount,
			"idle_runner_count":     readiness.IdleRunnerCount,
			"busy_runner_count":     readiness.BusyRunnerCount,
			"queue_depth":           readiness.QueueDepth,
			"reason_code":           code,
			"pools":                 readiness.Pools,
		},
	}
	return section, []AutomationPreflightIssue{issue}
}

func automationRunnerReadinessIssueMessage(code string) string {
	switch strings.TrimSpace(code) {
	case "runner_label_mismatch":
		return "No registered runner matches the automation's required runner labels or runner group"
	case "runner_no_matching_runner":
		return "No registered runner is available for this automation's required runner pool"
	default:
		return "Automation runner readiness is not satisfied for the required runner pool"
	}
}

func automationRunnerReadinessIssueUserAction(code string) string {
	switch strings.TrimSpace(code) {
	case "runner_label_mismatch":
		return "Register a runner whose labels and runner group match the automation, or update runs-on to an available pool."
	case "runner_no_matching_runner":
		return "Register a runner that matches the automation's required labels or runner group, then retry."
	default:
		return "Resolve the automation runner pool mismatch, then retry."
	}
}

func (s *AutomationsService) automationPreflightRepoCredentialSection(ctx context.Context, workspaceID string, snapshot AutomationSourceSnapshot, spec dsl.DefinitionSpec) (AutomationPreflightRepoCredentialSection, []AutomationPreflightIssue, error) {
	section := AutomationPreflightRepoCredentialSection{
		Status:            automationPreflightStatusOK,
		CheckoutSupported: automationUsesSupportedCheckout(spec),
		RepoTokenScope:    automationRepositoryTokenScopeReadOnly,
	}
	if !section.CheckoutSupported {
		return section, nil, nil
	}
	if strings.TrimSpace(snapshot.ContentID) != "" && strings.TrimSpace(snapshot.ContentVersionID) != "" {
		section.RepoTokenAvailable = true
		section.RepoTokenScope = "content_version"
		return section, nil, nil
	}
	if s.repoCredentialChecker != nil {
		availability, err := s.repoCredentialChecker.CheckAutomationRepositoryCredential(ctx, AutomationRepositoryCredentialCheckRequest{
			WorkspaceID:     strings.TrimSpace(workspaceID),
			RepositoryOwner: snapshot.RepositoryOwner,
			RepositoryName:  snapshot.RepositoryName,
			GitRef:          snapshot.GitRef,
			CommitSHA:       snapshot.CommitSHA,
		})
		if err != nil {
			return AutomationPreflightRepoCredentialSection{}, nil, err
		}
		section.RepoTokenAvailable = availability.RepoTokenAvailable
		if scope := strings.TrimSpace(availability.RepoTokenScope); scope != "" {
			section.RepoTokenScope = scope
		}
	}
	if section.RepoTokenAvailable {
		return section, nil, nil
	}
	section.Status = automationPreflightStatusError
	issue := AutomationPreflightIssue{
		Code:       automationCodeCredentialSecretMissing,
		Severity:   automationPreflightSeverityError,
		Message:    "Automation requires the platform repository checkout credential, but it is unavailable",
		UserAction: "Configure the repository runtime checkout credential path and retry.",
		Section:    "repo_credential_readiness",
		Field:      "jobs.*.steps[].run",
		Details: map[string]any{
			"checkout_supported":   section.CheckoutSupported,
			"repo_token_available": section.RepoTokenAvailable,
			"repo_token_scope":     section.RepoTokenScope,
		},
	}
	return section, []AutomationPreflightIssue{issue}, nil
}

func automationUsesSupportedCheckout(spec dsl.DefinitionSpec) bool {
	for _, job := range spec.Jobs {
		for _, step := range job.Steps {
			if step.Run == "agentteam-checkout" {
				return true
			}
		}
	}
	return false
}

func preflightUnsupportedFeatures(errors []dsl.ValidationError) []string {
	set := map[string]struct{}{}
	for _, err := range errors {
		switch strings.TrimSpace(err.Code) {
		case "step_uses_unsupported":
			set["external_uses"] = struct{}{}
		case "expression_unsupported":
			set["expressions"] = struct{}{}
		case "job_strategy_unsupported":
			set["matrix_or_strategy"] = struct{}{}
		case "job_container_unsupported":
			set["container"] = struct{}{}
		case "job_services_unsupported":
			set["services"] = struct{}{}
		case "job_if_unsupported", "step_if_unsupported":
			set["job_or_step_if"] = struct{}{}
		case "automation_env_unsupported", "automation_defaults_unsupported", "job_env_unsupported", "job_defaults_unsupported", "step_env_unsupported":
			set["env_or_defaults"] = struct{}{}
		case "on_schedule_unsupported":
			set["on_schedule"] = struct{}{}
		}
	}
	if len(set) == 0 {
		return nil
	}
	out := make([]string, 0, len(set))
	for value := range set {
		out = append(out, value)
	}
	sort.Strings(out)
	return out
}

func finalizeAutomationPreflightReport(report *AutomationPreflightReport) {
	if report == nil {
		return
	}
	report.Runnable = true
	report.Overall.Runnable = true
	severityRank := map[string]int{"": 0, automationPreflightSeverityWarning: 1, automationPreflightSeverityError: 2}
	priority := map[string]int{"source_resolution": 1, "automation_syntax": 2, "trigger_support": 3, "runner_readiness": 4, "repo_credential_readiness": 5, "permissions": 6}
	bestSeverity := ""
	bestIndex := -1
	for idx, issue := range report.Issues {
		if strings.TrimSpace(issue.Severity) == automationPreflightSeverityError {
			report.Runnable = false
			report.Overall.Runnable = false
		}
		if severityRank[issue.Severity] > severityRank[bestSeverity] {
			bestSeverity = issue.Severity
		}
		if bestIndex >= 0 {
			current := report.Issues[bestIndex]
			if priority[issue.Section] > priority[current.Section] {
				continue
			}
			if priority[issue.Section] < priority[current.Section] {
				bestIndex = idx
				continue
			}
			if severityRank[issue.Severity] > severityRank[current.Severity] {
				bestIndex = idx
			}
			continue
		}
		bestIndex = idx
	}
	report.Overall.HighestSeverity = bestSeverity
	if bestIndex >= 0 {
		report.Overall.PrimaryIssueCode = report.Issues[bestIndex].Code
		report.Overall.PrimaryIssueMessage = report.Issues[bestIndex].Message
	}
}

func automationStartEligibilityReport(report AutomationPreflightReport) AutomationPreflightReport {
	return report
}

func automationStartRejection(report AutomationPreflightReport) error {
	primary := strings.TrimSpace(report.Overall.PrimaryIssueCode)
	message := strings.TrimSpace(report.Overall.PrimaryIssueMessage)
	if message == "" {
		message = "Automation cannot start for the requested scenario"
	}
	details := map[string]any{
		"retryable":   false,
		"user_action": automationPrimaryIssueUserAction(report.Issues, primary),
		"issues":      report.Issues,
	}
	switch primary {
	case automationCodeSourceDefinitionPathNotFound:
		return automationNotFound(primary, message, automationPrimaryIssueUserAction(report.Issues, primary), details)
	case automationCodePermissionDenied:
		return automationForbidden(primary, message, automationPrimaryIssueUserAction(report.Issues, primary), details)
	default:
		return automationInvalidArgument(primary, message, automationPrimaryIssueUserAction(report.Issues, primary), details)
	}
}

func automationPushRejectionCreatesFailedRun(report AutomationPreflightReport) bool {
	switch strings.TrimSpace(report.Overall.PrimaryIssueCode) {
	case "trigger_push_not_supported", "trigger_push_branch_not_matched":
		return false
	default:
		return true
	}
}

func automationPrimaryIssueUserAction(issues []AutomationPreflightIssue, code string) string {
	for _, issue := range issues {
		if issue.Code == code {
			return issue.UserAction
		}
	}
	return ""
}

func normalizeAutomationPreflightInput(input AutomationRepositoryAutomationPreflightMutation) AutomationRepositoryAutomationPreflightMutation {
	input.RepositoryOwner = strings.TrimSpace(input.RepositoryOwner)
	input.RepositoryName = strings.TrimSpace(input.RepositoryName)
	input.DefinitionPath = normalizeAutomationSourcePath(input.DefinitionPath)
	input.GitRef = strings.TrimSpace(input.GitRef)
	input.Scenario = strings.TrimSpace(input.Scenario)
	input.InputJSON = strings.TrimSpace(input.InputJSON)
	input.Branch = strings.TrimSpace(input.Branch)
	input.AuthorizationHeader = strings.TrimSpace(input.AuthorizationHeader)
	return input
}

func validateAutomationPreflightInput(input AutomationRepositoryAutomationPreflightMutation) error {
	if err := validateAutomationRepository(input.RepositoryOwner, input.RepositoryName); err != nil {
		return err
	}
	if !isAutomationSourcePath(input.DefinitionPath) {
		return invalidArgument("Automation definition path must be under .agentteam and end in .yml or .yaml")
	}
	if strings.TrimSpace(input.GitRef) == "" {
		return invalidArgument("Automation preflight ref is required")
	}
	switch input.Scenario {
	case automationPreflightScenarioManualStart:
		if strings.TrimSpace(input.Branch) != "" {
			return invalidArgument("Automation preflight branch is not supported for manual_start")
		}
		if err := validateOptionalJSON(input.InputJSON, "Automation preflight input_json must be valid JSON"); err != nil {
			return err
		}
	case automationPreflightScenarioScheduleTarget:
		if strings.TrimSpace(input.InputJSON) != "" {
			return invalidArgument("Automation preflight input_json is not supported for schedule_target_validate")
		}
		if strings.TrimSpace(input.Branch) != "" {
			return invalidArgument("Automation preflight branch is not supported for schedule_target_validate")
		}
	case automationPreflightScenarioEventPushValidate:
		if strings.TrimSpace(input.InputJSON) != "" {
			return invalidArgument("Automation preflight input_json is not supported for event_push_validate")
		}
		if strings.TrimSpace(input.Branch) == "" {
			return invalidArgument("Automation preflight branch is required for event_push_validate")
		}
	default:
		return invalidArgument("Automation preflight scenario is invalid")
	}
	return nil
}

func automationResolvedCommitSHA(ref string) string {
	ref = strings.TrimSpace(ref)
	if automationCommitSHAPattern.MatchString(ref) {
		return ref
	}
	return ""
}
