package cmd

import (
	"fmt"
	"net/http"
	"strconv"
	"strings"
	"time"

	"code.gitea.io/sdk/gitea"
	"github.com/spf13/cobra"

	"forgejo.zerova.net/public/fj/internal/api"
	"forgejo.zerova.net/public/fj/internal/config"
)

// ActionRun represents a workflow run
type ActionRun struct {
	ID          int64  `json:"id"`
	Title       string `json:"title"`
	WorkflowID  string `json:"workflow_id"`
	IndexInRepo int64  `json:"index_in_repo"`
	Event       string `json:"event"`
	Status      string `json:"status"`
	CommitSHA   string `json:"commit_sha"`
	PrettyRef   string `json:"prettyref"`
	Created     string `json:"created"`
	Updated     string `json:"updated"`
	Started     string `json:"started"`
}

// ActionRunList represents a list of workflow runs
type ActionRunList struct {
	TotalCount   int         `json:"total_count"`
	WorkflowRuns []ActionRun `json:"workflow_runs"`
}

// ActionTask represents a job/task within a workflow run
type ActionTask struct {
	ID           int64  `json:"id"`
	Name         string `json:"name"`
	HeadBranch   string `json:"head_branch"`
	HeadSHA      string `json:"head_sha"`
	RunNumber    int64  `json:"run_number"`
	Event        string `json:"event"`
	DisplayTitle string `json:"display_title"`
	Status       string `json:"status"`
	WorkflowID   string `json:"workflow_id"`
	URL          string `json:"url"`
	CreatedAt    string `json:"created_at"`
	UpdatedAt    string `json:"updated_at"`
	RunStartedAt string `json:"run_started_at"`
}

// ActionTaskList represents a list of tasks/jobs
type ActionTaskList struct {
	WorkflowRuns []ActionTask `json:"workflow_runs"`
	TotalCount   int          `json:"total_count"`
}

// Workflow represents a workflow definition
type Workflow struct {
	ID    int64  `json:"id"`
	Name  string `json:"name"`
	Path  string `json:"path"`
	State string `json:"state"`
}

// WorkflowList represents a list of workflows
type WorkflowList struct {
	Workflows  []Workflow `json:"workflows"`
	TotalCount int        `json:"total_count"`
}

// ContentsResponse represents a file/directory in the repository
type ContentsResponse struct {
	Name string `json:"name"`
	Path string `json:"path"`
	Type string `json:"type"`
	Size int64  `json:"size"`
}

var actionsCmd = &cobra.Command{
	Use:     "actions",
	Aliases: []string{"action"},
	Short:   "Manage Forgejo Actions",
	Long:    "View and manage workflows, runs, secrets, and variables for Forgejo Actions in your repositories.",
}

// Run commands (compatible with gh run)
var runCmd = &cobra.Command{
	Use:   "run",
	Short: "View and manage workflow runs",
	Long:  "List, view, and manage workflow runs.",
}

var runListCmd = &cobra.Command{
	Use:   "list",
	Short: "List recent workflow runs",
	Long:  "List recent workflow runs for a repository.",
	Example: `  # List recent workflow runs
  fj actions run list

  # List runs with a custom limit
  fj actions run list -L 50

  # Output as JSON
  fj actions run list --json`,
	RunE: runRunList,
}

var runViewCmd = &cobra.Command{
	Use:   "view <run-id>",
	Short: "View a workflow run",
	Long:  "View details about a specific workflow run.",
	Example: `  # View a workflow run
  fj actions run view 123

  # View with job details
  fj actions run view 123 -v

  # View logs for a specific job
  fj actions run view 123 --job 456 --log

  # View only failed logs
  fj actions run view 123 --log-failed`,
	Args: cobra.ExactArgs(1),
	RunE: runRunView,
}

var runWatchCmd = &cobra.Command{
	Use:   "watch <run-id>",
	Short: "Watch a workflow run",
	Long:  "Poll a workflow run until it completes.",
	Example: `  # Watch a run until it completes
  fj actions run watch 123

  # Watch with a custom polling interval
  fj actions run watch 123 -i 10s`,
	Args: cobra.ExactArgs(1),
	RunE: runRunWatch,
}

var runRerunCmd = &cobra.Command{
	Use:   "rerun <run-id>",
	Short: "Rerun a workflow run",
	Long:  "Trigger a rerun for a specific workflow run.",
	Example: `  # Rerun a failed workflow run
  fj actions run rerun 123`,
	Args: cobra.ExactArgs(1),
	RunE: runRunRerun,
}

var runCancelCmd = &cobra.Command{
	Use:   "cancel <run-id>",
	Short: "Cancel a workflow run",
	Long:  "Cancel a running workflow run.",
	Example: `  # Cancel a running workflow
  fj actions run cancel 123`,
	Args: cobra.ExactArgs(1),
	RunE: runRunCancel,
}

// Workflow commands
var workflowCmd = &cobra.Command{
	Use:   "workflow",
	Short: "Manage workflows",
	Long:  "List, view, and run workflows.",
}

var workflowListCmd = &cobra.Command{
	Use:   "list",
	Short: "List workflows",
	Long:  "List all workflows in a repository.",
	Example: `  # List all workflows
  fj actions workflow list

  # List workflows as JSON
  fj actions workflow list --json

  # List workflows for a specific repo
  fj actions workflow list -R owner/repo`,
	RunE: runWorkflowList,
}

var workflowViewCmd = &cobra.Command{
	Use:   "view <workflow>",
	Short: "View a workflow",
	Long:  "View details about a specific workflow. You can specify the workflow by name or filename.",
	Example: `  # View a workflow by filename
  fj actions workflow view ci.yml

  # View as JSON
  fj actions workflow view ci.yml --json`,
	Args: cobra.ExactArgs(1),
	RunE: runWorkflowView,
}

var workflowRunCmd = &cobra.Command{
	Use:   "run <workflow>",
	Short: "Run a workflow",
	Long:  "Trigger a workflow_dispatch event for a workflow. The workflow must support the workflow_dispatch trigger.",
	Example: `  # Trigger a workflow on the default branch
  fj actions workflow run deploy.yml

  # Trigger on a specific branch with input parameters
  fj actions workflow run deploy.yml -r staging -f environment=staging -f version=1.2.3`,
	Args: cobra.ExactArgs(1),
	RunE: runWorkflowRun,
}

