// Copyright 2025 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package gitea

import (
	"fmt"
	"net/url"
	"time"
)

// ActionTask represents a workflow run task (from /actions/tasks endpoint)
// This is the format returned by older Gitea versions
type ActionTask struct {
	ID           int64     `json:"id"`
	Name         string    `json:"name"` // Workflow 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"` // PR title or commit message
	Status       string    `json:"status"`
	WorkflowID   string    `json:"workflow_id"` // e.g. "ci.yml"
	URL          string    `json:"url"`
	CreatedAt    time.Time `json:"created_at"`
	UpdatedAt    time.Time `json:"updated_at"`
	RunStartedAt time.Time `json:"run_started_at"`
}

// ActionTaskResponse holds the response for listing action tasks
type ActionTaskResponse struct {
	TotalCount   int64         `json:"total_count"`
	WorkflowRuns []*ActionTask `json:"workflow_runs"`
}

// ActionWorkflowRun represents a workflow run (from /actions/runs endpoint)
// This is the format returned by newer Gitea versions
type ActionWorkflowRun struct {
	ID             int64       `json:"id"`
	DisplayTitle   string      `json:"display_title"`
	Event          string      `json:"event"`
	HeadBranch     string      `json:"head_branch,omitempty"`
	HeadSha        string      `json:"head_sha"`
	Path           string      `json:"path"`
	RunAttempt     int64       `json:"run_attempt"`
	RunNumber      int64       `json:"run_number"`
	Status         string      `json:"status"`
	Conclusion     string      `json:"conclusion,omitempty"`
	URL            string      `json:"url"`
	HTMLURL        string      `json:"html_url"`
	StartedAt      time.Time   `json:"started_at"`
	CompletedAt    time.Time   `json:"completed_at"`
	Actor          *User       `json:"actor,omitempty"`
	TriggerActor   *User       `json:"trigger_actor,omitempty"`
	Repository     *Repository `json:"repository,omitempty"`
	HeadRepository *Repository `json:"head_repository,omitempty"`
	RepositoryID   int64       `json:"repository_id,omitempty"`
}

// ActionWorkflowRunsResponse holds the response for listing workflow runs
type ActionWorkflowRunsResponse struct {
	TotalCount   int64                `json:"total_count"`
	WorkflowRuns []*ActionWorkflowRun `json:"workflow_runs"`
}

// ActionWorkflowJob represents a job within a workflow run
type ActionWorkflowJob struct {
	ID          int64                 `json:"id"`
	RunID       int64                 `json:"run_id"`
	RunURL      string                `json:"run_url"`
	RunAttempt  int64                 `json:"run_attempt"`
	Name        string                `json:"name"`
	HeadBranch  string                `json:"head_branch,omitempty"`
	HeadSha     string                `json:"head_sha"`
	Status      string                `json:"status"`
	Conclusion  string                `json:"conclusion,omitempty"`
	URL         string                `json:"url"`
	HTMLURL     string                `json:"html_url"`
	CreatedAt   time.Time             `json:"created_at"`
	StartedAt   time.Time             `json:"started_at"`
	CompletedAt time.Time             `json:"completed_at"`
	RunnerID    int64                 `json:"runner_id,omitempty"`
	RunnerName  string                `json:"runner_name,omitempty"`
	Labels      []string              `json:"labels"`
	Steps       []*ActionWorkflowStep `json:"steps"`
}

// ActionWorkflowJobsResponse holds the response for listing workflow jobs
type ActionWorkflowJobsResponse struct {
	TotalCount int64                `json:"total_count"`
	Jobs       []*ActionWorkflowJob `json:"jobs"`
}

// ActionWorkflowStep represents a step within a job
type ActionWorkflowStep struct {
	Name        string    `json:"name"`
	Number      int64     `json:"number"`
	Status      string    `json:"status"`
	Conclusion  string    `json:"conclusion,omitempty"`
	StartedAt   time.Time `json:"started_at"`
	CompletedAt time.Time `json:"completed_at"`
}

// ListRepoActionRunsOptions options for listing repository action runs
type ListRepoActionRunsOptions struct {
	ListOptions
	Branch  string // Filter by branch
	Event   string // Filter by triggering event
	Status  string // Filter by status (pending, queued, in_progress, failure, success, skipped)
	Actor   string // Filter by actor (user who triggered the run)
	HeadSHA string // Filter by the SHA of the head commit
}

