package handlers

import (
	"database/sql"
	"encoding/json"
	"log"

	"github.com/gin-gonic/gin"

	"strings"

	"switchboard-core/database"
	"switchboard-core/models"
	"switchboard-core/store"
	"switchboard-core/triggers"
)

// ExtensionHandler serves extension management endpoints.
// v0.28.7: Backed by PackageStore (packages table) instead of ExtensionStore.
type ExtensionHandler struct {
	stores store.Stores
}

func NewExtensionHandler(stores store.Stores) *ExtensionHandler {
	return &ExtensionHandler{stores: stores}
}

// validTiers is the set of accepted extension tier values.
var validTiers = map[string]bool{
	models.ExtTierBrowser:  true,
	models.ExtTierStarlark: true,
	models.ExtTierSidecar:  true,
}

// ── User endpoints ──────────────────────────────

// ListUserExtensions returns all enabled extensions for the current user,
// with user-specific settings and enabled state merged in.
// GET /api/v1/extensions
func (h *ExtensionHandler) ListUserExtensions(c *gin.Context) {
	userID := c.GetString("user_id")
	tier := c.Query("tier")

	pkgs, err := h.stores.Packages.ListForUser(c.Request.Context(), userID)
	if err != nil {
		c.JSON(500, gin.H{"error": "failed to list extensions"})
		return
	}

	if tier != "" {
		filtered := make([]store.UserPackage, 0)
		for _, p := range pkgs {
			if p.Tier == tier {
				filtered = append(filtered, p)
			}
		}
		pkgs = filtered
	}

	c.JSON(200, gin.H{"data": pkgs})
}

// UpdateUserExtensionSettings saves per-user settings for an extension.
// POST /api/v1/extensions/:id/settings
func (h *ExtensionHandler) UpdateUserExtensionSettings(c *gin.Context) {
	userID := c.GetString("user_id")
	pkgID := c.Param("id")

	// Verify package exists
	pkg, err := h.stores.Packages.Get(c.Request.Context(), pkgID)
	if err != nil || pkg == nil {
		c.JSON(404, gin.H{"error": "extension not found"})
		return
	}

	// System packages can't be disabled by users
	var body struct {
		Settings  json.RawMessage `json:"settings"`
		IsEnabled *bool           `json:"is_enabled"`
	}
	if err := c.ShouldBindJSON(&body); err != nil {
		c.JSON(400, gin.H{"error": "invalid request body"})
		return
	}

	enabled := true
	if body.IsEnabled != nil {
		if pkg.IsSystem && !*body.IsEnabled {
			c.JSON(403, gin.H{"error": "system extensions cannot be disabled"})
			return
		}
		enabled = *body.IsEnabled
	}

	settings := json.RawMessage("{}")
	if body.Settings != nil {
		// v0.2.0: enforce user_overridable — strip locked keys before saving
		schema := store.ParseSettingsSchema(pkg.Manifest)
		if len(schema) > 0 {
			var incoming map[string]any
			if json.Unmarshal(body.Settings, &incoming) == nil && len(incoming) > 0 {
				filtered, rejected := store.FilterOverridableKeys(incoming, schema)
				if len(filtered) == 0 && len(rejected) > 0 {
					c.JSON(400, gin.H{"error": "these settings cannot be overridden: " + strings.Join(rejected, ", ")})
					return
				}
				if b, err := json.Marshal(filtered); err == nil {
					settings = json.RawMessage(b)
				} else {
					settings = body.Settings
				}
			} else {
				settings = body.Settings
			}
		} else {
			settings = body.Settings
		}
	}

	pus := &store.PackageUserSettings{
		PackageID: pkgID,
		UserID:    userID,
		Settings:  settings,
		IsEnabled: enabled,
	}
	if err := h.stores.Packages.SetUserSettings(c.Request.Context(), pus); err != nil {
		c.JSON(500, gin.H{"error": "failed to save settings"})
		return
	}

	c.JSON(200, gin.H{"ok": true})
}

// ── Admin endpoints ─────────────────────────────

// AdminListExtensions returns all extension-type packages.
// GET /api/v1/admin/extensions
func (h *ExtensionHandler) AdminListExtensions(c *gin.Context) {
	pkgs, err := h.stores.Packages.ListByType(c.Request.Context(), "extension")
	if err != nil {
		c.JSON(500, gin.H{"error": "failed to list extensions"})
		return
	}
	// Also include 'full' type packages
	fullPkgs, err := h.stores.Packages.ListByType(c.Request.Context(), "full")
	if err == nil {
		pkgs = append(pkgs, fullPkgs...)
	}
	if pkgs == nil {
		pkgs = []store.PackageRegistration{}
	}
	c.JSON(200, gin.H{"data": pkgs})
}