var workflowEnableCmd = &cobra.Command{
	Use:   "enable <workflow>",
	Short: "Enable a workflow",
	Long:  "Enable a workflow so it can be triggered.\n\nNote: This feature requires Forgejo 15.0+ or Gitea 1.24+.\nFor older versions, use the web UI to enable workflows.",
	Example: `  # Enable a workflow
  fj actions workflow enable ci.yml`,
	Args: cobra.ExactArgs(1),
	RunE: runWorkflowEnable,
}

var workflowDisableCmd = &cobra.Command{
	Use:   "disable <workflow>",
	Short: "Disable a workflow",
	Long:  "Disable a workflow so it cannot be triggered.\n\nNote: This feature requires Forgejo 15.0+ or Gitea 1.24+.\nFor older versions, use the web UI to disable workflows.",
	Example: `  # Disable a workflow
  fj actions workflow disable ci.yml`,
	Args: cobra.ExactArgs(1),
	RunE: runWorkflowDisable,
}

// Secret commands
var actionsSecretCmd = &cobra.Command{
	Use:   "secret",
	Short: "Manage repository secrets",
	Long:  "List, create, and delete secrets for Forgejo Actions.",
}

var actionsSecretListCmd = &cobra.Command{
	Use:   "list",
	Short: "List repository secrets",
	Long:  "List all secrets for a repository.",
	Example: `  # List all secrets
  fj actions secret list

  # List secrets for a specific repo
  fj actions secret list -R owner/repo`,
	RunE: runActionsSecretList,
}

var actionsSecretCreateCmd = &cobra.Command{
	Use:   "create <name>",
	Short: "Create or update a repository secret",
	Long:  "Create or update a secret for Forgejo Actions. The secret value will be read from stdin.",
	Example: `  # Create a secret (will prompt for value)
  fj actions secret create DEPLOY_TOKEN

  # Create a secret for a specific repo
  fj actions secret create API_KEY -R owner/repo`,
	Args: cobra.ExactArgs(1),
	RunE: runActionsSecretCreate,
}

var actionsSecretDeleteCmd = &cobra.Command{
	Use:   "delete <name>",
	Short: "Delete a repository secret",
	Long:  "Delete a secret from Forgejo Actions.",
	Example: `  # Delete a secret
  fj actions secret delete DEPLOY_TOKEN`,
	Args: cobra.ExactArgs(1),
	RunE: runActionsSecretDelete,
}

// Variable commands
var actionsVariableCmd = &cobra.Command{
	Use:   "variable",
	Short: "Manage repository variables",
	Long:  "List, get, create, update, and delete variables for Forgejo Actions.",
}

var actionsVariableListCmd = &cobra.Command{
	Use:   "list",
	Short: "List repository variables",
	Long:  "List all variables for a repository.",
	Example: `  # List all variables
  fj actions variable list

  # List variables for a specific repo
  fj actions variable list -R owner/repo`,
	RunE: runActionsVariableList,
}

var actionsVariableGetCmd = &cobra.Command{
	Use:   "get <name>",
	Short: "Get a repository variable",
	Long:  "Get the value of a specific repository variable.",
	Example: `  # Get a variable value
  fj actions variable get ENVIRONMENT`,
	Args: cobra.ExactArgs(1),
	RunE: runActionsVariableGet,
}

var actionsVariableCreateCmd = &cobra.Command{
	Use:   "create <name> <value>",
	Short: "Create a repository variable",
	Long:  "Create a new variable for Forgejo Actions.",
	Example: `  # Create a variable
  fj actions variable create ENVIRONMENT production

  # Create a variable for a specific repo
  fj actions variable create NODE_VERSION 20 -R owner/repo`,
	Args: cobra.ExactArgs(2),
	RunE: runActionsVariableCreate,
}

var actionsVariableUpdateCmd = &cobra.Command{
	Use:   "update <name> <value>",
	Short: "Update a repository variable",
	Long:  "Update an existing variable for Forgejo Actions.",
	Example: `  # Update a variable
  fj actions variable update ENVIRONMENT staging`,
	Args: cobra.ExactArgs(2),
	RunE: runActionsVariableUpdate,
}

var actionsVariableDeleteCmd = &cobra.Command{
	Use:   "delete <name>",
	Short: "Delete a repository variable",
	Long:  "Delete a variable from Forgejo Actions.",
	Example: `  # Delete a variable
  fj actions variable delete ENVIRONMENT`,
	Args: cobra.ExactArgs(1),
	RunE: runActionsVariableDelete,
}

