// tinqs/ci orchestrator — executor Lambda
//
// Handles deploy-only jobs directly in Lambda (no Fargate needed).
// Parses workflow YAML, runs shell steps, reports commit status to Gitea.
// 15 min timeout, 2 GB memory, 5 GB /tmp.

package main

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"os/exec"
	"time"

	"github.com/aws/aws-lambda-go/lambda"
	"gopkg.in/yaml.v3"
)

type ExecPayload struct {
	Repo         GiteaRepo `json:"repo"`
	Ref          string    `json:"ref"`
	CommitSHA    string    `json:"commit_sha"`
	Pusher       string    `json:"pusher"`
	Workflow     string    `json:"workflow_name"`
	WorkflowYAML string   `json:"workflow_yaml"`
	GiteaURL     string    `json:"gitea_url"`
	GiteaToken   string    `json:"gitea_token"`
}

type GiteaRepo struct {
	FullName string `json:"full_name"`
	CloneURL string `json:"clone_url"`
}

type Workflow struct {
	Name string                    `yaml:"name"`
	Jobs map[string]WorkflowJob    `yaml:"jobs"`
}

type WorkflowJob struct {
	Steps []WorkflowStep    `yaml:"steps"`
	Env   map[string]string `yaml:"env"`
}

type WorkflowStep struct {
	Name string            `yaml:"name"`
	Run  string            `yaml:"run"`
	Uses string            `yaml:"uses"`
	Env  map[string]string `yaml:"env"`
}

type StepResult struct {
	Name     string `json:"name"`
	Status   string `json:"status"`
	Duration string `json:"duration"`
}

type JobResult struct {
	Workflow string       `json:"workflow"`
	Status   string       `json:"status"`
	Steps    []StepResult `json:"steps"`
	Duration string       `json:"duration"`
}

func handler(ctx context.Context, payload ExecPayload) (JobResult, error) {
	sha := payload.CommitSHA
	if len(sha) > 7 {
		sha = sha[:7]
	}
	log.Printf("Executor: repo=%s workflow=%s sha=%s", payload.Repo.FullName, payload.Workflow, sha)

	start := time.Now()

	var wf Workflow
	if err := yaml.Unmarshal([]byte(payload.WorkflowYAML), &wf); err != nil {
		return JobResult{Workflow: payload.Workflow, Status: "failure"}, err
	}

	setCommitStatus(payload, "pending", "CI running...")

	overallStatus := "success"
	var steps []StepResult

	for _, job := range wf.Jobs {
		// Build environment
		env := os.Environ()
		env = append(env,
			"CI=true",
			fmt.Sprintf("GITHUB_REPOSITORY=%s", payload.Repo.FullName),
			fmt.Sprintf("GITHUB_REF=%s", payload.Ref),
			fmt.Sprintf("GITHUB_SHA=%s", payload.CommitSHA),
		)
		for k, v := range job.Env {
			env = append(env, fmt.Sprintf("%s=%s", k, v))
		}

		workdir := "/tmp/workspace"
		os.MkdirAll(workdir, 0755)
		os.RemoveAll(workdir)
		os.MkdirAll(workdir, 0755)

		for i, step := range job.Steps {
			stepStart := time.Now()
			name := step.Name
			if name == "" {
				name = fmt.Sprintf("Step %d", i+1)
			}

			// Handle checkout action
			if step.Uses != "" && contains(step.Uses, "checkout") {
				cloneURL := payload.Repo.CloneURL
				if payload.GiteaToken != "" {
					cloneURL = fmt.Sprintf("https://token:%s@%s",
						payload.GiteaToken, cloneURL[8:]) // strip https://
				}
				branch := payload.Ref
				if len(branch) > 11 && branch[:11] == "refs/heads/" {
					branch = branch[11:]
				}
				cmd := exec.CommandContext(ctx, "git", "clone", "--depth=1", "--branch", branch, cloneURL, workdir)
				if out, err := cmd.CombinedOutput(); err != nil {
					log.Printf("Checkout failed: %s", string(out))
					steps = append(steps, StepResult{Name: name, Status: "failure", Duration: since(stepStart)})
					overallStatus = "failure"
					break
				}
				steps = append(steps, StepResult{Name: name, Status: "success", Duration: since(stepStart)})
				continue
			}

			// Skip unknown actions
			if step.Uses != "" {
				steps = append(steps, StepResult{Name: name, Status: "skipped", Duration: since(stepStart)})
				continue
			}

			if step.Run == "" {
				continue
			}

			// Run shell step
			stepEnv := make([]string, len(env))
			copy(stepEnv, env)
			for k, v := range step.Env {
				stepEnv = append(stepEnv, fmt.Sprintf("%s=%s", k, v))
			}

			cmd := exec.CommandContext(ctx, "bash", "-c", step.Run)
			cmd.Dir = workdir
			cmd.Env = stepEnv

			var output bytes.Buffer
			cmd.Stdout = io.MultiWriter(&output, os.Stdout)
			cmd.Stderr = io.MultiWriter(&output, os.Stderr)

			if err := cmd.Run(); err != nil {
				log.Printf("Step %d FAILED: %v", i+1, err)
				steps = append(steps, StepResult{Name: name, Status: "failure", Duration: since(stepStart)})
				overallStatus = "failure"
				break
			}

			steps = append(steps, StepResult{Name: name, Status: "success", Duration: since(stepStart)})
		}

		if overallStatus == "failure" {
			break
		}
	}

	desc := fmt.Sprintf("CI %s in %s", overallStatus, time.Since(start).Round(time.Second))
	setCommitStatus(payload, overallStatus, desc)

	return JobResult{
		Workflow: payload.Workflow,
		Status:   overallStatus,
		Steps:    steps,
		Duration: time.Since(start).Round(time.Second).String(),
	}, nil
}

func setCommitStatus(payload ExecPayload, state, description string) {
	if payload.GiteaURL == "" || payload.GiteaToken == "" {
		return
	}

	url := fmt.Sprintf("%s/api/v1/repos/%s/statuses/%s",
		payload.GiteaURL, payload.Repo.FullName, payload.CommitSHA)

	body, _ := json.Marshal(map[string]string{
		"state":       state,
		"description": description,
		"context":     fmt.Sprintf("ci/%s", payload.Workflow),
	})

	req, _ := http.NewRequest("POST", url, bytes.NewReader(body))
	req.Header.Set("Authorization", "token "+payload.GiteaToken)
	req.Header.Set("Content-Type", "application/json")

	resp, err := (&http.Client{Timeout: 10 * time.Second}).Do(req)
	if err != nil {
		log.Printf("Failed to set commit status: %v", err)
		return
	}
	resp.Body.Close()
}

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

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

func since(t time.Time) string {
	return time.Since(t).Round(time.Millisecond).String()
}

func main() {
	lambda.Start(handler)
}
