package handlers

// v0.38.1: Extension connection handlers — personal scope + resolution.
// Pattern follows apiconfigs.go (ProviderConfigHandler).

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

	"github.com/gin-gonic/gin"

	"switchboard-core/crypto"
	"switchboard-core/models"
	"switchboard-core/store"
)

// ConnectionHandler handles user-facing extension connection endpoints.
type ConnectionHandler struct {
	stores store.Stores
	vault  *crypto.KeyResolver
}

func NewConnectionHandler(s store.Stores, vault *crypto.KeyResolver) *ConnectionHandler {
	return &ConnectionHandler{stores: s, vault: vault}
}

// ListConnections returns the user's personal connections.
func (h *ConnectionHandler) ListConnections(c *gin.Context) {
	userID := getUserID(c)
	// v0.38.5: Show all accessible connections (personal + team + global)
	// so users can see which connections are available to them.
	conns, err := h.stores.Connections.ListAccessible(c.Request.Context(), userID)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list connections"})
		return
	}
	c.JSON(http.StatusOK, gin.H{"data": maskConnectionSecrets(conns)})
}

// CreateConnection creates a personal-scope connection.
func (h *ConnectionHandler) CreateConnection(c *gin.Context) {
	userID := getUserID(c)

	var req struct {
		Type      string         `json:"type" binding:"required"`
		PackageID string         `json:"package_id" binding:"required"`
		Name      string         `json:"name" binding:"required"`
		Config    models.JSONMap `json:"config"`
	}
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	secretFields := h.lookupSecretFields(c, req.PackageID, req.Type)
	encConfig := h.encryptSecrets(req.Config, secretFields, models.ScopePersonal, userID)

	conn := &models.ExtConnection{
		Type:      req.Type,
		PackageID: req.PackageID,
		Scope:     models.ScopePersonal,
		OwnerID:   userID,
		Name:      req.Name,
		Config:    encConfig,
		IsActive:  true,
	}

	if err := h.stores.Connections.Create(c.Request.Context(), conn); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create connection"})
		return
	}

	c.JSON(http.StatusCreated, gin.H{
		"id":   conn.ID,
		"type": conn.Type,
		"name": conn.Name,
	})
}

// GetConnection returns a single connection by ID.
func (h *ConnectionHandler) GetConnection(c *gin.Context) {
	userID := getUserID(c)
	id := c.Param("id")

	conn, err := h.stores.Connections.GetByID(c.Request.Context(), id)
	if err != nil || conn.Scope != models.ScopePersonal || conn.OwnerID != userID {
		c.JSON(http.StatusNotFound, gin.H{"error": "connection not found"})
		return
	}

	c.JSON(http.StatusOK, maskSingleConnection(conn))
}

// UpdateConnection updates a personal connection.
func (h *ConnectionHandler) UpdateConnection(c *gin.Context) {
	userID := getUserID(c)
	id := c.Param("id")

	existing, err := h.stores.Connections.GetByID(c.Request.Context(), id)
	if err != nil || existing.Scope != models.ScopePersonal || existing.OwnerID != userID {
		c.JSON(http.StatusForbidden, gin.H{"error": "can only update your own connections"})
		return
	}

	var req struct {
		Name   *string        `json:"name,omitempty"`
		Config models.JSONMap `json:"config,omitempty"`
	}
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	patch := models.ExtConnectionPatch{Name: req.Name}
	if req.Config != nil {
		secretFields := h.lookupSecretFields(c, existing.PackageID, existing.Type)
		patch.Config = h.encryptSecrets(req.Config, secretFields, models.ScopePersonal, userID)
	}

	if err := h.stores.Connections.Update(c.Request.Context(), id, patch); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update connection"})
		return
	}

	c.JSON(http.StatusOK, gin.H{"message": "connection updated"})
}

// DeleteConnection deletes a personal connection.
func (h *ConnectionHandler) DeleteConnection(c *gin.Context) {
	userID := getUserID(c)
	id := c.Param("id")

	n, err := h.stores.Connections.DeleteByIDAndScope(c.Request.Context(), id, models.ScopePersonal, userID)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to delete connection"})
		return
	}
	if n == 0 {
		c.JSON(http.StatusNotFound, gin.H{"error": "connection not found"})
		return
	}

	c.JSON(http.StatusOK, gin.H{"message": "connection deleted"})
}

// ResolveConnection resolves a connection via the scope chain.
// GET /connections/resolve?type=gitea&name=Work+Gitea
func (h *ConnectionHandler) ResolveConnection(c *gin.Context) {
	userID := getUserID(c)
	connType := c.Query("type")
	if connType == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "type query parameter required"})
		return
	}
	name := c.Query("name")

	conn, err := h.stores.Connections.Resolve(c.Request.Context(), userID, connType, name)
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "no connection found"})
		return
	}

	// Decrypt secrets for the resolver response (used by frontend to test connections).
	secretFields := h.lookupSecretFields(c, conn.PackageID, conn.Type)
	conn.Config = h.decryptSecrets(conn.Config, secretFields, conn.Scope, conn.OwnerID)

	c.JSON(http.StatusOK, conn)
}

