package handlers

import (
	"log"
	"os"
	"path/filepath"
)

// restartTemplateInput is the subset of the /workspaces/:id/restart request
// body that affects which config source the provisioner uses. Extracted as
// a type so `resolveRestartTemplate` has a single pure-function signature
// for unit tests — no gin context, no DB, no filesystem writes.
type restartTemplateInput struct {
	// Template is an explicit template dir name from the request body.
	// Always honoured when resolvable — caller asked by name, that's
	// unambiguous consent to overwrite the config volume.
	Template string
	// ApplyTemplate opts the caller in to name-based auto-match AND the
	// runtime-default fallback. Without this flag a restart MUST NOT
	// overwrite the user's config volume — a user who edited their
	// model/provider/skills/prompts via the Canvas Config tab and hit
	// Save+Restart expects their edits to survive. The previous behaviour
	// (name-based auto-match unconditionally) silently reverted edits for
	// any workspace whose name matched a template dir (e.g. "Hermes Agent"
	// → hermes/), which is the regression this fix closes.
	ApplyTemplate bool
	// RebuildConfig (#239) is the recovery signal used when the workspace's
	// config volume was destroyed out-of-band. Tries org-templates as a
	// last-resort source so the workspace can self-heal without admin
	// intervention. Orthogonal to ApplyTemplate.
	RebuildConfig bool
}

// resolveRestartTemplate chooses the config source for a restart in the
// documented priority order:
//
//  1. Explicit `Template` from the request body (always honoured).
//  2. `ApplyTemplate=true` → name-based auto-match via findTemplateByName.
//  3. `RebuildConfig=true` → org-templates recovery fallback (#239).
//  4. `ApplyTemplate=true` + non-empty dbRuntime → runtime-default template
//     (e.g. `hermes-default/`) for runtime-change workflows.
//  5. Fall through → empty path + "existing-volume" label. Provisioner
//     reuses the workspace's existing config volume from the previous run.
//
// Returns (templatePath, configLabel). An empty templatePath is the signal
// to the provisioner that the existing volume is authoritative — the flow
// that preserves user edits.
//
// Pure function: no writes, no DB access, no network. Safe to unit-test
// with just a temp directory.
func resolveRestartTemplate(configsDir, wsName, dbRuntime string, body restartTemplateInput) (templatePath, configLabel string) {
	template := body.Template

	// Tier 2: name-based auto-match, gated on ApplyTemplate.
	if template == "" && body.ApplyTemplate {
		template = findTemplateByName(configsDir, wsName)
	}

	// Tier 1 + 2 resolve via the same code path — validate + stat.
	if template != "" {
		candidatePath, resolveErr := resolveInsideRoot(configsDir, template)
		if resolveErr != nil {
			log.Printf("Restart: invalid template %q: %v — proceeding without it", template, resolveErr)
			template = ""
		} else if _, err := os.Stat(candidatePath); err == nil {
			return candidatePath, template
		} else {
			log.Printf("Restart: template %q dir not found — proceeding without it", template)
		}
	}

	// Tier 3: #239 rebuild_config — org-templates as last-resort recovery.
	if body.RebuildConfig {
		if p, label := resolveOrgTemplate(configsDir, wsName); p != "" {
			log.Printf("Restart: rebuild_config — using org-template %s (%s)", label, wsName)
			return p, label
		}
	}

	// Tier 4: runtime-default — apply_template=true + known runtime.
	// Use case: Canvas Config tab changed the runtime; we need the new
	// runtime's base files (entry point, Dockerfile, skill scaffolding)
	// because the existing volume was written by the old runtime.
	//
	// SECURITY (CWE-22 / F1502): dbRuntime comes from the workspaces DB
	// column — set by the PATCH Update handler which only validates length
	// and newlines, not path-traversal characters.  Without sanitisation an
	// attacker who holds a workspace token could set runtime to
	// "../../../etc" and, if a directory matching that path existed on the
	// host, load an arbitrary host directory as the workspace template.
	//
	// sanitizeRuntime applies an allowlist of known runtimes; any unknown
	// value (including traversal strings) is remapped to "langgraph".  The
	// attacker cannot choose an arbitrary host path — they can at most
	// trigger application of the langgraph-default template.
	if body.ApplyTemplate && dbRuntime != "" {
		safeRuntime := sanitizeRuntime(dbRuntime)
		runtimeTemplate := filepath.Join(configsDir, safeRuntime+"-default")
		if _, err := os.Stat(runtimeTemplate); err == nil {
			label := safeRuntime + "-default"
			log.Printf("Restart: applying template %s (runtime change)", label)
			return runtimeTemplate, label
		}
	}

	// Tier 5: reuse existing volume. This is the default, and the path
	// the Canvas Save+Restart flow MUST hit to preserve user edits.
	return "", "existing-volume"
}
