package docs

import (
	"context"
	"encoding/json"
	"fmt"
	"log/slog"
	"strings"

	"github.com/google/uuid"

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

// maxDiffChars bounds the diffstat fed to the LLM so a huge commit can't blow the
// prompt budget.
const maxDiffChars = 12000

// WorkspaceLocator resolves the on-disk directory holding a commit's files.
type WorkspaceLocator interface {
	LocateWorkspace(ctx context.Context, wsID uuid.UUID) (mainPath string, err error)
	LocateWorktree(ctx context.Context, worktreeID uuid.UUID) (path string, err error)
}

// Summarizer turns a diffstat into a markdown summary. Implemented in app.go over
// the llm registry (default chat endpoint); the SummaryRunner only sees this
// narrow contract so tests can inject a canned-output fake.
type Summarizer interface {
	// Summarize returns (title, bodyMarkdown, modelID, error).
	Summarize(ctx context.Context, branch, commitSHA, diff string) (title, bodyMD, model string, err error)
}

// SummaryRunner is the jobs.Handler that generates a commit_summaries row. It is
// event-driven (enqueued post-commit), never a periodic ticker.
type SummaryRunner struct {
	repo    Repository
	locator WorkspaceLocator
	gitr    git.Runner
	summ    Summarizer
	log     *slog.Logger
}

// NewSummaryRunner constructs a SummaryRunner. log may be nil.
func NewSummaryRunner(repo Repository, locator WorkspaceLocator, gitr git.Runner, summ Summarizer, log *slog.Logger) *SummaryRunner {
	if log == nil {
		log = slog.Default()
	}
	return &SummaryRunner{repo: repo, locator: locator, gitr: gitr, summ: summ, log: log}
}

var _ jobs.Handler = (*SummaryRunner)(nil)

// Type returns the job type this handler processes.
func (h *SummaryRunner) Type() jobs.JobType { return JobTypeCommitSummary }

// Handle generates and upserts a commit summary. Idempotent: a completed summary
// for the same (workspace, commit, kind) is left untouched.
func (h *SummaryRunner) Handle(ctx context.Context, job jobs.Job) error {
	var p summaryPayload
	if err := json.Unmarshal(job.Payload, &p); err != nil {
		return jobs.Permanent(fmt.Errorf("docs.commit_summary: bad payload: %w", err))
	}

	// Skip if a completed summary already exists (idempotent re-enqueue).
	if existing, err := h.repo.Get(ctx, p.WorkspaceID, p.CommitSHA, KindCommit); err == nil && existing != nil && existing.Status == StatusCompleted {
		return nil
	}

	dir := ""
	if p.WorktreeID != nil {
		path, lerr := h.locator.LocateWorktree(ctx, *p.WorktreeID)
		if lerr != nil {
			return fmt.Errorf("locate worktree: %w", lerr)
		}
		dir = path
	} else {
		path, lerr := h.locator.LocateWorkspace(ctx, p.WorkspaceID)
		if lerr != nil {
			return fmt.Errorf("locate workspace: %w", lerr)
		}
		dir = path
	}

	diff, err := h.gitr.DiffStat(ctx, dir, p.CommitSHA)
	if err != nil {
		return fmt.Errorf("git show --stat: %w", err)
	}
	if len(diff) > maxDiffChars {
		diff = diff[:maxDiffChars] + "\n...[truncated]"
	}

	title, body, model, serr := h.summ.Summarize(ctx, p.Branch, p.CommitSHA, diff)
	if serr != nil {
		// Persist a failed row so the failure is visible, then return for retry.
		msg := serr.Error()
		if _, uerr := h.repo.Upsert(ctx, UpsertInput{
			WorkspaceID: p.WorkspaceID, WorktreeID: p.WorktreeID, Branch: p.Branch,
			CommitSHA: p.CommitSHA, Kind: KindCommit, BodyMD: "",
			Status: StatusFailed, Error: &msg,
		}); uerr != nil {
			h.log.Error("docs.summary.upsert_failed_row", "commit", p.CommitSHA, "err", uerr.Error())
		}
		return fmt.Errorf("summarize: %w", serr)
	}

	var titlePtr, modelPtr *string
	if t := strings.TrimSpace(title); t != "" {
		titlePtr = &t
	}
	if m := strings.TrimSpace(model); m != "" {
		modelPtr = &m
	}
	if _, err := h.repo.Upsert(ctx, UpsertInput{
		WorkspaceID: p.WorkspaceID, WorktreeID: p.WorktreeID, Branch: p.Branch,
		CommitSHA: p.CommitSHA, Kind: KindCommit, Title: titlePtr,
		BodyMD: body, Model: modelPtr, Status: StatusCompleted,
	}); err != nil {
		return fmt.Errorf("upsert summary: %w", err)
	}
	h.log.Info("docs.summary.completed", "workspace_id", p.WorkspaceID, "commit", p.CommitSHA)
	return nil
}