func init() {
	rootCmd.AddCommand(actionsCmd)

	// Add run commands (gh run compatible)
	actionsCmd.AddCommand(runCmd)
	runCmd.AddCommand(runListCmd)
	runCmd.AddCommand(runViewCmd)
	runCmd.AddCommand(runWatchCmd)
	runCmd.AddCommand(runRerunCmd)
	runCmd.AddCommand(runCancelCmd)

	// Add workflow commands (gh workflow compatible)
	actionsCmd.AddCommand(workflowCmd)
	workflowCmd.AddCommand(workflowListCmd)
	workflowCmd.AddCommand(workflowViewCmd)
	workflowCmd.AddCommand(workflowRunCmd)
	workflowCmd.AddCommand(workflowEnableCmd)
	workflowCmd.AddCommand(workflowDisableCmd)

	// Add secret commands
	actionsCmd.AddCommand(actionsSecretCmd)
	actionsSecretCmd.AddCommand(actionsSecretListCmd)
	actionsSecretCmd.AddCommand(actionsSecretCreateCmd)
	actionsSecretCmd.AddCommand(actionsSecretDeleteCmd)

	// Add variable commands
	actionsCmd.AddCommand(actionsVariableCmd)
	actionsVariableCmd.AddCommand(actionsVariableListCmd)
	actionsVariableCmd.AddCommand(actionsVariableGetCmd)
	actionsVariableCmd.AddCommand(actionsVariableCreateCmd)
	actionsVariableCmd.AddCommand(actionsVariableUpdateCmd)
	actionsVariableCmd.AddCommand(actionsVariableDeleteCmd)

	// Add flags for run commands
	addRepoFlags(runListCmd)
	runListCmd.Flags().IntP("limit", "L", 20, "Maximum number of runs to list")
	addJSONFlags(runListCmd, "Output workflow runs as JSON")
	addRepoFlags(runViewCmd)
	runViewCmd.Flags().BoolP("verbose", "v", false, "Show job steps")
	runViewCmd.Flags().BoolP("log", "", false, "View full log for either a run or specific job")
	runViewCmd.Flags().StringP("job", "j", "", "View a specific job ID from a run")
	runViewCmd.Flags().BoolP("log-failed", "", false, "View the log for any failed steps in a run or specific job")
	addJSONFlags(runViewCmd, "Output workflow run as JSON")
	addRepoFlags(runWatchCmd)
	runWatchCmd.Flags().DurationP("interval", "i", 5*time.Second, "Polling interval")
	addRepoFlags(runRerunCmd)
	addRepoFlags(runCancelCmd)

	// Add flags for workflow commands
	addRepoFlags(workflowListCmd)
	workflowListCmd.Flags().IntP("limit", "L", 20, "Maximum number of workflows to list")
	addJSONFlags(workflowListCmd, "Output workflows as JSON")
	addRepoFlags(workflowViewCmd)
	addJSONFlags(workflowViewCmd, "Output workflow as JSON")
	addRepoFlags(workflowRunCmd)
	addRepoFlags(workflowEnableCmd)
	addRepoFlags(workflowDisableCmd)
	workflowRunCmd.Flags().StringP("ref", "r", "", "Branch or tag name to run the workflow on (defaults to repository's default branch)")
	workflowRunCmd.Flags().StringSliceP("field", "f", nil, "Add a string parameter in key=value format (can be used multiple times)")
	workflowRunCmd.Flags().StringSliceP("raw-field", "F", nil, "Add a string parameter in key=value format, reading from file if value starts with @ (can be used multiple times)")

	// Add flags for secret commands
	addRepoFlags(actionsSecretListCmd)
	addRepoFlags(actionsSecretCreateCmd)
	addRepoFlags(actionsSecretDeleteCmd)

	// Add flags for variable commands
	addRepoFlags(actionsVariableListCmd)
	addJSONFlags(actionsVariableListCmd, "Output variables as JSON")
	addRepoFlags(actionsVariableGetCmd)
	addRepoFlags(actionsVariableCreateCmd)
	addRepoFlags(actionsVariableUpdateCmd)
	addRepoFlags(actionsVariableDeleteCmd)
}

func addRepoFlags(cmd *cobra.Command) {
	cmd.Flags().StringP("repo", "R", "", "Repository in owner/name format (auto-detected from git if not specified)")
}

// Run command implementations

func runRunList(cmd *cobra.Command, args []string) error {
	cfg, err := config.Load()
	if err != nil {
		return fmt.Errorf("failed to load config: %w", err)
	}

	client, err := api.NewClientFromConfig(cfg, "", getDetectedHost(), getCwd())
	if err != nil {
		return fmt.Errorf("failed to create client: %w", err)
	}

	repo, _ := cmd.Flags().GetString("repo")
	owner, name, err := parseRepo(repo)
	if err != nil {
		return err
	}

	limit, _ := cmd.Flags().GetInt("limit")

	// Call the API endpoint directly since SDK doesn't have it yet
	endpoint := fmt.Sprintf("/api/v1/repos/%s/%s/actions/runs?limit=%d", owner, name, limit)

	var runList ActionRunList
	if err := client.GetJSON(endpoint, &runList); err != nil {
		return fmt.Errorf("failed to list runs: %w", err)
	}

	if wantJSON(cmd) {
		return outputJSON(cmd, runList.WorkflowRuns)
	}

	if len(runList.WorkflowRuns) == 0 {
		fmt.Fprintln(ios.Out, "No workflow runs found")
		return nil
	}

	tp := ios.NewTablePrinter()
	tp.AddHeader("STATUS", "TITLE", "WORKFLOW", "EVENT", "ID", "CREATED")
	for _, run := range runList.WorkflowRuns {
		createdTime, err := time.Parse(time.RFC3339, run.Created)
		if err != nil {
			createdTime = time.Now()
		}
		timeStr := formatTimeSince(createdTime)
		tp.AddRow(formatStatus(run.Status), run.Title, run.WorkflowID, run.Event, fmt.Sprintf("%d", run.ID), timeStr)
	}
	return tp.Render()
}