// QueryEncode encodes the options to URL query parameters
func (opt *ListRepoActionRunsOptions) QueryEncode() string {
	query := opt.getURLQuery()
	if opt.Branch != "" {
		query.Add("branch", opt.Branch)
	}
	if opt.Event != "" {
		query.Add("event", opt.Event)
	}
	if opt.Status != "" {
		query.Add("status", opt.Status)
	}
	if opt.Actor != "" {
		query.Add("actor", opt.Actor)
	}
	if opt.HeadSHA != "" {
		query.Add("head_sha", opt.HeadSHA)
	}
	return query.Encode()
}

// ListRepoActionJobsOptions options for listing repository action jobs
type ListRepoActionJobsOptions struct {
	ListOptions
	Status string // Filter by status (pending, queued, in_progress, failure, success, skipped)
}

// QueryEncode encodes the options to URL query parameters
func (opt *ListRepoActionJobsOptions) QueryEncode() string {
	query := opt.getURLQuery()
	if opt.Status != "" {
		query.Add("status", opt.Status)
	}
	return query.Encode()
}

// ListRepoActionRuns lists workflow runs for a repository.
// Requires Gitea 1.26.0 or later. For older versions, use ListRepoActionTasks.
func (c *Client) ListRepoActionRuns(owner, repo string, opt ListRepoActionRunsOptions) (*ActionWorkflowRunsResponse, *Response, error) {
	if err := c.checkServerVersionGreaterThanOrEqual(version1_26_0); err != nil {
		return nil, nil, err
	}
	if err := escapeValidatePathSegments(&owner, &repo); err != nil {
		return nil, nil, err
	}
	opt.setDefaults()

	link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/actions/runs", owner, repo))
	link.RawQuery = opt.QueryEncode()

	resp := new(ActionWorkflowRunsResponse)
	response, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, resp)
	return resp, response, err
}

// GetRepoActionRun gets a single workflow run.
// Requires Gitea 1.26.0 or later.
func (c *Client) GetRepoActionRun(owner, repo string, runID int64) (*ActionWorkflowRun, *Response, error) {
	if err := c.checkServerVersionGreaterThanOrEqual(version1_26_0); err != nil {
		return nil, nil, err
	}
	if err := escapeValidatePathSegments(&owner, &repo); err != nil {
		return nil, nil, err
	}

	run := new(ActionWorkflowRun)
	resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/actions/runs/%d", owner, repo, runID), jsonHeader, nil, run)
	return run, resp, err
}

// ListRepoActionRunJobs lists jobs for a workflow run.
// Requires Gitea 1.26.0 or later.
func (c *Client) ListRepoActionRunJobs(owner, repo string, runID int64, opt ListRepoActionJobsOptions) (*ActionWorkflowJobsResponse, *Response, error) {
	if err := c.checkServerVersionGreaterThanOrEqual(version1_26_0); err != nil {
		return nil, nil, err
	}
	if err := escapeValidatePathSegments(&owner, &repo); err != nil {
		return nil, nil, err
	}
	opt.setDefaults()

	link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/actions/runs/%d/jobs", owner, repo, runID))
	link.RawQuery = opt.QueryEncode()

	resp := new(ActionWorkflowJobsResponse)
	response, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, resp)
	return resp, response, err
}

// ListRepoActionJobs lists all jobs for a repository.
// Requires Gitea 1.26.0 or later.
func (c *Client) ListRepoActionJobs(owner, repo string, opt ListRepoActionJobsOptions) (*ActionWorkflowJobsResponse, *Response, error) {
	if err := c.checkServerVersionGreaterThanOrEqual(version1_26_0); err != nil {
		return nil, nil, err
	}
	if err := escapeValidatePathSegments(&owner, &repo); err != nil {
		return nil, nil, err
	}
	opt.setDefaults()

	link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/actions/jobs", owner, repo))
	link.RawQuery = opt.QueryEncode()

	resp := new(ActionWorkflowJobsResponse)
	response, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, resp)
	return resp, response, err
}