// AdminInstallExtension installs an extension from a JSON body (legacy path).
// POST /api/v1/admin/extensions
// New installs should use POST /admin/packages/install with a .pkg archive.
func (h *ExtensionHandler) AdminInstallExtension(c *gin.Context) {
	userID := c.GetString("user_id")

	var body struct {
		ExtID       string          `json:"ext_id" binding:"required"`
		Name        string          `json:"name" binding:"required"`
		Version     string          `json:"version"`
		Tier        string          `json:"tier"`
		Description string          `json:"description"`
		Author      string          `json:"author"`
		Manifest    json.RawMessage `json:"manifest"`
		IsSystem    bool            `json:"is_system"`
		IsEnabled   bool            `json:"is_enabled"`
	}
	if err := c.ShouldBindJSON(&body); err != nil {
		c.JSON(400, gin.H{"error": "invalid request: " + err.Error()})
		return
	}

	// Defaults
	if body.Version == "" {
		body.Version = "0.0.0"
	}
	if body.Tier == "" {
		body.Tier = models.ExtTierBrowser
	}
	if body.Manifest == nil {
		body.Manifest = json.RawMessage("{}")
	}

	// Validate tier
	if !validTiers[body.Tier] {
		c.JSON(400, gin.H{"error": "invalid tier: must be browser, starlark, or sidecar"})
		return
	}

	// Check for duplicate (ext_id is now the package ID)
	existing, _ := h.stores.Packages.Get(c.Request.Context(), body.ExtID)
	if existing != nil {
		c.JSON(409, gin.H{"error": "extension with ext_id '" + body.ExtID + "' already installed"})
		return
	}

	// Parse manifest into map
	var manifestMap map[string]any
	if err := json.Unmarshal(body.Manifest, &manifestMap); err != nil {
		manifestMap = map[string]any{}
	}

	pkg := &store.PackageRegistration{
		ID:          body.ExtID,
		Title:       body.Name,
		Type:        "extension",
		Version:     body.Version,
		Description: body.Description,
		Author:      body.Author,
		Tier:        body.Tier,
		IsSystem:    body.IsSystem,
		Scope:       "global",
		Manifest:    manifestMap,
		Enabled:     body.IsEnabled,
		Source:      "extension",
		InstalledBy: &userID,
	}

	if err := h.stores.Packages.Create(c.Request.Context(), pkg); err != nil {
		if database.IsUniqueViolation(err) {
			c.JSON(409, gin.H{"error": "extension with ext_id '" + body.ExtID + "' already installed"})
			return
		}
		c.JSON(500, gin.H{"error": "failed to install extension"})
		return
	}

	// v0.29.0: Parse manifest permissions and declare them.
	// If permissions are declared, package moves to pending_review.
	SyncManifestPermissions(c, h.stores, pkg.ID, manifestMap)

	// v0.29.2: Create namespaced DB tables declared in the manifest.
	if tables, ok := ParseDBTables(manifestMap); ok {
		if err := CreateExtTables(c.Request.Context(), database.DB, database.IsPostgres(), h.stores, pkg.ID, tables); err != nil {
			log.Printf("[ext-db] schema create failed for %s: %v", pkg.ID, err)
		}
	}

	// v0.2.2: Sync triggers from manifest
	SyncManifestTriggers(c.Request.Context(), h.stores, triggers.GlobalEngine(), pkg.ID, manifestMap)

	c.JSON(201, gin.H{"data": pkg})
}

// AdminUpdateExtension updates an extension-type package.
// PUT /api/v1/admin/extensions/:id
func (h *ExtensionHandler) AdminUpdateExtension(c *gin.Context) {
	id := c.Param("id")

	pkg, err := h.stores.Packages.Get(c.Request.Context(), id)
	if err != nil || pkg == nil {
		c.JSON(404, gin.H{"error": "extension not found"})
		return
	}

	var body struct {
		Name        *string          `json:"name"`
		Version     *string          `json:"version"`
		Description *string          `json:"description"`
		Author      *string          `json:"author"`
		IsSystem    *bool            `json:"is_system"`
		IsEnabled   *bool            `json:"is_enabled"`
		Manifest    *json.RawMessage `json:"manifest"`
	}
	if err := c.ShouldBindJSON(&body); err != nil {
		c.JSON(400, gin.H{"error": "invalid request body"})
		return
	}

	if body.Name != nil {
		pkg.Title = *body.Name
	}
	if body.Version != nil {
		pkg.Version = *body.Version
	}
	if body.Description != nil {
		pkg.Description = *body.Description
	}
	if body.Author != nil {
		pkg.Author = *body.Author
	}
	if body.IsSystem != nil {
		pkg.IsSystem = *body.IsSystem
	}
	if body.IsEnabled != nil {
		pkg.Enabled = *body.IsEnabled
	}
	if body.Manifest != nil {
		var manifestMap map[string]any
		if err := json.Unmarshal(*body.Manifest, &manifestMap); err == nil {
			pkg.Manifest = manifestMap
		}
	}

	if err := h.stores.Packages.Update(c.Request.Context(), id, pkg); err != nil {
		c.JSON(500, gin.H{"error": "failed to update extension"})
		return
	}

	c.JSON(200, gin.H{"data": pkg})
}

