package deployer

import (
	"log/slog"

	"github.com/alexei/tinyforge/internal/store"
	"github.com/alexei/tinyforge/internal/workload/plugin"
)

// deployHistoryKeepPerWorkload bounds the ledger per workload. Newer rows
// always have larger ids, so pruning keeps the most recent N — enough for a
// useful rollback menu without unbounded growth on hot workloads.
const deployHistoryKeepPerWorkload = 50

// recordDeployHistory appends one ledger row for a completed dispatch.
//
// Best-effort: a store failure is logged and swallowed — recording must
// never turn a successful deploy into a failed request (same contract as
// EmitDeployEvent and the pre-deploy backup). The raw deploy error is NEVER
// persisted: it can carry registry-auth bytes or compose stdout, so only a
// fixed, secret-free marker lands in the row (raw detail goes to slog at the
// call site). Called only from DispatchPlugin — reconcile/teardown ticks are
// not deploys and must not appear in the ledger.
func (d *Deployer) recordDeployHistory(w plugin.Workload, intent plugin.DeploymentIntent, outcome string, deployErr error, startedAt string) {
	if d.store == nil {
		return
	}
	entry := store.DeployHistoryEntry{
		WorkloadID:  w.ID,
		SourceKind:  w.SourceKind,
		Reference:   d.effectiveReference(w, intent),
		Reason:      intent.Reason,
		TriggeredBy: intent.TriggeredBy,
		Note:        intent.Metadata["note"], // nil map read is safe
		Outcome:     outcome,
		StartedAt:   startedAt,
		FinishedAt:  store.Now(),
	}
	if deployErr != nil {
		entry.Error = "deploy failed (see server logs)"
	}
	if _, err := d.store.InsertDeployHistory(entry); err != nil {
		slog.Warn("deploy history: insert failed", "workload", w.ID, "error", err)
		return
	}
	// Cheap indexed DELETE — negligible next to a multi-second deploy, so it
	// stays inline rather than on an untracked goroutine that could outrace
	// graceful shutdown's db.Close().
	if err := d.store.PruneDeployHistory(w.ID, deployHistoryKeepPerWorkload); err != nil {
		slog.Warn("deploy history: prune failed", "workload", w.ID, "error", err)
	}
}

// effectiveReference resolves the artifact handle to record (and, for
// rollback-capable sources, to replay). It starts from the trigger-supplied
// intent.Reference and, for the image source, prefers the tag actually
// written onto the freshest container row — capturing the DefaultTag /
// "latest" resolution the source performs when intent.Reference is empty
// (e.g. a manual deploy with no override). ListContainersByWorkload returns
// newest-first, so rows[0] is the just-deployed container on success.
//
// For static/dockerfile the git trigger already supplies the commit SHA as
// intent.Reference; a manual deploy of those may record an empty reference
// (acceptable — they are not rollback-capable in this phase). compose has no
// single artifact handle.
func (d *Deployer) effectiveReference(w plugin.Workload, intent plugin.DeploymentIntent) string {
	ref := intent.Reference
	if w.SourceKind == "image" && d.store != nil {
		if rows, err := d.store.ListContainersByWorkload(w.ID); err == nil && len(rows) > 0 {
			if tag := rows[0].ImageTag; tag != "" {
				ref = tag
			}
		}
	}
	return ref
}
