package handlers

import (
	"context"
	"encoding/json"
	"testing"

	"armature/database"
	"armature/store"
	postgres "armature/store/postgres"
	sqlite "armature/store/sqlite"
)

// Minimal static spec for testing — valid OpenAPI 3.0 structure.
var testStaticSpec = []byte(`
openapi: "3.0.3"
info:
  title: Armature
  version: "${VERSION}"
paths:
  /api/v1/health:
    get:
      summary: Health check
      responses:
        "200":
          description: OK
tags:
  - name: system
    description: System endpoints
`)

func openapiTestStores(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)
}

// ── Test: Zero extensions → kernel spec only ──────────────────

func TestBuildOpenAPISpec_NoExtensions(t *testing.T) {
	stores := openapiTestStores(t)

	spec, err := BuildOpenAPISpec(stores, testStaticSpec, "0.6.2")
	if err != nil {
		t.Fatalf("BuildOpenAPISpec failed: %v", err)
	}

	// Version should be substituted
	info, _ := spec["info"].(map[string]any)
	if info == nil {
		t.Fatal("missing info field")
	}
	if v, _ := info["version"].(string); v != "0.6.2" {
		t.Errorf("expected version 0.6.2, got %q", v)
	}

	// Paths should contain the kernel endpoint
	paths, _ := spec["paths"].(map[string]any)
	if paths == nil {
		t.Fatal("missing paths field")
	}
	if _, ok := paths["/api/v1/health"]; !ok {
		t.Error("expected /api/v1/health in paths")
	}

	// Tags should contain kernel tag
	tags, _ := spec["tags"].([]any)
	if len(tags) < 1 {
		t.Error("expected at least 1 tag")
	}
}

// ── Test: Extension with api_routes but no api_schema → stubs ────

func TestBuildOpenAPISpec_StubEntries(t *testing.T) {
	stores := openapiTestStores(t)

	// Install a test extension with api_routes
	manifest := map[string]any{
		"api_routes": []any{
			map[string]any{"method": "GET", "path": "/items"},
			map[string]any{"method": "POST", "path": "/items"},
		},
	}
	manifestJSON, _ := json.Marshal(manifest)
	err := stores.Packages.Create(context.Background(), &store.PackageRegistration{
		ID:       "test-ext",
		Title:    "Test Extension",
		Type:     "extension",
		Version:  "1.0.0",
		Tier:     "starlark",
		Manifest: manifest,
		Enabled:  true,
		Status:   "active",
		Source:   "extension",
		Scope:    "global",
	})
	_ = manifestJSON
	if err != nil {
		t.Fatalf("failed to create test package: %v", err)
	}

	spec, err := BuildOpenAPISpec(stores, testStaticSpec, "0.6.2")
	if err != nil {
		t.Fatalf("BuildOpenAPISpec failed: %v", err)
	}

	paths, _ := spec["paths"].(map[string]any)

	// Check GET stub
	getPath, ok := paths["/s/test-ext/api/items"]
	if !ok {
		t.Fatal("expected /s/test-ext/api/items in paths")
	}
	pathItem, _ := getPath.(map[string]any)
	getOp, _ := pathItem["get"].(map[string]any)
	if getOp == nil {
		t.Fatal("expected GET operation")
	}
	summary, _ := getOp["summary"].(string)
	if summary != "test-ext: GET /items" {
		t.Errorf("unexpected stub summary: %q", summary)
	}

	// Check POST stub
	postOp, _ := pathItem["post"].(map[string]any)
	if postOp == nil {
		t.Fatal("expected POST operation")
	}

	// Check tag
	tags, _ := getOp["tags"].([]any)
	if len(tags) != 1 || tags[0] != "extensions/test-ext" {
		t.Errorf("unexpected tags: %v", tags)
	}
}

// ── Test: Extension with api_schema → rich entries ────────