func runRunView(cmd *cobra.Command, args []string) error {
	cfg, err := config.Load()
	if err != nil {
		return fmt.Errorf("failed to load config: %w", err)
	}

	client, err := api.NewClientFromConfig(cfg, "", getDetectedHost(), getCwd())
	if err != nil {
		return fmt.Errorf("failed to create client: %w", err)
	}

	repo, _ := cmd.Flags().GetString("repo")
	owner, name, err := parseRepo(repo)
	if err != nil {
		return err
	}

	runID, err := strconv.ParseInt(args[0], 10, 64)
	if err != nil {
		return fmt.Errorf("invalid run ID: %w", err)
	}

	verbose, _ := cmd.Flags().GetBool("verbose")
	showLog, _ := cmd.Flags().GetBool("log")
	jobIDStr, _ := cmd.Flags().GetString("job")
	showLogFailed, _ := cmd.Flags().GetBool("log-failed")
	jsonRequested := wantJSON(cmd)

	var jobID int64
	if jobIDStr != "" {
		var err error
		jobID, err = strconv.ParseInt(jobIDStr, 10, 64)
		if err != nil {
			return fmt.Errorf("invalid job ID: %w", err)
		}
	}

	if jsonRequested && (showLog || showLogFailed) {
		return fmt.Errorf("--json cannot be used with --log or --log-failed")
	}

	// Call the API endpoint directly since SDK doesn't have it yet
	endpoint := fmt.Sprintf("/api/v1/repos/%s/%s/actions/runs/%d", owner, name, runID)

	var run ActionRun
	if err := client.GetJSON(endpoint, &run); err != nil {
		return fmt.Errorf("failed to get run: %w", err)
	}

	needsJobs := verbose || showLog || showLogFailed || jobID > 0

	if jsonRequested {
		var runTasks []ActionTask
		if needsJobs {
			tasksEndpoint := fmt.Sprintf("/api/v1/repos/%s/%s/actions/tasks", owner, name)
			var taskList ActionTaskList
			if err := client.GetJSON(tasksEndpoint, &taskList); err != nil {
				return fmt.Errorf("failed to get tasks: %w", err)
			}

			for _, task := range taskList.WorkflowRuns {
				if task.RunNumber == run.IndexInRepo {
					runTasks = append(runTasks, task)
				}
			}

			if jobID > 0 {
				var filtered []ActionTask
				for _, task := range runTasks {
					if task.ID == jobID {
						filtered = append(filtered, task)
						break
					}
				}
				if len(filtered) == 0 {
					return fmt.Errorf("job %d not found in this run", jobID)
				}
				runTasks = filtered
			}
		}

		payload := struct {
			Run   ActionRun    `json:"run"`
			Tasks []ActionTask `json:"tasks,omitempty"`
		}{
			Run:   run,
			Tasks: runTasks,
		}
		return outputJSON(cmd, payload)
	}

	// Display run information
	fmt.Fprintf(ios.Out, "Title: %s\n", run.Title)
	fmt.Fprintf(ios.Out, "Workflow: %s\n", run.WorkflowID)
	fmt.Fprintf(ios.Out, "Run: #%d\n", run.IndexInRepo)
	fmt.Fprintf(ios.Out, "Status: %s\n", formatStatus(run.Status))
	fmt.Fprintf(ios.Out, "Event: %s\n", run.Event)
	fmt.Fprintf(ios.Out, "Ref: %s\n", run.PrettyRef)

	commit := run.CommitSHA
	if len(commit) > 8 {
		commit = commit[:8]
	}
	fmt.Fprintf(ios.Out, "Commit: %s\n", commit)

	if createdTime, err := time.Parse(time.RFC3339, run.Created); err == nil {
		fmt.Fprintf(ios.Out, "Created: %s\n", createdTime.Format("2006-01-02 15:04:05"))
	}
	if run.Started != "" {
		if startedTime, err := time.Parse(time.RFC3339, run.Started); err == nil {
			fmt.Fprintf(ios.Out, "Started: %s\n", startedTime.Format("2006-01-02 15:04:05"))
		}
	}
	if updatedTime, err := time.Parse(time.RFC3339, run.Updated); err == nil {
		fmt.Fprintf(ios.Out, "Updated: %s\n", updatedTime.Format("2006-01-02 15:04:05"))
	}

	// Fetch jobs if needed for verbose, log, or job-specific views
	if !needsJobs {
		return nil
	}

	tasksEndpoint := fmt.Sprintf("/api/v1/repos/%s/%s/actions/tasks", owner, name)
	var taskList ActionTaskList
	if err := client.GetJSON(tasksEndpoint, &taskList); err != nil {
		return fmt.Errorf("failed to get tasks: %w", err)
	}

	// Filter tasks for this run number
	var runTasks []ActionTask
	for _, task := range taskList.WorkflowRuns {
		if task.RunNumber == run.IndexInRepo {
			runTasks = append(runTasks, task)
		}
	}

	if len(runTasks) == 0 {
		fmt.Fprintln(ios.Out, "\nNo jobs found for this run")
		return nil
	}

	// If --job is specified, filter to that job
	if jobID > 0 {
		var found bool
		for _, task := range runTasks {
			if task.ID == jobID {
				runTasks = []ActionTask{task}
				found = true
				break
			}
		}
		if !found {
			return fmt.Errorf("job %d not found in this run", jobID)
		}
	}

	// Case 1: --verbose (show job steps/details without logs)
	if verbose && !showLog && !showLogFailed {
		fmt.Fprintln(ios.Out, "\nJobs:")
		for _, task := range runTasks {
			fmt.Fprintf(ios.Out, "\n  %s - %s (ID: %d)\n", formatStatus(task.Status), task.Name, task.ID)
			if startedTime, err := time.Parse(time.RFC3339, task.RunStartedAt); err == nil {
				fmt.Fprintf(ios.Out, "    Started: %s\n", startedTime.Format("2006-01-02 15:04:05"))
			}
			if updatedTime, err := time.Parse(time.RFC3339, task.UpdatedAt); err == nil {
				fmt.Fprintf(ios.Out, "    Updated: %s\n", updatedTime.Format("2006-01-02 15:04:05"))
			}
		}
		return nil
	}

	// Case 2: --log or --log-failed (show logs)
	if showLog || showLogFailed {
		for _, task := range runTasks {
			if err := showJobLog(client, owner, name, task, showLogFailed); err != nil {
				fmt.Fprintf(ios.Out, "\nError fetching log for job %s: %v\n", task.Name, err)
			}
		}
		return nil
	}

	// Case 3: --job without --log or --verbose (show job details only)
	if jobID > 0 {
		task := runTasks[0]
		fmt.Fprintln(ios.Out, "\nJob Details:")
		fmt.Fprintf(ios.Out, "  Name: %s\n", task.Name)
		fmt.Fprintf(ios.Out, "  ID: %d\n", task.ID)
		fmt.Fprintf(ios.Out, "  Status: %s\n", formatStatus(task.Status))
		if startedTime, err := time.Parse(time.RFC3339, task.RunStartedAt); err == nil {
			fmt.Fprintf(ios.Out, "  Started: %s\n", startedTime.Format("2006-01-02 15:04:05"))
		}
		if updatedTime, err := time.Parse(time.RFC3339, task.UpdatedAt); err == nil {
			fmt.Fprintf(ios.Out, "  Updated: %s\n", updatedTime.Format("2006-01-02 15:04:05"))
		}
	}

	return nil
}