// GetRepoActionJob gets a single job.
// Requires Gitea 1.26.0 or later.
func (c *Client) GetRepoActionJob(owner, repo string, jobID int64) (*ActionWorkflowJob, *Response, error) {
	if err := c.checkServerVersionGreaterThanOrEqual(version1_26_0); err != nil {
		return nil, nil, err
	}
	if err := escapeValidatePathSegments(&owner, &repo); err != nil {
		return nil, nil, err
	}

	job := new(ActionWorkflowJob)
	resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/actions/jobs/%d", owner, repo, jobID), jsonHeader, nil, job)
	return job, resp, err
}

// GetRepoActionJobLogs gets the logs for a specific job.
// Requires Gitea 1.26.0 or later.
func (c *Client) GetRepoActionJobLogs(owner, repo string, jobID int64) ([]byte, *Response, error) {
	if err := c.checkServerVersionGreaterThanOrEqual(version1_26_0); err != nil {
		return nil, nil, err
	}
	if err := escapeValidatePathSegments(&owner, &repo); err != nil {
		return nil, nil, err
	}

	return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/actions/jobs/%d/logs", owner, repo, jobID), nil, nil)
}

// ListRepoActionTasks lists workflow tasks for a repository (Gitea 1.24.x and earlier)
// Use this for older Gitea versions that don't have /actions/runs endpoint
func (c *Client) ListRepoActionTasks(owner, repo string, opt ListOptions) (*ActionTaskResponse, *Response, error) {
	if err := escapeValidatePathSegments(&owner, &repo); err != nil {
		return nil, nil, err
	}
	opt.setDefaults()

	link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/actions/tasks", owner, repo))
	link.RawQuery = opt.getURLQuery().Encode()

	resp := new(ActionTaskResponse)
	response, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, resp)
	return resp, response, err
}

// DeleteRepoActionRun deletes a workflow run.
// Requires Gitea 1.26.0 or later.
func (c *Client) DeleteRepoActionRun(owner, repo string, runID int64) (*Response, error) {
	if err := c.checkServerVersionGreaterThanOrEqual(version1_26_0); err != nil {
		return nil, err
	}
	if err := escapeValidatePathSegments(&owner, &repo); err != nil {
		return nil, err
	}

	return c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/repos/%s/%s/actions/runs/%d", owner, repo, runID), jsonHeader, nil)
}

// RerunRepoActionRun reruns an entire workflow run.
// Requires Gitea 1.26.0 or later.
func (c *Client) RerunRepoActionRun(owner, repo string, runID int64) (*ActionWorkflowRun, *Response, error) {
	if err := c.checkServerVersionGreaterThanOrEqual(version1_26_0); err != nil {
		return nil, nil, err
	}
	if err := escapeValidatePathSegments(&owner, &repo); err != nil {
		return nil, nil, err
	}

	run := new(ActionWorkflowRun)
	resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/actions/runs/%d/rerun", owner, repo, runID), jsonHeader, nil, run)
	return run, resp, err
}

// RerunRepoActionRunFailedJobs reruns all failed jobs in a workflow run.
// Requires Gitea 1.26.0 or later.
func (c *Client) RerunRepoActionRunFailedJobs(owner, repo string, runID int64) (*Response, error) {
	if err := c.checkServerVersionGreaterThanOrEqual(version1_26_0); err != nil {
		return nil, err
	}
	if err := escapeValidatePathSegments(&owner, &repo); err != nil {
		return nil, err
	}

	return c.doRequestWithStatusHandle("POST", fmt.Sprintf("/repos/%s/%s/actions/runs/%d/rerun-failed-jobs", owner, repo, runID), jsonHeader, nil)
}

// RerunRepoActionJob reruns a specific workflow job in a run.
// Requires Gitea 1.26.0 or later.
func (c *Client) RerunRepoActionJob(owner, repo string, runID, jobID int64) (*ActionWorkflowJob, *Response, error) {
	if err := c.checkServerVersionGreaterThanOrEqual(version1_26_0); err != nil {
		return nil, nil, err
	}
	if err := escapeValidatePathSegments(&owner, &repo); err != nil {
		return nil, nil, err
	}

	job := new(ActionWorkflowJob)
	resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/actions/runs/%d/jobs/%d/rerun", owner, repo, runID, jobID), jsonHeader, nil, job)
	return job, resp, err
}