func TestBuildOpenAPISpec_SchemaEntries(t *testing.T) {
	stores := openapiTestStores(t)

	manifest := map[string]any{
		"api_routes": []any{
			map[string]any{"method": "GET", "path": "/items"},
			map[string]any{"method": "POST", "path": "/items"},
			map[string]any{"method": "DELETE", "path": "/items/:id"},
		},
		"api_schema": []any{
			map[string]any{
				"path":    "/items",
				"method":  "GET",
				"summary": "List items",
				"params": map[string]any{
					"limit": map[string]any{"type": "integer", "default": 50},
				},
				"response": map[string]any{
					"type":    "object",
					"example": map[string]any{"data": []any{}},
				},
			},
			map[string]any{
				"path":    "/items",
				"method":  "POST",
				"summary": "Create item",
				"body": map[string]any{
					"name":        map[string]any{"type": "string", "required": true},
					"description": map[string]any{"type": "string"},
				},
			},
		},
	}

	err := stores.Packages.Create(context.Background(), &store.PackageRegistration{
		ID:       "rich-ext",
		Title:    "Rich Extension",
		Type:     "extension",
		Version:  "1.0.0",
		Tier:     "starlark",
		Manifest: manifest,
		Enabled:  true,
		Status:   "active",
		Source:   "extension",
		Scope:    "global",
	})
	if err != nil {
		t.Fatalf("failed to create test package: %v", err)
	}

	spec, err := BuildOpenAPISpec(stores, testStaticSpec, "0.6.2")
	if err != nil {
		t.Fatalf("BuildOpenAPISpec failed: %v", err)
	}

	paths, _ := spec["paths"].(map[string]any)

	// GET /items should have rich schema (summary = "List items")
	itemsPath, ok := paths["/s/rich-ext/api/items"]
	if !ok {
		t.Fatal("expected /s/rich-ext/api/items in paths")
	}
	pathItem, _ := itemsPath.(map[string]any)
	getOp, _ := pathItem["get"].(map[string]any)
	if getOp == nil {
		t.Fatal("expected GET operation")
	}
	if s, _ := getOp["summary"].(string); s != "List items" {
		t.Errorf("expected rich summary, got %q", s)
	}
	// Should have parameters
	params, _ := getOp["parameters"].([]any)
	if len(params) == 0 {
		t.Error("expected parameters from api_schema")
	}

	// POST /items should have requestBody
	postOp, _ := pathItem["post"].(map[string]any)
	if postOp == nil {
		t.Fatal("expected POST operation")
	}
	if _, ok := postOp["requestBody"]; !ok {
		t.Error("expected requestBody from api_schema body")
	}

	// DELETE /items/:id should be a stub (no api_schema for it)
	deletePath, ok := paths["/s/rich-ext/api/items/:id"]
	if !ok {
		t.Fatal("expected /s/rich-ext/api/items/:id in paths")
	}
	deleteItem, _ := deletePath.(map[string]any)
	deleteOp, _ := deleteItem["delete"].(map[string]any)
	if deleteOp == nil {
		t.Fatal("expected DELETE operation")
	}
	deleteSummary, _ := deleteOp["summary"].(string)
	if deleteSummary != "rich-ext: DELETE /items/:id" {
		t.Errorf("expected stub summary for undeclared route, got %q", deleteSummary)
	}
}

// ── Test: Multiple extensions → all appear ────────

func TestBuildOpenAPISpec_MultipleExtensions(t *testing.T) {
	stores := openapiTestStores(t)

	for _, ext := range []struct {
		id    string
		title string
		route string
	}{
		{"ext-a", "Extension A", "/status"},
		{"ext-b", "Extension B", "/health"},
	} {
		err := stores.Packages.Create(context.Background(), &store.PackageRegistration{
			ID:      ext.id,
			Title:   ext.title,
			Type:    "extension",
			Version: "1.0.0",
			Tier:    "starlark",
			Manifest: map[string]any{
				"api_routes": []any{
					map[string]any{"method": "GET", "path": ext.route},
				},
			},
			Enabled: true,
			Status:  "active",
			Source:  "extension",
			Scope:   "global",
		})
		if err != nil {
			t.Fatalf("failed to create %s: %v", ext.id, err)
		}
	}

	spec, err := BuildOpenAPISpec(stores, testStaticSpec, "0.6.2")
	if err != nil {
		t.Fatalf("BuildOpenAPISpec failed: %v", err)
	}

	paths, _ := spec["paths"].(map[string]any)
	if _, ok := paths["/s/ext-a/api/status"]; !ok {
		t.Error("expected ext-a path")
	}
	if _, ok := paths["/s/ext-b/api/health"]; !ok {
		t.Error("expected ext-b path")
	}

	// Verify tags
	tags, _ := spec["tags"].([]any)
	tagNames := make(map[string]bool)
	for _, tag := range tags {
		if m, ok := tag.(map[string]any); ok {
			if n, ok := m["name"].(string); ok {
				tagNames[n] = true
			}
		}
	}
	if !tagNames["extensions/ext-a"] {
		t.Error("expected extensions/ext-a tag")
	}
	if !tagNames["extensions/ext-b"] {
		t.Error("expected extensions/ext-b tag")
	}
}