func runRunWatch(cmd *cobra.Command, args []string) error {
	cfg, err := config.Load()
	if err != nil {
		return fmt.Errorf("failed to load config: %w", err)
	}

	client, err := api.NewClientFromConfig(cfg, "", getDetectedHost(), getCwd())
	if err != nil {
		return fmt.Errorf("failed to create client: %w", err)
	}

	repo, _ := cmd.Flags().GetString("repo")
	owner, name, err := parseRepo(repo)
	if err != nil {
		return err
	}

	runID, err := strconv.ParseInt(args[0], 10, 64)
	if err != nil {
		return fmt.Errorf("invalid run ID: %w", err)
	}

	interval, _ := cmd.Flags().GetDuration("interval")
	if interval <= 0 {
		return fmt.Errorf("interval must be greater than 0")
	}

	endpoint := fmt.Sprintf("/api/v1/repos/%s/%s/actions/runs/%d", owner, name, runID)

	var lastStatus string
	for {
		var run ActionRun
		if err := client.GetJSON(endpoint, &run); err != nil {
			return fmt.Errorf("failed to get run: %w", err)
		}

		if run.Status != lastStatus {
			fmt.Fprintf(ios.Out, "Status: %s\n", formatStatus(run.Status))
			lastStatus = run.Status
		}

		if isRunComplete(run.Status) {
			fmt.Fprintf(ios.Out, "Run #%d completed with status %s\n", run.IndexInRepo, formatStatus(run.Status))
			return nil
		}

		time.Sleep(interval)
	}
}

func runRunRerun(cmd *cobra.Command, args []string) error {
	cfg, err := config.Load()
	if err != nil {
		return fmt.Errorf("failed to load config: %w", err)
	}

	client, err := api.NewClientFromConfig(cfg, "", getDetectedHost(), getCwd())
	if err != nil {
		return fmt.Errorf("failed to create client: %w", err)
	}

	repo, _ := cmd.Flags().GetString("repo")
	owner, name, err := parseRepo(repo)
	if err != nil {
		return err
	}

	runID, err := strconv.ParseInt(args[0], 10, 64)
	if err != nil {
		return fmt.Errorf("invalid run ID: %w", err)
	}

	endpoint := fmt.Sprintf("/api/v1/repos/%s/%s/actions/runs/%d/rerun", owner, name, runID)
	if err := client.PostJSON(endpoint, nil, nil); err != nil {
		return fmt.Errorf("failed to rerun workflow: %w", err)
	}

	fmt.Fprintf(ios.Out, "✓ Rerun requested for run %d\n", runID)
	return nil
}

func runRunCancel(cmd *cobra.Command, args []string) error {
	cfg, err := config.Load()
	if err != nil {
		return fmt.Errorf("failed to load config: %w", err)
	}

	client, err := api.NewClientFromConfig(cfg, "", getDetectedHost(), getCwd())
	if err != nil {
		return fmt.Errorf("failed to create client: %w", err)
	}

	repo, _ := cmd.Flags().GetString("repo")
	owner, name, err := parseRepo(repo)
	if err != nil {
		return err
	}

	runID, err := strconv.ParseInt(args[0], 10, 64)
	if err != nil {
		return fmt.Errorf("invalid run ID: %w", err)
	}

	endpoint := fmt.Sprintf("/api/v1/repos/%s/%s/actions/runs/%d/cancel", owner, name, runID)
	if err := client.PostJSON(endpoint, nil, nil); err != nil {
		return fmt.Errorf("failed to cancel workflow run: %w", err)
	}

	fmt.Fprintf(ios.Out, "✓ Cancel requested for run %d\n", runID)
	return nil
}

func showJobLog(client *api.Client, owner, name string, task ActionTask, logFailed bool) error {
	// Fetch log from API: GET /api/v1/repos/{owner}/{repo}/actions/jobs/{job_id}/logs
	logURL := fmt.Sprintf("https://%s/api/v1/repos/%s/%s/actions/jobs/%d/logs",
		client.Hostname(), owner, name, task.ID)

	fmt.Fprintf(ios.Out, "\n========================================\n")
	fmt.Fprintf(ios.Out, "Job: %s (ID: %d)\n", task.Name, task.ID)
	fmt.Fprintf(ios.Out, "Status: %s\n", formatStatus(task.Status))
	fmt.Fprintf(ios.Out, "========================================\n\n")

	// Use GetRawLog helper
	logContent, err := client.GetRawLog(logURL)
	if err != nil {
		return err
	}

	// If --log-failed, filter to only show failed steps
	// For now, just show all logs (filtering failed steps would require parsing the log format)
	if logFailed {
		// TODO: Implement filtering for failed steps only
		// This would require parsing the log format and identifying failed step markers
		fmt.Fprintln(ios.Out, "Note: --log-failed filtering not yet implemented, showing all logs")
	}

	fmt.Fprint(ios.Out, logContent)
	fmt.Fprintln(ios.Out)

	return nil
}

func formatStatus(status string) string {
	switch status {
	case "success":
		return "✓ success"
	case "failure":
		return "✗ failure"
	case "cancelled":
		return "- cancelled"
	case "skipped":
		return "○ skipped"
	case "in_progress", "running":
		return "● in progress"
	case "queued", "waiting":
		return "○ queued"
	default:
		return status
	}
}

func isRunComplete(status string) bool {
	switch status {
	case "success", "failure", "cancelled", "skipped":
		return true
	default:
		return false
	}
}

func formatTimeSince(t time.Time) string {
	duration := time.Since(t)

	if duration < time.Minute {
		return "just now"
	} else if duration < time.Hour {
		mins := int(duration.Minutes())
		if mins == 1 {
			return "1 minute ago"
		}
		return fmt.Sprintf("%d minutes ago", mins)
	} else if duration < 24*time.Hour {
		hours := int(duration.Hours())
		if hours == 1 {
			return "1 hour ago"
		}
		return fmt.Sprintf("%d hours ago", hours)
	}

	days := int(duration.Hours() / 24)
	if days == 1 {
		return "1 day ago"
	}
	return fmt.Sprintf("%d days ago", days)
}

// Workflow command implementations

