package handlers

import (
	"encoding/json"
	"net/http"

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

	"github.com/forgeo/forgebucket/internal/api/middleware"
	"github.com/forgeo/forgebucket/internal/models"
)

type SecretHandler struct {
	db            *xorm.Engine
	sessionSecret string
}

func NewSecretHandler(db *xorm.Engine, sessionSecret string) *SecretHandler {
	return &SecretHandler{db: db, sessionSecret: sessionSecret}
}

// ── Secret list response (names only — never values) ─────────────────────────

type secretListItem struct {
	ID        int64  `json:"id"`
	Name      string `json:"name"`
	CreatedAt string `json:"createdAt"`
	UpdatedAt string `json:"updatedAt"`
}

func toListItem(s models.Secret) secretListItem {
	return secretListItem{
		ID:        s.ID,
		Name:      s.Name,
		CreatedAt: s.CreatedAt.Format("2006-01-02T15:04:05Z"),
		UpdatedAt: s.UpdatedAt.Format("2006-01-02T15:04:05Z"),
	}
}

// ── Repo secrets ──────────────────────────────────────────────────────────────

func (h *SecretHandler) ListRepoSecrets(w http.ResponseWriter, r *http.Request) {
	repoID, ok := h.resolveRepoID(w, r)
	if !ok {
		return
	}
	h.listSecrets(w, models.SecretScopeRepo, repoID)
}

func (h *SecretHandler) UpsertRepoSecret(w http.ResponseWriter, r *http.Request) {
	repoID, ok := h.resolveRepoID(w, r)
	if !ok {
		return
	}
	h.upsertSecret(w, r, models.SecretScopeRepo, repoID)
}

func (h *SecretHandler) DeleteRepoSecret(w http.ResponseWriter, r *http.Request) {
	repoID, ok := h.resolveRepoID(w, r)
	if !ok {
		return
	}
	h.deleteSecret(w, r, models.SecretScopeRepo, repoID)
}

// ── Env secrets ───────────────────────────────────────────────────────────────

func (h *SecretHandler) ListEnvSecrets(w http.ResponseWriter, r *http.Request) {
	envID, ok := h.resolveEnvID(w, r)
	if !ok {
		return
	}
	h.listSecrets(w, models.SecretScopeEnv, envID)
}

func (h *SecretHandler) UpsertEnvSecret(w http.ResponseWriter, r *http.Request) {
	envID, ok := h.resolveEnvID(w, r)
	if !ok {
		return
	}
	h.upsertSecret(w, r, models.SecretScopeEnv, envID)
}

func (h *SecretHandler) DeleteEnvSecret(w http.ResponseWriter, r *http.Request) {
	envID, ok := h.resolveEnvID(w, r)
	if !ok {
		return
	}
	h.deleteSecret(w, r, models.SecretScopeEnv, envID)
}

// ── Workspace secrets ─────────────────────────────────────────────────────────

func (h *SecretHandler) ListWorkspaceSecrets(w http.ResponseWriter, r *http.Request) {
	wsID, ok := h.resolveWorkspaceID(w, r)
	if !ok {
		return
	}
	h.listSecrets(w, models.SecretScopeWorkspace, wsID)
}

func (h *SecretHandler) UpsertWorkspaceSecret(w http.ResponseWriter, r *http.Request) {
	wsID, ok := h.resolveWorkspaceID(w, r)
	if !ok {
		return
	}
	h.upsertSecret(w, r, models.SecretScopeWorkspace, wsID)
}

func (h *SecretHandler) DeleteWorkspaceSecret(w http.ResponseWriter, r *http.Request) {
	wsID, ok := h.resolveWorkspaceID(w, r)
	if !ok {
		return
	}
	h.deleteSecret(w, r, models.SecretScopeWorkspace, wsID)
}

// ── Global secrets (admin only) ───────────────────────────────────────────────

func (h *SecretHandler) ListGlobalSecrets(w http.ResponseWriter, r *http.Request) {
	if !h.requireAdmin(w, r) {
		return
	}
	h.listSecrets(w, models.SecretScopeGlobal, 0)
}

func (h *SecretHandler) UpsertGlobalSecret(w http.ResponseWriter, r *http.Request) {
	if !h.requireAdmin(w, r) {
		return
	}
	h.upsertSecret(w, r, models.SecretScopeGlobal, 0)
}

func (h *SecretHandler) DeleteGlobalSecret(w http.ResponseWriter, r *http.Request) {
	if !h.requireAdmin(w, r) {
		return
	}
	h.deleteSecret(w, r, models.SecretScopeGlobal, 0)
}

// ── Shared CRUD primitives ────────────────────────────────────────────────────

func (h *SecretHandler) listSecrets(w http.ResponseWriter, scope models.SecretScope, scopeID int64) {
	var secrets []models.Secret
	h.db.Where("scope = ? AND scope_id = ?", scope, scopeID).Asc("name").Find(&secrets)
	items := make([]secretListItem, len(secrets))
	for i, s := range secrets {
		items[i] = toListItem(s)
	}
	jsonOK(w, items)
}