// ServeExtensionAsset serves browser extension JS files.
// GET /api/v1/extensions/:id/assets/*path
func (h *ExtensionHandler) ServeExtensionAsset(c *gin.Context) {
	pkgID := c.Param("id")

	pkg, err := h.stores.Packages.Get(c.Request.Context(), pkgID)
	if err != nil || pkg == nil {
		c.JSON(404, gin.H{"error": "extension not found"})
		return
	}

	if !pkg.Enabled {
		c.JSON(404, gin.H{"error": "extension not enabled"})
		return
	}

	// Extract inline script from manifest
	manifestBytes := marshalManifest(pkg.Manifest)
	var manifest map[string]json.RawMessage
	if err := json.Unmarshal(manifestBytes, &manifest); err != nil {
		c.JSON(500, gin.H{"error": "invalid manifest"})
		return
	}

	scriptRaw, ok := manifest["_script"]
	if !ok {
		c.JSON(404, gin.H{"error": "no script in manifest"})
		return
	}

	var script string
	if err := json.Unmarshal(scriptRaw, &script); err != nil {
		c.JSON(500, gin.H{"error": "invalid script in manifest"})
		return
	}

	c.Header("Content-Type", "application/javascript; charset=utf-8")
	c.Header("Cache-Control", "public, max-age=3600")
	c.String(200, script)
}

// GetExtensionManifest returns the manifest for a specific extension.
// GET /api/v1/extensions/:id/manifest
func (h *ExtensionHandler) GetExtensionManifest(c *gin.Context) {
	pkgID := c.Param("id")

	pkg, err := h.stores.Packages.Get(c.Request.Context(), pkgID)
	if err != nil || pkg == nil {
		c.JSON(404, gin.H{"error": "extension not found"})
		return
	}

	c.JSON(200, gin.H{"data": pkg.Manifest})
}

// ListBrowserToolSchemas returns tool schemas from all enabled browser extensions.
// GET /api/v1/extensions/tools
func (h *ExtensionHandler) ListBrowserToolSchemas(c *gin.Context) {
	userID := c.GetString("user_id")

	pkgs, err := h.stores.Packages.ListForUser(c.Request.Context(), userID)
	if err != nil {
		c.JSON(500, gin.H{"error": "failed to list extensions"})
		return
	}

	var toolSchemas []json.RawMessage
	for _, pkg := range pkgs {
		if pkg.Tier != "browser" {
			continue
		}
		manifestBytes := marshalManifest(pkg.Manifest)
		var manifest struct {
			Tools []json.RawMessage `json:"tools"`
		}
		if err := json.Unmarshal(manifestBytes, &manifest); err != nil {
			log.Printf("[extensions] failed to parse manifest for %s: %v", pkg.ID, err)
			continue
		}
		toolSchemas = append(toolSchemas, manifest.Tools...)
	}

	if toolSchemas == nil {
		toolSchemas = make([]json.RawMessage, 0)
	}
	c.JSON(200, gin.H{"data": toolSchemas})
}

// AdminUninstallExtension removes an extension-type package.
// DELETE /api/v1/admin/extensions/:id
func (h *ExtensionHandler) AdminUninstallExtension(c *gin.Context) {
	id := c.Param("id")

	pkg, err := h.stores.Packages.Get(c.Request.Context(), id)
	if err != nil || pkg == nil {
		c.JSON(404, gin.H{"error": "extension not found"})
		return
	}

	// v0.29.2: Drop namespaced DB tables before removing the package record.
	if err := DropExtTables(c.Request.Context(), database.DB, h.stores, id); err != nil {
		log.Printf("[ext-db] schema drop failed for %s: %v", id, err)
	}

	if err := h.stores.Packages.Delete(c.Request.Context(), id); err != nil {
		if err == sql.ErrNoRows {
			c.JSON(400, gin.H{"error": "core packages cannot be deleted"})
			return
		}
		c.JSON(500, gin.H{"error": "failed to uninstall extension"})
		return
	}

	c.JSON(200, gin.H{"ok": true})
}

// marshalManifest converts map[string]any → []byte for struct unmarshaling.
// PackageRegistration.Manifest is map[string]any (from JSONB scan), but
// several callsites need to unmarshal into typed structs.
func marshalManifest(m map[string]any) []byte {
	if m == nil {
		return []byte("{}")
	}
	b, err := json.Marshal(m)
	if err != nil {
		return []byte("{}")
	}
	return b
}
