package api

import (
	"fmt"
	"net/http"
	"regexp"
	"strings"

	"gitea.hostxtra.co.uk/mrhid6/vantage/server/internal/auth"
	"gitea.hostxtra.co.uk/mrhid6/vantage/server/internal/services"
	"github.com/gin-gonic/gin"
)

var groupNamePattern = regexp.MustCompile(`^[A-Za-z0-9._-]+$`)

func validName(s string) bool {
	return s != "" && len(s) <= 128 && groupNamePattern.MatchString(s)
}

const ctxSecretsInstanceKey = "km_secrets_instance"

func secretsReadAuth() gin.HandlerFunc {
	return func(c *gin.Context) {
		const prefix = "Bearer "
		authHeader := c.GetHeader("Authorization")
		if len(authHeader) <= len(prefix) || !strings.EqualFold(authHeader[:len(prefix)], prefix) {
			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "missing bearer token"})
			return
		}
		instanceID, ok := services.ResolveSecretsReadToken(authHeader[len(prefix):])
		if !ok {
			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid token"})
			return
		}
		c.Set(ctxSecretsInstanceKey, instanceID)
		c.Next()
	}
}

func esoGetGroup(c *gin.Context) {
	group := c.Param("group")

	instanceID := c.GetString(ctxSecretsInstanceKey)
	if instanceID == "" {

		c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid token"})
		return
	}
	values, err := services.GetSecretGroupDecrypted(instanceID, group)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "store error"})
		return
	}
	if len(values) == 0 {
		c.JSON(http.StatusNotFound, gin.H{"error": "group not found"})
		return
	}
	c.JSON(http.StatusOK, values)
}

func listSecretGroups(c *gin.Context) {
	groups, err := services.ListSecretGroups(auth.InstanceID(c))
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, groups)
}

func createSecretGroup(c *gin.Context) {
	var body struct {
		Group  string            `json:"group" binding:"required"`
		Values map[string]string `json:"values"`
	}
	if err := c.ShouldBindJSON(&body); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	if !validName(body.Group) {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid group name"})
		return
	}
	if len(body.Values) == 0 {
		c.JSON(http.StatusBadRequest, gin.H{"error": "a group must be created with at least one key"})
		return
	}
	for k := range body.Values {
		if !validName(k) {
			c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("invalid key name: %s", k)})
			return
		}
	}
	if err := services.UpsertSecrets(auth.InstanceID(c), body.Group, body.Values); err != nil {
		if limitStatus(c, err) {
			return
		}
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	services.LogEvent(auth.InstanceID(c), "secret.updated", actorFromCtx(c), "", "", fmt.Sprintf("group '%s' created with keys: %s", body.Group, strings.Join(services.SortedKeys(body.Values), ", ")))
	c.JSON(http.StatusCreated, gin.H{"group": body.Group})
}

func getSecretGroup(c *gin.Context) {
	group := c.Param("group")
	secrets, err := services.GetSecretGroup(auth.InstanceID(c), group)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	if len(secrets) == 0 {
		c.JSON(http.StatusNotFound, gin.H{"error": "group not found"})
		return
	}
	c.JSON(http.StatusOK, gin.H{"group": group, "secrets": secrets})
}

func putSecretGroup(c *gin.Context) {
	group := c.Param("group")
	if !validName(group) {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid group name"})
		return
	}
	var values map[string]string
	if err := c.ShouldBindJSON(&values); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid JSON body"})
		return
	}
	if len(values) == 0 {
		c.JSON(http.StatusBadRequest, gin.H{"error": "body must contain at least one key"})
		return
	}
	for k := range values {
		if !validName(k) {
			c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("invalid key name: %s", k)})
			return
		}
	}
	if err := services.UpsertSecrets(auth.InstanceID(c), group, values); err != nil {
		if limitStatus(c, err) {
			return
		}
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	services.LogEvent(auth.InstanceID(c), "secret.updated", actorFromCtx(c), "", "", fmt.Sprintf("group '%s' keys updated: %s", group, strings.Join(services.SortedKeys(values), ", ")))
	c.JSON(http.StatusOK, gin.H{"saved": true})
}

func revealSecret(c *gin.Context) {
	group := c.Param("group")
	var body struct {
		Key string `json:"key" binding:"required"`
	}
	if err := c.ShouldBindJSON(&body); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	value, err := services.RevealSecret(auth.InstanceID(c), group, body.Key)
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
		return
	}
	services.LogEvent(auth.InstanceID(c), "secret.revealed", actorFromCtx(c), "", "", fmt.Sprintf("value of '%s/%s' revealed", group, body.Key))
	c.JSON(http.StatusOK, gin.H{"value": value})
}

func deleteSecretKey(c *gin.Context) {
	group := c.Param("group")
	key := c.Param("key")
	if err := services.DeleteSecret(auth.InstanceID(c), group, key); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	services.LogEvent(auth.InstanceID(c), "secret.deleted", actorFromCtx(c), "", "", fmt.Sprintf("key '%s' deleted from group '%s'", key, group))
	c.JSON(http.StatusOK, gin.H{"deleted": true})
}

func deleteSecretGroup(c *gin.Context) {
	group := c.Param("group")
	if err := services.DeleteSecretGroup(auth.InstanceID(c), group); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	services.LogEvent(auth.InstanceID(c), "secretgroup.deleted", actorFromCtx(c), "", "", fmt.Sprintf("group '%s' deleted", group))
	c.JSON(http.StatusOK, gin.H{"deleted": true})
}

func rotateSecretsToken(c *gin.Context) {
	token, err := services.RotateSecretsReadToken(auth.InstanceID(c))
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	services.LogEvent(auth.InstanceID(c), "secrets.token_rotated", actorFromCtx(c), "", "", "ESO read token rotated")
	c.JSON(http.StatusOK, gin.H{"token": token})
}
