package handlers

import (
	"context"
	"encoding/json"
	"log"
	"os"
	"path/filepath"

	"chat-switchboard/store"
)

// builtinManifest is the on-disk manifest.json shape.
type builtinManifest struct {
	ID          string          `json:"id"`
	Name        string          `json:"name"`
	Version     string          `json:"version"`
	Tier        string          `json:"tier"`
	Author      string          `json:"author"`
	Description string          `json:"description"`
	Permissions json.RawMessage `json:"permissions"`
	Tools       json.RawMessage `json:"tools"`
	Surfaces    json.RawMessage `json:"surfaces"`
	Settings    json.RawMessage `json:"settings"`
}

// SeedBuiltinPackages scans a directory for builtin extension bundles
// (each subdirectory contains manifest.json + script.js) and upserts them
// into the packages table as system packages.
//
// Layout:
//
//	extensions/builtin/
//	  mermaid-renderer/
//	    manifest.json
//	    script.js
//
// Idempotent: skips packages whose version matches what's already installed,
// updates those with a newer version, creates new ones.
func SeedBuiltinPackages(stores store.Stores, extensionsDir string) {
	if stores.Packages == nil {
		return
	}

	entries, err := os.ReadDir(extensionsDir)
	if err != nil {
		if os.IsNotExist(err) {
			log.Printf("  📦 No builtin extensions directory at %s (skipping)", extensionsDir)
			return
		}
		log.Printf("⚠ Failed to read extensions directory %s: %v", extensionsDir, err)
		return
	}

	ctx := context.Background()
	seeded, updated, skipped := 0, 0, 0

	for _, entry := range entries {
		if !entry.IsDir() {
			continue
		}

		extDir := filepath.Join(extensionsDir, entry.Name())
		manifestPath := filepath.Join(extDir, "manifest.json")
		scriptPath := filepath.Join(extDir, "script.js")

		// Read manifest
		manifestData, err := os.ReadFile(manifestPath)
		if err != nil {
			log.Printf("⚠ Skipping %s: no manifest.json: %v", entry.Name(), err)
			continue
		}

		var manifest builtinManifest
		if err := json.Unmarshal(manifestData, &manifest); err != nil {
			log.Printf("⚠ Skipping %s: invalid manifest.json: %v", entry.Name(), err)
			continue
		}

		if manifest.ID == "" {
			log.Printf("⚠ Skipping %s: manifest missing 'id'", entry.Name())
			continue
		}

		// Read script (optional — some extensions might be manifest-only)
		var script string
		if scriptData, err := os.ReadFile(scriptPath); err == nil {
			script = string(scriptData)
		}

		// Build the full manifest with _script inlined
		fullManifest := buildFullManifest(manifestData, script)

		// Parse into map for Seed/Update
		var manifestMap map[string]any
		json.Unmarshal(fullManifest, &manifestMap)

		// Check if already installed
		existing, err := stores.Packages.Get(ctx, manifest.ID)
		if err == nil && existing != nil {
			// Resolve tier for fixup + update paths
			tier := manifest.Tier
			if tier == "" {
				tier = "browser"
			}

			// Same version — skip, but fixup type/tier if wrong
			// (v0.28.7 initial seed created builtins with type='surface'
			// because Seed() uses schema DEFAULT and Update() didn't set type)
			if existing.Version == manifest.Version {
				if existing.Type != "extension" || existing.Tier != tier {
					pkg := &store.PackageRegistration{
						Title: existing.Title, Type: "extension", Version: existing.Version,
						Description: existing.Description, Author: existing.Author,
						Tier: tier, Manifest: existing.Manifest,
						IsSystem: true, Enabled: existing.Enabled,
					}
					if err := stores.Packages.Update(ctx, manifest.ID, pkg); err != nil {
						log.Printf("⚠ Failed to fixup builtin package %s: %v", manifest.ID, err)
					}
				}
				skipped++
				continue
			}
			// Version changed — update
			pkg := &store.PackageRegistration{
				Title:       manifest.Name,
				Type:        "extension",
				Version:     manifest.Version,
				Description: manifest.Description,
				Author:      manifest.Author,
				Tier:        tier,
				Manifest:    manifestMap,
				IsSystem:    true,
				Enabled:     true,
			}
			if err := stores.Packages.Update(ctx, manifest.ID, pkg); err != nil {
				log.Printf("⚠ Failed to update builtin package %s: %v", manifest.ID, err)
				continue
			}
			updated++
			continue
		}

		// New package — seed with full metadata
		if err := stores.Packages.Seed(ctx, manifest.ID, manifest.Name, "builtin", manifestMap); err != nil {
			log.Printf("⚠ Failed to seed builtin package %s: %v", manifest.ID, err)
			continue
		}

		// Set extension-specific fields that Seed doesn't cover
		tier := manifest.Tier
		if tier == "" {
			tier = "browser"
		}
		pkg := &store.PackageRegistration{
			Title:       manifest.Name,
			Type:        "extension",
			Version:     manifest.Version,
			Description: manifest.Description,
			Author:      manifest.Author,
			Tier:        tier,
			Manifest:    manifestMap,
			IsSystem:    true,
			Enabled:     true,
		}
		if err := stores.Packages.Update(ctx, manifest.ID, pkg); err != nil {
			log.Printf("⚠ Failed to update builtin package metadata %s: %v", manifest.ID, err)
		}
		seeded++
	}

	if seeded+updated > 0 {
		log.Printf("  📦 Builtin packages: %d seeded, %d updated, %d unchanged", seeded, updated, skipped)
	} else if skipped > 0 {
		log.Printf("  📦 Builtin packages: %d up to date", skipped)
	}
}

// buildFullManifest merges the raw manifest JSON with an inline _script field.
func buildFullManifest(rawManifest []byte, script string) json.RawMessage {
	if script == "" {
		return json.RawMessage(rawManifest)
	}

	var m map[string]json.RawMessage
	if err := json.Unmarshal(rawManifest, &m); err != nil {
		return json.RawMessage(rawManifest)
	}

	scriptJSON, err := json.Marshal(script)
	if err != nil {
		return json.RawMessage(rawManifest)
	}

	m["_script"] = json.RawMessage(scriptJSON)
	result, err := json.Marshal(m)
	if err != nil {
		return json.RawMessage(rawManifest)
	}

	return json.RawMessage(result)
}
