package workspace

import (
	"context"
	"log/slog"
	"sync"
	"time"

	"github.com/google/uuid"

	"github.com/yan1h/agent-coding-workflow/internal/audit"
	"github.com/yan1h/agent-coding-workflow/internal/infra/git"
)

// CloneRunner 用 goroutine 异步跑 git clone。Schedule 是非阻塞的：
// 调用方立即返回，clone 进度通过 sync_status 列轮询。
//
// Plan #5（Job Runner）实现后会替换 Schedule 内部为 job dispatcher 入队，
// 调用点不需要改动。
type CloneRunner struct {
	repo    Repository
	rec     audit.Recorder
	gitr    git.Runner
	log     *slog.Logger
	timeout time.Duration

	wg          sync.WaitGroup
	resolveCred func(ctx context.Context, wsID uuid.UUID) (*git.Credential, error)
}

// NewCloneRunner 构造 CloneRunner。timeout==0 时使用默认 30 分钟超时。
func NewCloneRunner(
	repo Repository, rec audit.Recorder, gitr git.Runner, log *slog.Logger,
	resolveCred func(context.Context, uuid.UUID) (*git.Credential, error),
	timeout time.Duration,
) *CloneRunner {
	if timeout == 0 {
		timeout = 30 * time.Minute
	}
	return &CloneRunner{
		repo: repo, rec: rec, gitr: gitr, log: log,
		timeout: timeout, resolveCred: resolveCred,
	}
}

// Schedule 非阻塞地排程一次 clone；调用方立即返回。
func (r *CloneRunner) Schedule(wsID uuid.UUID) {
	r.wg.Add(1)
	go func() {
		defer r.wg.Done()
		ctx, cancel := context.WithTimeout(context.Background(), r.timeout)
		defer cancel()
		r.run(ctx, wsID)
	}()
}

// Wait 阻塞直到所有已排程的 clone 任务完成；测试与 graceful shutdown 用。
func (r *CloneRunner) Wait() { r.wg.Wait() }

func (r *CloneRunner) run(ctx context.Context, wsID uuid.UUID) {
	ws, err := r.repo.GetWorkspaceByID(ctx, wsID)
	if err != nil {
		r.log.Error("clone runner: get workspace", "ws_id", wsID, "err", err.Error())
		return
	}
	// 状态/审计写操作与 clone 的超时 ctx 解耦：clone 超时后 ctx 已 DeadlineExceeded，
	// 仍需把状态写成 error，否则永久卡在 cloning。
	writeCtx := context.WithoutCancel(ctx)
	cred, err := r.resolveCred(ctx, wsID)
	if err != nil {
		r.markError(writeCtx, wsID, "resolve credential: "+err.Error())
		return
	}
	if cloneErr := r.gitr.Clone(ctx, ws.MainPath, ws.GitRemoteURL, ws.DefaultBranch, cred); cloneErr != nil {
		r.markError(writeCtx, wsID, summarizeErr(cloneErr))
		_ = r.rec.Record(writeCtx, audit.Entry{Action: "workspace.clone_failed", TargetType: "workspace", TargetID: wsID.String(), Metadata: map[string]any{"err": summarizeErr(cloneErr)}})
		return
	}
	now := time.Now().UTC()
	if _, err := r.repo.UpdateWorkspaceSyncStatus(writeCtx, wsID, SyncStatusIdle, &now, ""); err != nil {
		r.log.Error("clone runner: update sync status idle", "ws_id", wsID, "err", err.Error())
		return
	}
	_ = r.rec.Record(writeCtx, audit.Entry{Action: "workspace.clone_ok", TargetType: "workspace", TargetID: wsID.String()})
}

func (r *CloneRunner) markError(ctx context.Context, wsID uuid.UUID, msg string) {
	if _, err := r.repo.UpdateWorkspaceSyncStatus(ctx, wsID, SyncStatusError, nil, msg); err != nil {
		r.log.Error("clone runner: update sync status error", "ws_id", wsID, "err", err.Error())
	}
}
