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

package v2

import (
	"time"

	actions_model "code.gitea.io/gitea/models/actions"
	"code.gitea.io/gitea/modules/actions"
	"code.gitea.io/gitea/modules/json"
	"code.gitea.io/gitea/modules/setting"
	api "code.gitea.io/gitea/modules/structs"
	"code.gitea.io/gitea/modules/web"
	"code.gitea.io/gitea/services/context"
)

// getSupportedActions converts the compatibility module data to API format
func getSupportedActions() map[string]api.ActionSupport {
	result := make(map[string]api.ActionSupport)
	for name, compat := range actions.BuiltinCompatibility {
		result[name] = api.ActionSupport{
			Versions: compat.Versions,
			Notes:    compat.Notes,
		}
	}
	return result
}

// GetActionsCapabilities returns structured capability information for AI consumption
// @Summary Get runner capabilities for AI workflow generation
// @Description Returns detailed runner capabilities, platform info, and action compatibility
// @Tags actions
// @Accept json
// @Produce json
// @Param owner path string true "owner of the repo"
// @Param repo path string true "name of the repo"
// @Success 200 {object} api.ActionsCapabilitiesResponse
// @Router /repos/{owner}/{repo}/actions/runners/capabilities [get]
func GetActionsCapabilities(ctx *context.APIContext) {
	repo := ctx.Repo.Repository

	// Get runners available for this repository
	runners, err := actions_model.GetRunnersOfRepo(ctx, repo.ID)
	if err != nil {
		ctx.APIErrorInternal(err)
		return
	}

	// Build response
	response := &api.ActionsCapabilitiesResponse{
		Runners: make([]*api.RunnerWithCapabilities, 0, len(runners)),
		Platform: &api.PlatformInfo{
			Type:                "gitea",
			Version:             setting.AppVer,
			DefaultActionsURL:   setting.Actions.DefaultActionsURL.URL(),
			SupportedActions:    getSupportedActions(),
			UnsupportedFeatures: actions.UnsupportedFeatures,
		},
		WorkflowHints: &api.WorkflowHints{
			PreferredCheckout:         "actions/checkout@v4",
			ArtifactUploadAlternative: "Use Gitea API: curl -X POST $GITHUB_SERVER_URL/api/v1/repos/$GITHUB_REPOSITORY/releases/{id}/assets",
			SecretAccess:              "Use ${{ secrets.NAME }} syntax",
		},
	}

	// Process each runner
	for _, runner := range runners {
		status := "offline"
		if runner.LastOnline.AsTime().Add(actions_model.RunnerOfflineTime).After(time.Now()) {
			status = "online"
		}

		runnerResp := &api.RunnerWithCapabilities{
			ID:     runner.ID,
			Name:   runner.Name,
			Status: status,
			Labels: runner.AgentLabels,
		}

		// Parse capabilities JSON if available
		if runner.CapabilitiesJSON != "" {
			var runnerCaps api.RunnerCapability
			if err := json.Unmarshal([]byte(runner.CapabilitiesJSON), &runnerCaps); err == nil {
				runnerResp.Capabilities = &runnerCaps
			}
		}

		// If no capabilities, infer from labels
		if runnerResp.Capabilities == nil {
			runnerResp.Capabilities = inferCapabilitiesFromLabels(runner.AgentLabels)
		}

		response.Runners = append(response.Runners, runnerResp)
	}

	ctx.JSON(200, response)
}

// inferCapabilitiesFromLabels attempts to infer capabilities from runner labels
func inferCapabilitiesFromLabels(labels []string) *api.RunnerCapability {
	caps := &api.RunnerCapability{
		Limitations: []string{
			"Capabilities inferred from labels - may not be accurate",
			"actions/upload-artifact@v4 not supported (use v3 or direct API upload)",
		},
	}

	for _, label := range labels {
		switch label {
		case "ubuntu-latest", "ubuntu-22.04", "ubuntu-20.04":
			caps.OS = "linux"
			caps.Shell = []string{"bash", "sh"}
		case "windows-latest", "windows-2022", "windows-2019":
			caps.OS = "windows"
			caps.Shell = []string{"pwsh", "powershell", "cmd"}
		case "macos-latest", "macos-13", "macos-12":
			caps.OS = "darwin"
			caps.Shell = []string{"bash", "sh", "zsh"}
		case "linux":
			caps.OS = "linux"
		case "x64", "amd64":
			caps.Arch = "amd64"
		case "arm64", "aarch64":
			caps.Arch = "arm64"
		case "docker":
			caps.Docker = true
			caps.ContainerRuntime = "docker"
		}
	}

	return caps
}

// ValidateWorkflow validates a workflow YAML and returns compatibility warnings
// @Summary Validate a workflow for compatibility
// @Description Parses workflow YAML and returns warnings about unsupported features
// @Tags actions
// @Accept json
// @Produce json
// @Param owner path string true "owner of the repo"
// @Param repo path string true "name of the repo"
// @Param body body api.WorkflowValidationRequest true "Workflow content"
// @Success 200 {object} api.WorkflowValidationResponse
// @Router /repos/{owner}/{repo}/actions/workflows/validate [post]
func ValidateWorkflow(ctx *context.APIContext) {
	form := web.GetForm(ctx).(*api.WorkflowValidationRequest)

	response := &api.WorkflowValidationResponse{
		Valid:    true,
		Warnings: make([]*api.WorkflowValidationWarning, 0),
	}

	// Check for known incompatible actions using the compatibility module
	for action, message := range actions.IncompatibleActions {
		if containsAction(form.Content, action) {
			response.Warnings = append(response.Warnings, &api.WorkflowValidationWarning{
				Action:     action,
				Severity:   "error",
				Message:    message,
				Suggestion: actions.GetSuggestedAlternative(action),
			})
		}
	}

	if len(response.Warnings) > 0 {
		response.Valid = false
	}

	ctx.JSON(200, response)
}

// containsAction checks if workflow content contains a specific action
func containsAction(content, action string) bool {
	// Simple string search - could be enhanced with YAML parsing
	return len(content) > 0 && len(action) > 0 &&
		(contains(content, "uses: "+action) || contains(content, "uses: \""+action+"\""))
}

func contains(s, substr string) bool {
	return len(s) >= len(substr) && (s == substr || len(s) > 0 && containsSubstr(s, substr))
}

func containsSubstr(s, substr string) bool {
	for i := 0; i <= len(s)-len(substr); i++ {
		if s[i:i+len(substr)] == substr {
			return true
		}
	}
	return false
}