func (h *SecretHandler) upsertSecret(w http.ResponseWriter, r *http.Request, scope models.SecretScope, scopeID int64) {
	var body struct {
		Name  string `json:"name"`
		Value string `json:"value"`
	}
	if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
		jsonError(w, "invalid request body", http.StatusBadRequest)
		return
	}
	if body.Name == "" || body.Value == "" {
		jsonError(w, "name and value are required", http.StatusBadRequest)
		return
	}

	encrypted, err := models.EncryptSecret(body.Value, h.sessionSecret)
	if err != nil {
		jsonError(w, "encryption failed", http.StatusInternalServerError)
		return
	}

	// Upsert: update if the name already exists for this scope.
	var existing models.Secret
	found, _ := h.db.Where("scope = ? AND scope_id = ? AND name = ?", scope, scopeID, body.Name).Get(&existing)
	if found {
		existing.EncryptedValue = encrypted
		h.db.ID(existing.ID).Cols("encrypted_value", "updated_at").Update(&existing) //nolint:errcheck
		jsonOK(w, toListItem(existing))
		return
	}

	secret := &models.Secret{
		Scope:          scope,
		ScopeID:        scopeID,
		Name:           body.Name,
		EncryptedValue: encrypted,
	}
	if _, err := h.db.Insert(secret); err != nil {
		jsonError(w, "could not save secret", http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusCreated)
	json.NewEncoder(w).Encode(toListItem(*secret)) //nolint:errcheck
}

func (h *SecretHandler) deleteSecret(w http.ResponseWriter, r *http.Request, scope models.SecretScope, scopeID int64) {
	name := chi.URLParam(r, "name")
	res, err := h.db.Where("scope = ? AND scope_id = ? AND name = ?", scope, scopeID, name).
		Delete(&models.Secret{})
	if err != nil || res == 0 {
		jsonError(w, "secret not found", http.StatusNotFound)
		return
	}
	w.WriteHeader(http.StatusNoContent)
}

// ── ResolveSecretsForRun ──────────────────────────────────────────────────────
// Used by the CI executor to build the env var map for a pipeline job.
// Priority order (highest wins): Env > Repo > Workspace > Global.

func ResolveSecretsForRun(db *xorm.Engine, repoID, workspaceID, envID int64, sessionSecret string) map[string]string {
	out := map[string]string{}

	loadScope := func(scope models.SecretScope, scopeID int64) {
		var secrets []models.Secret
		db.Where("scope = ? AND scope_id = ?", scope, scopeID).Find(&secrets)
		for _, s := range secrets {
			if _, already := out[s.Name]; !already {
				pt, err := models.DecryptSecret(s.EncryptedValue, sessionSecret)
				if err == nil {
					out[s.Name] = pt
				}
			}
		}
	}

	// Load in reverse priority so higher-priority scopes overwrite.
	loadScope(models.SecretScopeGlobal, 0)
	if workspaceID != 0 {
		loadScope(models.SecretScopeWorkspace, workspaceID)
	}
	loadScope(models.SecretScopeRepo, repoID)
	if envID != 0 {
		loadScope(models.SecretScopeEnv, envID)
	}
	return out
}

// ── Helpers ───────────────────────────────────────────────────────────────────

func (h *SecretHandler) resolveRepoID(w http.ResponseWriter, r *http.Request) (int64, bool) {
	return resolveRepoID(h.db, w, r)
}

func (h *SecretHandler) resolveEnvID(w http.ResponseWriter, r *http.Request) (int64, bool) {
	repoID, ok := h.resolveRepoID(w, r)
	if !ok {
		return 0, false
	}
	envName := chi.URLParam(r, "envName")
	var env models.Environment
	if found, _ := h.db.Where("repo_id = ? AND name = ?", repoID, envName).Get(&env); !found {
		jsonError(w, "environment not found", http.StatusNotFound)
		return 0, false
	}
	return env.ID, true
}

func (h *SecretHandler) resolveWorkspaceID(w http.ResponseWriter, r *http.Request) (int64, bool) {
	handle := chi.URLParam(r, "handle")
	var ws models.Workspace
	if found, _ := h.db.Where("handle = ?", handle).Get(&ws); !found {
		jsonError(w, "workspace not found", http.StatusNotFound)
		return 0, false
	}
	// Require membership.
	userID, _ := middleware.UserIDFromContext(r.Context())
	var member models.WorkspaceMember
	if found, _ := h.db.Where("workspace_id = ? AND user_id = ?", ws.ID, userID).Get(&member); !found {
		jsonError(w, "workspace not found", http.StatusNotFound)
		return 0, false
	}
	return ws.ID, true
}

func (h *SecretHandler) requireAdmin(w http.ResponseWriter, r *http.Request) bool {
	userID, _ := middleware.UserIDFromContext(r.Context())
	var u models.User
	if found, _ := h.db.ID(userID).Get(&u); !found || !u.IsAdmin {
		jsonError(w, "admin required", http.StatusForbidden)
		return false
	}
	return true
}
