package server

import (
	"archive/tar"
	"compress/gzip"
	"context"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"testing"
	"time"

	agsconfig "github.com/pingcap/agent-git-service/config"
	agsserver "github.com/pingcap/agent-git-service/server"
	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/config"
	"github.com/tidbcloud/AgentTeam/internal/service/automation"
	"github.com/tidbcloud/AgentTeam/internal/service/automation/runtime"
	automationstore "github.com/tidbcloud/AgentTeam/internal/store/automation"
)

func TestServerManagedRunnerCompletesAgentTeamCheckoutAutomation(t *testing.T) {
	automationYAML := "name: Checkout smoke\non:\n  manual: {}\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: agentteam/checkout\n      - run: test -f go.mod\n"
	commitSHA := "5656565656565656565656565656565656565656"
	srv := newAutomationRunnerCheckoutTestServer(t, automationYAML, commitSHA)
	auth := "Bearer " + jwt(map[string]any{"sub": "checkout-user"})
	workspaceID := "ws-automation-checkout"

	run := startAutomationRunnerTestRun(t, srv, auth, workspaceID, ".agentteam/runner.yml")

	require.Eventually(t, func() bool {
		var stored automationstore.AutomationRun
		err := srv.meshxDB.Where("workspace_id = ? AND id = ?", workspaceID, run.ID).First(&stored).Error
		return err == nil && stored.Status == automationstore.AutomationRunStatusSucceeded
	}, 3*time.Second, 20*time.Millisecond)
	var events []automationstore.AutomationEvent
	require.NoError(t, srv.meshxDB.Where("workspace_id = ? AND run_id = ?", workspaceID, run.ID).Find(&events).Error)
	requireServerAutomationEventTypes(t, events, "checkout_started", "checkout_succeeded")
}

func newAutomationRunnerCheckoutTestServer(t *testing.T, automationYAML string, commitSHA string) *Server {
	t.Helper()
	srv, err := New(config.ResourceRuntimeConfig{
		HTTPAddress: ":0",
		Auth:        config.ResourceRuntimeAuthConfig{Enabled: true, Header: "Authorization"},
		Database:    config.ResourceDatabaseConfig{DSN: meshxDatabaseDSN(t)},
		AGS:         config.AGSConfig{DatabaseDSN: prepareResolverDB(t), GitRepoDir: t.TempDir(), BaseURL: "http://" + actionsRunnerHost},
		Automation:  config.AutomationConfig{ServerRunnerEnabled: true, CheckoutMaxBytes: 1024, CheckoutMaxFiles: 10, CheckoutTimeout: time.Minute},
	}, Options{AutomationAuthorizer: automation.AllowAutomationAuthorizer{}, AutomationJobExecutor: checkoutAutomationTestExecutor{}, NewAGS: func(agsconfig.Config, ...agsserver.Option) (agsServer, error) {
		agsHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			switch {
			case r.URL.Path == "/api/v3/repos/pingcap/agentteam/git/refs/heads/main":
				_ = json.NewEncoder(w).Encode(map[string]any{"ref": "refs/heads/main", "object": map[string]any{"sha": commitSHA, "type": "commit"}})
			case r.URL.Path == "/api/v3/repos/pingcap/agentteam/commits/"+commitSHA:
				_ = json.NewEncoder(w).Encode(map[string]any{"sha": commitSHA})
			case r.URL.Path == "/api/v3/repos/pingcap/agentteam/contents/.agentteam/runner.yml" && r.URL.Query().Get("ref") == commitSHA:
				_ = json.NewEncoder(w).Encode(map[string]any{"type": "file", "encoding": "base64", "content": base64.StdEncoding.EncodeToString([]byte(automationYAML))})
			default:
				http.NotFound(w, r)
			}
		})
		return checkoutFakeAGS{handler: agsHandler, commitSHA: commitSHA}, nil
	}})
	require.NoError(t, err)
	t.Cleanup(func() { require.NoError(t, srv.Shutdown(context.Background())) })
	return srv
}

type checkoutAutomationTestExecutor struct{}

func (checkoutAutomationTestExecutor) ExecuteJob(ctx context.Context, req runtime.ExecuteJobRequest) (runtime.ExecuteJobResult, error) {
	result := runtime.ExecuteJobResult{Steps: make([]runtime.JobStepResult, 0, len(req.Spec.Steps))}
	for idx, step := range req.Spec.Steps {
		started := req.Now().Format(time.RFC3339Nano)
		exitCode := 0
		var err error
		if strings.TrimSpace(step.Uses) != "" {
			exitCode, err = req.UsesStepHandler(ctx, runtime.UsesStepRequest{Step: step, Index: idx})
		} else if _, statErr := os.Stat(filepath.Join(req.WorkDir, "go.mod")); statErr != nil {
			exitCode = 1
			err = statErr
		}
		result.Steps = append(result.Steps, runtime.JobStepResult{
			Index:       idx,
			ExitCode:    exitCode,
			StartedAt:   started,
			CompletedAt: req.Now().Format(time.RFC3339Nano),
		})
		if err != nil {
			return result, err
		}
	}
	return result, nil
}

type checkoutFakeAGS struct {
	handler   http.Handler
	commitSHA string
}

func (f checkoutFakeAGS) Handler() http.Handler          { return f.handler }
func (f checkoutFakeAGS) Shutdown(context.Context) error { return nil }
func (f checkoutFakeAGS) ArchiveRepository(ctx context.Context, request agsserver.RepositoryArchiveRequest) error {
	if request.RepositoryFullName != "pingcap/agentteam" {
		return fmt.Errorf("archive repository = %q, want pingcap/agentteam", request.RepositoryFullName)
	}
	if request.CommitSHA != f.commitSHA {
		return fmt.Errorf("archive commit SHA = %q, want %s", request.CommitSHA, f.commitSHA)
	}
	gzipWriter := gzip.NewWriter(request.Writer)
	tarWriter := tar.NewWriter(gzipWriter)
	payload := []byte("module checkout.smoke\n")
	if err := tarWriter.WriteHeader(&tar.Header{Name: "go.mod", Mode: 0o644, Size: int64(len(payload)), Typeflag: tar.TypeReg}); err != nil {
		return err
	}
	if _, err := tarWriter.Write(payload); err != nil {
		return err
	}
	if err := tarWriter.Close(); err != nil {
		return err
	}
	return gzipWriter.Close()
}

func requireServerAutomationEventTypes(t *testing.T, events []automationstore.AutomationEvent, want ...string) {
	t.Helper()
	seen := map[string]struct{}{}
	for _, event := range events {
		seen[event.EventType] = struct{}{}
	}
	for _, eventType := range want {
		if _, ok := seen[eventType]; !ok {
			t.Fatalf("missing automation event %q in %#v", eventType, events)
		}
	}
}