func runWorkflowList(cmd *cobra.Command, args []string) error {
	cfg, err := config.Load()
	if err != nil {
		return fmt.Errorf("failed to load config: %w", err)
	}

	client, err := api.NewClientFromConfig(cfg, "", getDetectedHost(), getCwd())
	if err != nil {
		return fmt.Errorf("failed to create client: %w", err)
	}

	repo, _ := cmd.Flags().GetString("repo")
	owner, name, err := parseRepo(repo)
	if err != nil {
		return err
	}

	limit, _ := cmd.Flags().GetInt("limit")

	// List workflows from both .gitea/workflows and .forgejo/workflows
	var allWorkflows []Workflow

	for _, dir := range []string{".gitea/workflows", ".forgejo/workflows"} {
		endpoint := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", owner, name, dir)

		var contents []ContentsResponse
		if err := client.GetJSON(endpoint, &contents); err != nil {
			// Directory might not exist, continue
			continue
		}

		for _, content := range contents {
			if content.Type == "file" && (len(content.Name) > 4 && (content.Name[len(content.Name)-4:] == ".yml" || content.Name[len(content.Name)-5:] == ".yaml")) {
				workflow := Workflow{
					Name:  content.Name,
					Path:  content.Path,
					State: "active",
				}
				allWorkflows = append(allWorkflows, workflow)

				if len(allWorkflows) >= limit {
					break
				}
			}
		}

		if len(allWorkflows) >= limit {
			break
		}
	}

	if len(allWorkflows) == 0 {
		if wantJSON(cmd) {
			return outputJSON(cmd, allWorkflows)
		}
		fmt.Fprintln(ios.Out, "No workflows found")
		return nil
	}

	if wantJSON(cmd) {
		return outputJSON(cmd, allWorkflows)
	}

	tp := ios.NewTablePrinter()
	tp.AddHeader("NAME", "STATE", "PATH")

	for _, workflow := range allWorkflows {
		tp.AddRow(workflow.Name, workflow.State, workflow.Path)
	}

	return tp.Render()
}

func runWorkflowView(cmd *cobra.Command, args []string) error {
	cfg, err := config.Load()
	if err != nil {
		return fmt.Errorf("failed to load config: %w", err)
	}

	client, err := api.NewClientFromConfig(cfg, "", getDetectedHost(), getCwd())
	if err != nil {
		return fmt.Errorf("failed to create client: %w", err)
	}

	repo, _ := cmd.Flags().GetString("repo")
	owner, name, err := parseRepo(repo)
	if err != nil {
		return err
	}

	workflowIdentifier := args[0]
	workflow, err := findWorkflow(client, owner, name, workflowIdentifier)
	if err != nil {
		return err
	}

	var latestRun *ActionRun

	// Get the latest run for this workflow
	runsEndpoint := fmt.Sprintf("/api/v1/repos/%s/%s/actions/runs?workflow_id=%s&limit=1", owner, name, workflow.Path)
	var runList ActionRunList
	if err := client.GetJSON(runsEndpoint, &runList); err == nil && len(runList.WorkflowRuns) > 0 {
		latestRun = &runList.WorkflowRuns[0]
	}

	if wantJSON(cmd) {
		payload := struct {
			Workflow  *Workflow  `json:"workflow"`
			LatestRun *ActionRun `json:"latest_run,omitempty"`
		}{
			Workflow:  workflow,
			LatestRun: latestRun,
		}
		return outputJSON(cmd, payload)
	}

	// Display workflow information
	fmt.Fprintf(ios.Out, "Name: %s\n", workflow.Name)
	fmt.Fprintf(ios.Out, "Path: %s\n", workflow.Path)
	fmt.Fprintf(ios.Out, "State: %s\n", workflow.State)

	if latestRun != nil {
		fmt.Fprintf(ios.Out, "\nLatest run:\n")
		fmt.Fprintf(ios.Out, "  Status: %s\n", formatStatus(latestRun.Status))
		fmt.Fprintf(ios.Out, "  Event: %s\n", latestRun.Event)
		fmt.Fprintf(ios.Out, "  Ref: %s\n", latestRun.PrettyRef)
		if createdTime, err := time.Parse(time.RFC3339, latestRun.Created); err == nil {
			fmt.Fprintf(ios.Out, "  Created: %s\n", formatTimeSince(createdTime))
		}
	}

	return nil
}

func runWorkflowRun(cmd *cobra.Command, args []string) error {
	cfg, err := config.Load()
	if err != nil {
		return fmt.Errorf("failed to load config: %w", err)
	}

	client, err := api.NewClientFromConfig(cfg, "", getDetectedHost(), getCwd())
	if err != nil {
		return fmt.Errorf("failed to create client: %w", err)
	}

	repo, _ := cmd.Flags().GetString("repo")
	owner, name, err := parseRepo(repo)
	if err != nil {
		return err
	}

	workflowIdentifier := args[0]
	ref, _ := cmd.Flags().GetString("ref")
	fields, _ := cmd.Flags().GetStringSlice("field")
	rawFields, _ := cmd.Flags().GetStringSlice("raw-field")

	// If no ref is specified, get the repository's default branch
	if ref == "" {
		repoInfo, _, err := client.GetRepo(owner, name)
		if err != nil {
			return fmt.Errorf("failed to get repository info: %w", err)
		}
		ref = repoInfo.DefaultBranch
	}

	// Build the inputs map
	inputs := make(map[string]string)

	// Process -f/--field flags
	for _, field := range fields {
		parts := splitKeyValue(field)
		if len(parts) == 2 {
			inputs[parts[0]] = parts[1]
		}
	}

	// Process -F/--raw-field flags (same as field for now, file reading can be added later)
	for _, field := range rawFields {
		parts := splitKeyValue(field)
		if len(parts) == 2 {
			inputs[parts[0]] = parts[1]
		}
	}

	// Prepare the dispatch request
	dispatchReq := map[string]any{
		"ref": ref,
	}
	if len(inputs) > 0 {
		dispatchReq["inputs"] = inputs
	}

	// Trigger the workflow
	endpoint := fmt.Sprintf("/api/v1/repos/%s/%s/actions/workflows/%s/dispatches", owner, name, workflowIdentifier)

	if err := client.PostJSON(endpoint, dispatchReq, nil); err != nil {
		return fmt.Errorf("failed to trigger workflow: %w", err)
	}

	fmt.Fprintf(ios.Out, "✓ Workflow '%s' triggered successfully\n", workflowIdentifier)
	fmt.Fprintf(ios.Out, "  Branch/Tag: %s\n", ref)
	if len(inputs) > 0 {
		fmt.Fprintln(ios.Out, "  Inputs:")
		for key, value := range inputs {
			fmt.Fprintf(ios.Out, "    %s: %s\n", key, value)
		}
	}

	return nil
}