// ── Encryption helpers ─────────────────────────────

// lookupSecretFields reads the package manifest to identify which config
// fields are of type "secret" for a given connection type.
func (h *ConnectionHandler) lookupSecretFields(c *gin.Context, packageID, connType string) map[string]bool {
	result := map[string]bool{}
	pkg, err := h.stores.Packages.Get(c.Request.Context(), packageID)
	if err != nil || pkg == nil {
		return result
	}
	connsRaw, ok := pkg.Manifest["connections"]
	if !ok {
		return result
	}
	connsJSON, _ := json.Marshal(connsRaw)
	var conns []struct {
		Type   string                       `json:"type"`
		Fields map[string]map[string]string `json:"fields"`
	}
	json.Unmarshal(connsJSON, &conns)
	for _, cd := range conns {
		if cd.Type == connType {
			for fieldName, fieldDef := range cd.Fields {
				if fieldDef["type"] == "secret" {
					result[fieldName] = true
				}
			}
			break
		}
	}
	return result
}

// encryptSecrets encrypts secret-type fields within a config map.
// Non-secret fields pass through unchanged.
func (h *ConnectionHandler) encryptSecrets(config models.JSONMap, secretFields map[string]bool, scope, ownerID string) models.JSONMap {
	if config == nil || h.vault == nil {
		return config
	}
	out := make(models.JSONMap, len(config))
	for k, v := range config {
		if secretFields[k] {
			if s, ok := v.(string); ok && s != "" {
				enc, nonce, err := h.vault.EncryptForScope(s, scope, ownerID)
				if err == nil {
					out[k] = map[string]interface{}{
						"enc":   enc,
						"nonce": nonce,
					}
					continue
				}
			}
		}
		out[k] = v
	}
	return out
}

// decryptSecrets decrypts secret-type fields within a config map.
func (h *ConnectionHandler) decryptSecrets(config models.JSONMap, secretFields map[string]bool, scope, ownerID string) models.JSONMap {
	if config == nil || h.vault == nil {
		return config
	}
	out := make(models.JSONMap, len(config))
	for k, v := range config {
		if secretFields[k] {
			if m, ok := v.(map[string]interface{}); ok {
				encRaw, _ := m["enc"]
				nonceRaw, _ := m["nonce"]
				// Type-assert to []byte or base64-decode from string
				enc := toBytes(encRaw)
				nonce := toBytes(nonceRaw)
				if len(enc) > 0 {
					plaintext, err := h.vault.Decrypt(enc, nonce, scope, ownerID)
					if err == nil {
						out[k] = plaintext
						continue
					}
				}
			}
		}
		out[k] = v
	}
	return out
}

// toBytes coerces an interface{} to []byte. Handles both []byte (from Go)
// and string (from JSON unmarshal — base64 encoded by json.Marshal for []byte).
func toBytes(v interface{}) []byte {
	switch t := v.(type) {
	case []byte:
		return t
	case string:
		if t == "" {
			return nil
		}
		// JSON round-trip: json.Marshal encodes []byte as base64 strings.
		// Decode back to the original bytes.
		decoded, err := base64.StdEncoding.DecodeString(t)
		if err != nil {
			// Fallback: might be raw string (shouldn't happen, but be safe)
			return []byte(t)
		}
		return decoded
	}
	return nil
}

// ── Response masking ───────────────────────────────

// maskConnectionSecrets returns safe connection summaries (no secret values).
func maskConnectionSecrets(conns []models.ExtConnection) []map[string]interface{} {
	out := make([]map[string]interface{}, len(conns))
	for i, c := range conns {
		out[i] = map[string]interface{}{
			"id":         c.ID,
			"type":       c.Type,
			"package_id": c.PackageID,
			"scope":      c.Scope,
			"name":       c.Name,
			"is_active":  c.IsActive,
			"has_config":  len(c.Config) > 0,
			"created_at": c.CreatedAt.Format("2006-01-02T15:04:05Z"),
			"updated_at": c.UpdatedAt.Format("2006-01-02T15:04:05Z"),
		}
	}
	return out
}

func maskSingleConnection(c *models.ExtConnection) map[string]interface{} {
	return map[string]interface{}{
		"id":         c.ID,
		"type":       c.Type,
		"package_id": c.PackageID,
		"scope":      c.Scope,
		"name":       c.Name,
		"is_active":  c.IsActive,
		"has_config":  len(c.Config) > 0,
		"created_at": c.CreatedAt.Format("2006-01-02T15:04:05Z"),
		"updated_at": c.UpdatedAt.Format("2006-01-02T15:04:05Z"),
	}
}