// ── Test: Malformed api_schema → stubs, no crash ────────

func TestBuildOpenAPISpec_MalformedSchema(t *testing.T) {
	stores := openapiTestStores(t)

	manifest := map[string]any{
		"api_routes": []any{
			map[string]any{"method": "GET", "path": "/data"},
		},
		"api_schema": []any{
			"not-an-object",                                    // malformed
			map[string]any{"path": "/data"},                    // missing method
			map[string]any{"method": "GET"},                    // missing path
			map[string]any{"path": "/data", "method": "POST"}, // valid but no matching route
		},
	}

	err := stores.Packages.Create(context.Background(), &store.PackageRegistration{
		ID:       "bad-schema",
		Title:    "Bad Schema",
		Type:     "extension",
		Version:  "1.0.0",
		Tier:     "starlark",
		Manifest: manifest,
		Enabled:  true,
		Status:   "active",
		Source:   "extension",
		Scope:    "global",
	})
	if err != nil {
		t.Fatalf("failed to create package: %v", err)
	}

	spec, err := BuildOpenAPISpec(stores, testStaticSpec, "0.6.2")
	if err != nil {
		t.Fatalf("BuildOpenAPISpec should not error on malformed schema: %v", err)
	}

	paths, _ := spec["paths"].(map[string]any)
	// GET /data should still appear as a stub
	if _, ok := paths["/s/bad-schema/api/data"]; !ok {
		t.Error("expected stub entry for /data despite malformed api_schema")
	}
}

// ── Test: Disabled extension → excluded ────────

func TestBuildOpenAPISpec_DisabledExcluded(t *testing.T) {
	stores := openapiTestStores(t)

	err := stores.Packages.Create(context.Background(), &store.PackageRegistration{
		ID:      "disabled-ext",
		Title:   "Disabled",
		Type:    "extension",
		Version: "1.0.0",
		Tier:    "starlark",
		Manifest: map[string]any{
			"api_routes": []any{
				map[string]any{"method": "GET", "path": "/secret"},
			},
		},
		Enabled: false,
		Status:  "active",
		Source:  "extension",
		Scope:   "global",
	})
	if err != nil {
		t.Fatalf("failed to create package: %v", err)
	}

	spec, err := BuildOpenAPISpec(stores, testStaticSpec, "0.6.2")
	if err != nil {
		t.Fatalf("BuildOpenAPISpec failed: %v", err)
	}

	paths, _ := spec["paths"].(map[string]any)
	if _, ok := paths["/s/disabled-ext/api/secret"]; ok {
		t.Error("disabled extension should not appear in spec")
	}
}

// ── Test: Required OpenAPI 3.0 fields present ────────

func TestBuildOpenAPISpec_RequiredFields(t *testing.T) {
	stores := openapiTestStores(t)

	spec, err := BuildOpenAPISpec(stores, testStaticSpec, "1.0.0")
	if err != nil {
		t.Fatalf("BuildOpenAPISpec failed: %v", err)
	}

	if _, ok := spec["openapi"]; !ok {
		t.Error("missing required 'openapi' field")
	}
	if _, ok := spec["info"]; !ok {
		t.Error("missing required 'info' field")
	}
	if _, ok := spec["paths"]; !ok {
		t.Error("missing required 'paths' field")
	}
}
