package runners

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

	"github.com/google/uuid"

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

// WorktreeRow projects the worktree fields needed for prune. OwnerID and
// MainPath come from a JOIN performed in the production adapter (workspace
// + project), not from the worktrees table directly.
type WorktreeRow struct {
	ID          uuid.UUID
	WorkspaceID uuid.UUID
	OwnerID     uuid.UUID
	Branch      string
	MainPath    string
	Path        string
}

// WorktreePruneRepo is the minimal repository surface used by this runner.
type WorktreePruneRepo interface {
	ListWorktreesNeedingPruneWarning(ctx context.Context, lastUsedBefore time.Time) ([]WorktreeRow, error)
	ListWorktreesNeedingPrune(ctx context.Context, lastUsedBefore time.Time) ([]WorktreeRow, error)
	MarkPruneWarning(ctx context.Context, id uuid.UUID) error
	TryStartPrune(ctx context.Context, id uuid.UUID) (WorktreeRow, error)
	FinishPruneSuccess(ctx context.Context, id uuid.UUID) error
	FinishPruneRollback(ctx context.Context, id uuid.UUID) error
}

// WorktreePruner abstracts the git CLI surface needed.
type WorktreePruner interface {
	WorktreeRemove(ctx context.Context, mainPath, worktreePath string) error
}

// WorktreePruneConfig matches jobs.WorktreePruneConfig minus enabled/interval
// (managed by scheduler).
type WorktreePruneConfig struct {
	IdleThreshold time.Duration
	WarningLead   time.Duration
}

// WorktreePrune is the B runner.
type WorktreePrune struct {
	repo  WorktreePruneRepo
	gitr  WorktreePruner
	disp  Dispatcher
	cfg   WorktreePruneConfig
	log   *slog.Logger
	audit audit.Recorder
}

// NewWorktreePrune constructs the runner. rec is optional; when non-nil the
// runner emits a worktree.pruned audit row after each successful prune.
func NewWorktreePrune(repo WorktreePruneRepo, gitr WorktreePruner, disp Dispatcher, cfg WorktreePruneConfig, log *slog.Logger, rec audit.Recorder) *WorktreePrune {
	if log == nil {
		log = slog.Default()
	}
	return &WorktreePrune{repo: repo, gitr: gitr, disp: disp, cfg: cfg, log: log, audit: rec}
}

// Run executes warning phase then execution phase.
func (r *WorktreePrune) Run(ctx context.Context) {
	r.warningPhase(ctx)
	r.executionPhase(ctx)
}

func (r *WorktreePrune) warningPhase(ctx context.Context) {
	cutoff := time.Now().Add(-(r.cfg.IdleThreshold - r.cfg.WarningLead))
	rows, err := r.repo.ListWorktreesNeedingPruneWarning(ctx, cutoff)
	if err != nil {
		r.log.Error("worktree_prune.list_warning", "err", err.Error())
		return
	}
	for _, w := range rows {
		if err := r.repo.MarkPruneWarning(ctx, w.ID); err != nil {
			r.log.Error("worktree_prune.mark_warning", "worktree_id", w.ID, "err", err.Error())
			continue
		}
		if derr := r.disp.Dispatch(ctx, notify.Message{
			UserID:   w.OwnerID,
			Topic:    "worktree.prune_pending",
			Severity: notify.SeverityWarning,
			Title:    "Worktree will be pruned soon",
			Body:     "Branch " + w.Branch + " has been idle and will be removed in 3 days unless used.",
			Metadata: map[string]any{
				"worktree_id": w.ID.String(),
				"branch":      w.Branch,
			},
			CreatedAt: time.Now().UTC(),
		}); derr != nil {
			r.log.Error("worktree_prune.dispatch", "worktree_id", w.ID, "err", derr.Error())
		}
	}
}

func (r *WorktreePrune) executionPhase(ctx context.Context) {
	cutoff := time.Now().Add(-r.cfg.IdleThreshold)
	rows, err := r.repo.ListWorktreesNeedingPrune(ctx, cutoff)
	if err != nil {
		r.log.Error("worktree_prune.list_prune", "err", err.Error())
		return
	}
	for _, w := range rows {
		started, err := r.repo.TryStartPrune(ctx, w.ID)
		if err != nil {
			// CAS lost (idle → other status), skip
			continue
		}
		if rmErr := r.gitr.WorktreeRemove(ctx, started.MainPath, started.Path); rmErr != nil {
			r.log.Error("worktree_prune.remove_failed", "worktree_id", w.ID, "err", rmErr.Error())
			if rbErr := r.repo.FinishPruneRollback(ctx, w.ID); rbErr != nil {
				r.log.Error("worktree_prune.rollback", "worktree_id", w.ID, "err", rbErr.Error())
			}
			continue
		}
		if err := r.repo.FinishPruneSuccess(ctx, w.ID); err != nil {
			r.log.Error("worktree_prune.finish_success", "worktree_id", w.ID, "err", err.Error())
			continue
		}
		if r.audit != nil {
			if rerr := r.audit.Record(ctx, audit.Entry{
				Action:     "worktree.pruned",
				TargetType: "worktree",
				TargetID:   w.ID.String(),
				Metadata: map[string]any{
					"workspace_id": w.WorkspaceID.String(),
					"branch":       w.Branch,
					"path":         w.Path,
				},
			}); rerr != nil {
				r.log.Warn("worktree_prune.audit_failed", "worktree_id", w.ID, "err", rerr.Error())
			}
		}
	}
}
