package handlers

// package_registry.go — v0.30.0 CS4
//
// Package registry (marketplace discovery). Admin browses available
// packages from an external JSON registry and installs them by URL.
//
// Registry format (external JSON file):
//   {
//     "packages": [
//       {"id": "...", "title": "...", "version": "...", "description": "...",
//        "author": "...", "type": "...", "tier": "...",
//        "download_url": "https://..."}
//     ]
//   }
//
// Registry URL is stored in global_config under key "package_registry".

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"strings"
	"sync"
	"time"

	"github.com/gin-gonic/gin"

	"switchboard-core/store"
)

// RegistryEntry represents a single package in the registry.
type RegistryEntry struct {
	ID          string `json:"id"`
	Title       string `json:"title"`
	Version     string `json:"version"`
	Description string `json:"description"`
	Author      string `json:"author"`
	Type        string `json:"type"`
	Tier        string `json:"tier"`
	DownloadURL string `json:"download_url"`
	Size        int64  `json:"size,omitempty"`
	UpdatedAt   string `json:"updated_at,omitempty"`
}

// RegistryResponse is the top-level registry JSON structure.
type RegistryResponse struct {
	Packages []RegistryEntry `json:"packages"`
}

// RegistryHandler handles registry browse and install operations.
type RegistryHandler struct {
	stores      store.Stores
	packagesDir string
	pkgHandler  *PackageHandler
	client      *http.Client

	cacheMu    sync.RWMutex
	cacheData  *RegistryResponse
	cacheURL   string
	cacheUntil time.Time
}

const (
	registryCacheTTL     = 5 * time.Minute
	registryFetchTimeout = 10 * time.Second
	registryMaxSize      = 50 * 1024 * 1024 // 50MB
)

// NewRegistryHandler creates a new registry handler.
func NewRegistryHandler(s store.Stores, packagesDir string, pkgHandler *PackageHandler) *RegistryHandler {
	return &RegistryHandler{
		stores:      s,
		packagesDir: packagesDir,
		pkgHandler:  pkgHandler,
		client: &http.Client{
			Timeout: registryFetchTimeout,
		},
	}
}

// getRegistryURL reads the registry URL from global_config.
func (h *RegistryHandler) getRegistryURL(c *gin.Context) string {
	if h.stores.GlobalConfig == nil {
		return ""
	}
	val, err := h.stores.GlobalConfig.Get(c.Request.Context(), "package_registry")
	if err != nil {
		return ""
	}
	url, _ := val["url"].(string)
	return url
}

// BrowseRegistry fetches and returns the registry package list.
// GET /api/v1/admin/packages/registry
func (h *RegistryHandler) BrowseRegistry(c *gin.Context) {
	url := h.getRegistryURL(c)
	if url == "" {
		c.JSON(http.StatusOK, gin.H{
			"data":[]RegistryEntry{},
			"registry_url": "",
		})
		return
	}

	// Check cache
	h.cacheMu.RLock()
	if h.cacheData != nil && h.cacheURL == url && time.Now().Before(h.cacheUntil) {
		data := h.cacheData
		h.cacheMu.RUnlock()
		c.JSON(http.StatusOK, gin.H{
			"data":data.Packages,
			"registry_url": url,
		})
		return
	}
	h.cacheMu.RUnlock()

	// Fetch registry
	data, err := h.fetchRegistry(url)
	if err != nil {
		log.Printf("[registry] fetch failed: %v", err)
		c.JSON(http.StatusBadGateway, gin.H{"error": "failed to fetch registry: " + err.Error()})
		return
	}

	// Update cache
	h.cacheMu.Lock()
	h.cacheData = data
	h.cacheURL = url
	h.cacheUntil = time.Now().Add(registryCacheTTL)
	h.cacheMu.Unlock()

	// Mark installed packages
	installed := make(map[string]bool)
	pkgs, _ := h.stores.Packages.List(c.Request.Context())
	for _, p := range pkgs {
		installed[p.ID] = true
	}

	type enrichedEntry struct {
		RegistryEntry
		Installed bool `json:"installed"`
	}

	entries := make([]enrichedEntry, len(data.Packages))
	for i, p := range data.Packages {
		entries[i] = enrichedEntry{
			RegistryEntry: p,
			Installed:     installed[p.ID],
		}
	}

	c.JSON(http.StatusOK, gin.H{
		"data":entries,
		"registry_url": url,
	})
}

// InstallFromRegistry downloads a .pkg from a URL and installs it.
// POST /api/v1/admin/packages/registry/install
func (h *RegistryHandler) InstallFromRegistry(c *gin.Context) {
	var body struct {
		DownloadURL string `json:"download_url"`
	}
	if err := c.ShouldBindJSON(&body); err != nil || body.DownloadURL == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "download_url is required"})
		return
	}

	// Security: HTTPS only
	if !strings.HasPrefix(body.DownloadURL, "https://") {
		c.JSON(http.StatusBadRequest, gin.H{"error": "download_url must use HTTPS"})
		return
	}

	// Download the package
	resp, err := h.client.Get(body.DownloadURL)
	if err != nil {
		c.JSON(http.StatusBadGateway, gin.H{"error": "failed to download package: " + err.Error()})
		return
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		c.JSON(http.StatusBadGateway, gin.H{
			"error": fmt.Sprintf("registry returned HTTP %d", resp.StatusCode),
		})
		return
	}

	// Write to temp file with size limit
	tmpFile, err := os.CreateTemp("", "registry-pkg-*.pkg")
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create temp file"})
		return
	}
	tmpPath := tmpFile.Name()
	defer os.Remove(tmpPath)

	limited := io.LimitReader(resp.Body, registryMaxSize)
	n, err := io.Copy(tmpFile, limited)
	tmpFile.Close()
	if err != nil {
		c.JSON(http.StatusBadGateway, gin.H{"error": "failed to download package"})
		return
	}

	log.Printf("[registry] downloaded %d bytes from %s", n, body.DownloadURL)

	// Open and re-serve through the package install handler by creating
	// a synthetic multipart request. Instead, we directly open the file
	// and call the shared install logic.
	file, err := os.Open(tmpPath)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to read downloaded package"})
		return
	}
	defer file.Close()

	// Use the existing install flow by setting the file on the request
	// We'll create a helper that accepts an io.ReadSeeker
	c.Set("_registry_file", tmpPath)
	c.Set("_registry_source", "registry")
	h.pkgHandler.InstallPackage(c)
}

// fetchRegistry downloads and parses a registry JSON file.
func (h *RegistryHandler) fetchRegistry(url string) (*RegistryResponse, error) {
	if !strings.HasPrefix(url, "https://") {
		return nil, fmt.Errorf("registry URL must use HTTPS")
	}

	resp, err := h.client.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("registry returned HTTP %d", resp.StatusCode)
	}

	limited := io.LimitReader(resp.Body, 10*1024*1024) // 10MB for registry JSON
	data, err := io.ReadAll(limited)
	if err != nil {
		return nil, err
	}

	var reg RegistryResponse
	if err := json.Unmarshal(data, &reg); err != nil {
		return nil, fmt.Errorf("invalid registry JSON: %w", err)
	}

	return &reg, nil
}