func runWorkflowEnable(cmd *cobra.Command, args []string) error {
	cfg, err := config.Load()
	if err != nil {
		return fmt.Errorf("failed to load config: %w", err)
	}

	client, err := api.NewClientFromConfig(cfg, "", getDetectedHost(), getCwd())
	if err != nil {
		return fmt.Errorf("failed to create client: %w", err)
	}

	repo, _ := cmd.Flags().GetString("repo")
	owner, name, err := parseRepo(repo)
	if err != nil {
		return err
	}

	workflowIdentifier := args[0]
	workflow, err := findWorkflow(client, owner, name, workflowIdentifier)
	if err != nil {
		return err
	}

	endpoint := fmt.Sprintf("/api/v1/repos/%s/%s/actions/workflows/%s/enable", owner, name, workflow.Name)

	// Try PUT first (correct method per GitHub/Gitea API spec)
	status, err := client.DoJSON(http.MethodPut, endpoint, nil, nil)
	if err != nil && (status == http.StatusNotFound || status == http.StatusMethodNotAllowed) {
		// Fall back to POST for older versions
		status, err = client.DoJSON(http.MethodPost, endpoint, nil, nil)
	}

	if err != nil {
		if status == http.StatusNotFound && strings.Contains(err.Error(), "404") {
			return fmt.Errorf("failed to enable workflow: this feature requires Forgejo 15.0+ or Gitea 1.24+. " +
				"Your instance does not support the workflow enable/disable API endpoints yet. " +
				"You can enable workflows via the web UI instead")
		}
		return fmt.Errorf("failed to enable workflow: %w", err)
	}

	fmt.Fprintf(ios.Out, "✓ Workflow '%s' enabled\n", workflow.Name)
	return nil
}

func runWorkflowDisable(cmd *cobra.Command, args []string) error {
	cfg, err := config.Load()
	if err != nil {
		return fmt.Errorf("failed to load config: %w", err)
	}

	client, err := api.NewClientFromConfig(cfg, "", getDetectedHost(), getCwd())
	if err != nil {
		return fmt.Errorf("failed to create client: %w", err)
	}

	repo, _ := cmd.Flags().GetString("repo")
	owner, name, err := parseRepo(repo)
	if err != nil {
		return err
	}

	workflowIdentifier := args[0]
	workflow, err := findWorkflow(client, owner, name, workflowIdentifier)
	if err != nil {
		return err
	}

	endpoint := fmt.Sprintf("/api/v1/repos/%s/%s/actions/workflows/%s/disable", owner, name, workflow.Name)

	// Try PUT first (correct method per GitHub/Gitea API spec)
	status, err := client.DoJSON(http.MethodPut, endpoint, nil, nil)
	if err != nil && (status == http.StatusNotFound || status == http.StatusMethodNotAllowed) {
		// Fall back to POST for older versions
		status, err = client.DoJSON(http.MethodPost, endpoint, nil, nil)
	}

	if err != nil {
		if status == http.StatusNotFound && strings.Contains(err.Error(), "404") {
			return fmt.Errorf("failed to disable workflow: this feature requires Forgejo 15.0+ or Gitea 1.24+. " +
				"Your instance does not support the workflow enable/disable API endpoints yet. " +
				"You can disable workflows via the web UI instead")
		}
		return fmt.Errorf("failed to disable workflow: %w", err)
	}

	fmt.Fprintf(ios.Out, "✓ Workflow '%s' disabled\n", workflow.Name)
	return nil
}

func splitKeyValue(s string) []string {
	idx := -1
	for i, c := range s {
		if c == '=' {
			idx = i
			break
		}
	}
	if idx == -1 {
		return []string{s}
	}
	return []string{s[:idx], s[idx+1:]}
}

func findWorkflow(client *api.Client, owner, name, workflowIdentifier string) (*Workflow, error) {
	for _, dir := range []string{".gitea/workflows", ".forgejo/workflows"} {
		endpoint := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", owner, name, dir)

		var contents []ContentsResponse
		if err := client.GetJSON(endpoint, &contents); err != nil {
			continue
		}

		for _, content := range contents {
			if content.Type == "file" && (content.Name == workflowIdentifier || content.Path == workflowIdentifier) {
				return &Workflow{
					Name:  content.Name,
					Path:  content.Path,
					State: "active",
				}, nil
			}
		}
	}

	return nil, fmt.Errorf("workflow '%s' not found", workflowIdentifier)
}

// Secret command implementations

func runActionsSecretList(cmd *cobra.Command, args []string) error {
	cfg, err := config.Load()
	if err != nil {
		return fmt.Errorf("failed to load config: %w", err)
	}

	client, err := api.NewClientFromConfig(cfg, "", getDetectedHost(), getCwd())
	if err != nil {
		return fmt.Errorf("failed to create client: %w", err)
	}

	repo, _ := cmd.Flags().GetString("repo")
	owner, name, err := parseRepo(repo)
	if err != nil {
		return err
	}

	secrets, _, err := client.ListRepoActionSecret(owner, name, gitea.ListRepoActionSecretOption{})
	if err != nil {
		return fmt.Errorf("failed to list secrets: %w", err)
	}

	if len(secrets) == 0 {
		fmt.Fprintln(ios.Out, "No secrets found")
		return nil
	}

	tp := ios.NewTablePrinter()
	tp.AddHeader("NAME", "CREATED")
	for _, secret := range secrets {
		tp.AddRow(secret.Name, secret.Created.Format("2006-01-02 15:04:05"))
	}
	return tp.Render()
}

