package api

import (
	"errors"
	"log/slog"
	"net/http"
	"strings"

	"github.com/go-chi/chi/v5"

	"github.com/alexei/tinyforge/internal/crypto"
	"github.com/alexei/tinyforge/internal/store"
)

// workloadEnvRow is the JSON shape returned to clients. Plaintext is
// redacted for encrypted entries — once a value is encrypted, the
// server treats it as write-only. To rotate, the operator submits a new
// value; to read, they have to look at the running container.
type workloadEnvRow struct {
	ID         string `json:"id"`
	WorkloadID string `json:"workload_id"`
	Key        string `json:"key"`
	Value      string `json:"value"`
	Encrypted  bool   `json:"encrypted"`
	CreatedAt  string `json:"created_at"`
	UpdatedAt  string `json:"updated_at"`
}

func (s *Server) listWorkloadEnv(w http.ResponseWriter, r *http.Request) {
	id := chi.URLParam(r, "id")
	if _, err := s.store.GetWorkloadByID(id); err != nil {
		if errors.Is(err, store.ErrNotFound) {
			respondNotFound(w, "workload")
			return
		}
		respondError(w, http.StatusInternalServerError, "get workload")
		return
	}
	rows, err := s.store.ListWorkloadEnv(id)
	if err != nil {
		respondError(w, http.StatusInternalServerError, "list workload env")
		return
	}
	out := make([]workloadEnvRow, 0, len(rows))
	for _, e := range rows {
		row := workloadEnvRow{
			ID:         e.ID,
			WorkloadID: e.WorkloadID,
			Key:        e.Key,
			Encrypted:  e.Encrypted,
			CreatedAt:  e.CreatedAt,
			UpdatedAt:  e.UpdatedAt,
		}
		if e.Encrypted {
			row.Value = "" // write-only after encryption
		} else {
			row.Value = e.Value
		}
		out = append(out, row)
	}
	respondJSON(w, http.StatusOK, out)
}

// setWorkloadEnvRequest is the POST/PUT body. Encrypted=true causes the
// server to encrypt the value at rest with the global encryption key.
type setWorkloadEnvRequest struct {
	Key       string `json:"key"`
	Value     string `json:"value"`
	Encrypted bool   `json:"encrypted"`
}

func (s *Server) setWorkloadEnv(w http.ResponseWriter, r *http.Request) {
	id := chi.URLParam(r, "id")
	if _, err := s.store.GetWorkloadByID(id); err != nil {
		if errors.Is(err, store.ErrNotFound) {
			respondNotFound(w, "workload")
			return
		}
		respondError(w, http.StatusInternalServerError, "get workload")
		return
	}
	var req setWorkloadEnvRequest
	if !decodeJSONStrict(w, r, &req) {
		return
	}
	req.Key = strings.TrimSpace(req.Key)
	if req.Key == "" {
		respondError(w, http.StatusBadRequest, "key is required")
		return
	}
	if !validEnvKey(req.Key) {
		respondError(w, http.StatusBadRequest, "key must match [A-Za-z_][A-Za-z0-9_]*")
		return
	}
	value := req.Value
	if req.Encrypted && value != "" {
		enc, err := crypto.Encrypt(s.encKey, value)
		if err != nil {
			respondError(w, http.StatusInternalServerError, "encrypt value")
			return
		}
		value = enc
	}
	row, err := s.store.SetWorkloadEnv(store.WorkloadEnv{
		WorkloadID: id,
		Key:        req.Key,
		Value:      value,
		Encrypted:  req.Encrypted,
	})
	if err != nil {
		slog.Error("set workload env", "workload", id, "key", req.Key, "error", err)
		respondError(w, http.StatusInternalServerError, "set workload env")
		return
	}
	respondJSON(w, http.StatusOK, workloadEnvRow{
		ID:         row.ID,
		WorkloadID: row.WorkloadID,
		Key:        row.Key,
		Value:      "", // never echo even fresh writes — caller already has it
		Encrypted:  row.Encrypted,
		CreatedAt:  row.CreatedAt,
		UpdatedAt:  row.UpdatedAt,
	})
}

func (s *Server) deleteWorkloadEnv(w http.ResponseWriter, r *http.Request) {
	envID := chi.URLParam(r, "envID")
	if err := s.store.DeleteWorkloadEnv(envID); err != nil {
		if errors.Is(err, store.ErrNotFound) {
			respondNotFound(w, "workload env")
			return
		}
		respondError(w, http.StatusInternalServerError, "delete workload env")
		return
	}
	respondJSON(w, http.StatusOK, map[string]string{"deleted": envID})
}

// Workload-level webhook URL handlers were dropped in the hard legacy
// cutover: the old `/api/webhook/workloads/{secret}` route is gone, so
// minting a workload secret would hand operators a URL that 404s. The
// inbound webhook surface is now exclusively first-class triggers
// (`/api/webhook/triggers/{secret}`); use the trigger CRUD + bindings
// endpoints to wire a workload to inbound deploys.

// validEnvKey accepts POSIX-style env names. Rejects anything that would
// confuse Docker's env parser (=, spaces, control chars).
func validEnvKey(k string) bool {
	if len(k) == 0 || len(k) > 256 {
		return false
	}
	for i, ch := range k {
		switch {
		case ch >= 'A' && ch <= 'Z',
			ch >= 'a' && ch <= 'z',
			ch == '_':
			continue
		case (ch >= '0' && ch <= '9') && i > 0:
			continue
		default:
			return false
		}
	}
	return true
}
