package handlers

import (
	"archive/zip"
	"context"
	"encoding/json"
	"os"
	"path/filepath"
	"testing"

	"switchboard-core/database"
	"switchboard-core/store"
	postgres "switchboard-core/store/postgres"
	sqlite "switchboard-core/store/sqlite"
)

// ── Test helpers ──────────────────────────────

func newTestStores(t *testing.T) store.Stores {
	t.Helper()
	database.RequireTestDB(t)
	database.TruncateAll(t)

	if database.IsSQLite() {
		return sqlite.NewStores(database.TestDB)
	}
	return postgres.NewStores(database.TestDB)
}

// buildTestPkg creates a minimal .pkg archive in dir with the given manifest.
func buildTestPkg(t *testing.T, dir string, manifest map[string]any) string {
	t.Helper()

	pkgID, _ := manifest["id"].(string)
	if pkgID == "" {
		t.Fatal("manifest must have 'id'")
	}

	data, err := json.Marshal(manifest)
	if err != nil {
		t.Fatal(err)
	}

	pkgPath := filepath.Join(dir, pkgID+".pkg")
	f, err := os.Create(pkgPath)
	if err != nil {
		t.Fatal(err)
	}
	defer f.Close()

	zw := zip.NewWriter(f)
	w, err := zw.Create("manifest.json")
	if err != nil {
		t.Fatal(err)
	}
	if _, err := w.Write(data); err != nil {
		t.Fatal(err)
	}
	if err := zw.Close(); err != nil {
		t.Fatal(err)
	}

	return pkgPath
}

// ═══════════════════════════════════════════════
// Tests
// ═══════════════════════════════════════════════

// TestBundledInstall_FreshDB verifies that bundled packages are installed
// into an empty database on first run.
func TestBundledInstall_FreshDB(t *testing.T) {
	stores := newTestStores(t)
	ctx := context.Background()

	bundledDir := t.TempDir()
	packagesDir := t.TempDir()

	buildTestPkg(t, bundledDir, map[string]any{
		"id":    "test-surface-a",
		"title": "Test Surface A",
		"type":  "surface",
	})
	buildTestPkg(t, bundledDir, map[string]any{
		"id":    "test-surface-b",
		"title": "Test Surface B",
		"type":  "surface",
		"route": "/s/test-b",
	})

	InstallBundledPackages(bundledDir, packagesDir, "", stores, nil)

	// Both packages should be installed and enabled
	for _, id := range []string{"test-surface-a", "test-surface-b"} {
		pkg, err := stores.Packages.Get(ctx, id)
		if err != nil || pkg == nil {
			t.Fatalf("package %s not found after install", id)
		}
		if !pkg.Enabled {
			t.Errorf("package %s should be enabled", id)
		}
		if pkg.Source != "bundled" {
			t.Errorf("package %s source = %q, want %q", id, pkg.Source, "bundled")
		}
	}
}

// TestBundledInstall_SkipsExisting verifies that already-installed packages
// are not re-installed (preserves admin uninstall/reconfigure).
func TestBundledInstall_SkipsExisting(t *testing.T) {
	stores := newTestStores(t)
	ctx := context.Background()

	bundledDir := t.TempDir()
	packagesDir := t.TempDir()

	// Pre-seed a package with same ID but different title (simulating existing install)
	manifest := map[string]any{"id": "pre-existing", "title": "Original Title", "type": "surface"}
	if err := stores.Packages.Seed(ctx, "pre-existing", "Original Title", "extension", manifest); err != nil {
		t.Fatal(err)
	}

	// Bundled package has same ID but different title
	buildTestPkg(t, bundledDir, map[string]any{
		"id":    "pre-existing",
		"title": "Bundled Title",
		"type":  "surface",
	})

	InstallBundledPackages(bundledDir, packagesDir, "", stores, nil)

	// Package should retain original title (not overwritten)
	pkg, err := stores.Packages.Get(ctx, "pre-existing")
	if err != nil || pkg == nil {
		t.Fatal("pre-existing package not found")
	}
	if pkg.Title != "Original Title" {
		t.Errorf("title = %q, want %q (should not overwrite existing)", pkg.Title, "Original Title")
	}
}

// TestBundledInstall_MissingDir verifies graceful handling when bundled
// packages directory doesn't exist.
func TestBundledInstall_MissingDir(t *testing.T) {
	stores := newTestStores(t)

	// Should not panic or error — just log and return
	InstallBundledPackages("/nonexistent/path", "", "", stores, nil)
}

// TestBundledInstall_EmptyDir verifies no-op when directory exists but is empty.
func TestBundledInstall_EmptyDir(t *testing.T) {
	stores := newTestStores(t)

	bundledDir := t.TempDir()

	// Should not panic or error
	InstallBundledPackages(bundledDir, "", "", stores, nil)
}

// TestBundledInstall_DormantPackage verifies that bundled packages with
// unmet requires are marked dormant.
func TestBundledInstall_DormantPackage(t *testing.T) {
	stores := newTestStores(t)
	ctx := context.Background()

	bundledDir := t.TempDir()
	packagesDir := t.TempDir()

	buildTestPkg(t, bundledDir, map[string]any{
		"id":       "dormant-pkg",
		"title":    "Dormant Package",
		"type":     "extension",
		"requires": []string{"chat"},
	})

	InstallBundledPackages(bundledDir, packagesDir, "", stores, nil)

	pkg, err := stores.Packages.Get(ctx, "dormant-pkg")
	if err != nil || pkg == nil {
		t.Fatal("dormant package not found after install")
	}
	if pkg.Status != "dormant" {
		t.Errorf("status = %q, want %q", pkg.Status, "dormant")
	}
	if pkg.Enabled {
		t.Error("dormant package should not be enabled")
	}
}

// TestBundledInstall_Allowlist verifies that BUNDLED_PACKAGES allowlist
// filters which packages are installed.
func TestBundledInstall_Allowlist(t *testing.T) {
	stores := newTestStores(t)
	ctx := context.Background()

	bundledDir := t.TempDir()
	packagesDir := t.TempDir()

	buildTestPkg(t, bundledDir, map[string]any{
		"id": "pkg-alpha", "title": "Alpha", "type": "surface",
	})
	buildTestPkg(t, bundledDir, map[string]any{
		"id": "pkg-beta", "title": "Beta", "type": "surface",
	})
	buildTestPkg(t, bundledDir, map[string]any{
		"id": "pkg-gamma", "title": "Gamma", "type": "surface",
	})

	// Only allow alpha and gamma
	InstallBundledPackages(bundledDir, packagesDir, "pkg-alpha,pkg-gamma", stores, nil)

	// Alpha and gamma should be installed
	for _, id := range []string{"pkg-alpha", "pkg-gamma"} {
		pkg, err := stores.Packages.Get(ctx, id)
		if err != nil || pkg == nil {
			t.Fatalf("package %s should have been installed (in allowlist)", id)
		}
	}

	// Beta should NOT be installed
	pkg, _ := stores.Packages.Get(ctx, "pkg-beta")
	if pkg != nil {
		t.Fatal("pkg-beta should NOT have been installed (not in allowlist)")
	}
}