func runActionsSecretCreate(cmd *cobra.Command, args []string) error {
	cfg, err := config.Load()
	if err != nil {
		return fmt.Errorf("failed to load config: %w", err)
	}

	client, err := api.NewClientFromConfig(cfg, "", getDetectedHost(), getCwd())
	if err != nil {
		return fmt.Errorf("failed to create client: %w", err)
	}

	repo, _ := cmd.Flags().GetString("repo")
	owner, name, err := parseRepo(repo)
	if err != nil {
		return err
	}

	secretName := args[0]

	// Read secret value from stdin
	fmt.Fprint(ios.ErrOut, "Enter secret value: ")
	var secretValue string
	_, err = fmt.Scanln(&secretValue)
	if err != nil {
		return fmt.Errorf("failed to read secret value: %w", err)
	}

	opt := gitea.CreateSecretOption{
		Name: secretName,
		Data: secretValue,
	}

	_, err = client.CreateRepoActionSecret(owner, name, opt)
	if err != nil {
		return fmt.Errorf("failed to create secret: %w", err)
	}

	fmt.Fprintf(ios.Out, "Secret '%s' created successfully\n", secretName)
	return nil
}

func runActionsSecretDelete(cmd *cobra.Command, args []string) error {
	cfg, err := config.Load()
	if err != nil {
		return fmt.Errorf("failed to load config: %w", err)
	}

	client, err := api.NewClientFromConfig(cfg, "", getDetectedHost(), getCwd())
	if err != nil {
		return fmt.Errorf("failed to create client: %w", err)
	}

	repo, _ := cmd.Flags().GetString("repo")
	owner, name, err := parseRepo(repo)
	if err != nil {
		return err
	}

	secretName := args[0]

	_, err = client.DeleteRepoActionSecret(owner, name, secretName)
	if err != nil {
		return fmt.Errorf("failed to delete secret: %w", err)
	}

	fmt.Fprintf(ios.Out, "Secret '%s' deleted successfully\n", secretName)
	return nil
}

// Variable command implementations

// ActionVariable represents a repository action variable from the API
type ActionVariable struct {
	Name  string `json:"name"`
	Value string `json:"data"`
}

func runActionsVariableList(cmd *cobra.Command, args []string) error {
	cfg, err := config.Load()
	if err != nil {
		return fmt.Errorf("failed to load config: %w", err)
	}

	client, err := api.NewClientFromConfig(cfg, "", getDetectedHost(), getCwd())
	if err != nil {
		return fmt.Errorf("failed to create client: %w", err)
	}

	repo, _ := cmd.Flags().GetString("repo")
	owner, name, err := parseRepo(repo)
	if err != nil {
		return err
	}

	endpoint := fmt.Sprintf("/api/v1/repos/%s/%s/actions/variables", owner, name)

	var variables []ActionVariable
	if err := client.GetJSON(endpoint, &variables); err != nil {
		return fmt.Errorf("failed to list variables: %w", err)
	}

	if wantJSON(cmd) {
		return outputJSON(cmd, variables)
	}

	if len(variables) == 0 {
		fmt.Fprintln(ios.Out, "No variables found")
		return nil
	}

	tp := ios.NewTablePrinter()
	tp.AddHeader("NAME", "VALUE")
	for _, v := range variables {
		tp.AddRow(v.Name, v.Value)
	}
	return tp.Render()
}

func runActionsVariableGet(cmd *cobra.Command, args []string) error {
	cfg, err := config.Load()
	if err != nil {
		return fmt.Errorf("failed to load config: %w", err)
	}

	client, err := api.NewClientFromConfig(cfg, "", getDetectedHost(), getCwd())
	if err != nil {
		return fmt.Errorf("failed to create client: %w", err)
	}

	repo, _ := cmd.Flags().GetString("repo")
	owner, name, err := parseRepo(repo)
	if err != nil {
		return err
	}

	variableName := args[0]

	variable, _, err := client.GetRepoActionVariable(owner, name, variableName)
	if err != nil {
		return fmt.Errorf("failed to get variable: %w", err)
	}

	fmt.Fprintf(ios.Out, "%s=%s\n", variable.Name, variable.Value)
	return nil
}

func runActionsVariableCreate(cmd *cobra.Command, args []string) error {
	cfg, err := config.Load()
	if err != nil {
		return fmt.Errorf("failed to load config: %w", err)
	}

	client, err := api.NewClientFromConfig(cfg, "", getDetectedHost(), getCwd())
	if err != nil {
		return fmt.Errorf("failed to create client: %w", err)
	}

	repo, _ := cmd.Flags().GetString("repo")
	owner, name, err := parseRepo(repo)
	if err != nil {
		return err
	}

	variableName := args[0]
	variableValue := args[1]

	_, err = client.CreateRepoActionVariable(owner, name, variableName, variableValue)
	if err != nil {
		return fmt.Errorf("failed to create variable: %w", err)
	}

	fmt.Fprintf(ios.Out, "Variable '%s' created successfully\n", variableName)
	return nil
}

func runActionsVariableUpdate(cmd *cobra.Command, args []string) error {
	cfg, err := config.Load()
	if err != nil {
		return fmt.Errorf("failed to load config: %w", err)
	}

	client, err := api.NewClientFromConfig(cfg, "", getDetectedHost(), getCwd())
	if err != nil {
		return fmt.Errorf("failed to create client: %w", err)
	}

	repo, _ := cmd.Flags().GetString("repo")
	owner, name, err := parseRepo(repo)
	if err != nil {
		return err
	}

	variableName := args[0]
	variableValue := args[1]

	_, err = client.UpdateRepoActionVariable(owner, name, variableName, variableValue)
	if err != nil {
		return fmt.Errorf("failed to update variable: %w", err)
	}

	fmt.Fprintf(ios.Out, "Variable '%s' updated successfully\n", variableName)
	return nil
}

func runActionsVariableDelete(cmd *cobra.Command, args []string) error {
	cfg, err := config.Load()
	if err != nil {
		return fmt.Errorf("failed to load config: %w", err)
	}

	client, err := api.NewClientFromConfig(cfg, "", getDetectedHost(), getCwd())
	if err != nil {
		return fmt.Errorf("failed to create client: %w", err)
	}

	repo, _ := cmd.Flags().GetString("repo")
	owner, name, err := parseRepo(repo)
	if err != nil {
		return err
	}

	variableName := args[0]

	_, err = client.DeleteRepoActionVariable(owner, name, variableName)
	if err != nil {
		return fmt.Errorf("failed to delete variable: %w", err)
	}

	fmt.Fprintf(ios.Out, "Variable '%s' deleted successfully\n", variableName)
	return nil
}
