package app

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

	"github.com/gin-gonic/gin"
	"kra/pkg/module"
	platformtask "kra/pkg/task"
)

func TestCatalogMigrationIDsAreUnique(t *testing.T) {
	seen := map[string]string{}
	for _, definition := range Catalog().Definitions {
		for _, step := range definition.Migrations {
			if previous, exists := seen[step.ID]; exists {
				t.Fatalf("migration %q is declared by both %s and %s", step.ID, previous, definition.Name)
			}
			seen[step.ID] = definition.Name
		}
	}
}

func TestCatalogIncludesSystemDefinition(t *testing.T) {
	catalog := Catalog()
	if len(catalog.Definitions) != 4 {
		t.Fatalf("definitions = %d, want 4", len(catalog.Definitions))
	}
	if catalog.Definitions[0].Name != "system" || catalog.Definitions[1].Name != "integration" || catalog.Definitions[2].Name != "task" || catalog.Definitions[3].Name != "payment" {
		t.Fatalf("definition order = [%q, %q, %q, %q], want [system, integration, task, payment]", catalog.Definitions[0].Name, catalog.Definitions[1].Name, catalog.Definitions[2].Name, catalog.Definitions[3].Name)
	}
	if got := catalog.MigrationSteps(); len(got) != 8 {
		t.Fatalf("module migrations = %d, want 8", len(got))
	}
	if surface := catalog.Surface(); len(surface.Menus) != 3 || len(surface.APIs) != 15 {
		t.Fatalf("admin surface = %d menus/%d APIs, want 3/15", len(surface.Menus), len(surface.APIs))
	}
	if got := catalog.DefaultTimedTasks(); len(got) != 2 {
		t.Fatalf("default timed tasks = %d, want 2", len(got))
	}
}

func TestTaskRegistryRegistersStaticModuleMethods(t *testing.T) {
	method := platformtask.Method{
		Name: "test.static",
		Run:  func(context.Context, json.RawMessage) error { return nil },
	}
	catalog := module.Catalog{Definitions: []module.Definition{{Tasks: []platformtask.Method{method}}}}
	registry := TaskRegistry(catalog)
	if _, ok := registry.Lookup(method.Name); !ok {
		t.Fatalf("method %q was not registered", method.Name)
	}
}

type testRouteRegistrar struct{ called bool }

func (registrar *testRouteRegistrar) RegisterRoutes(*gin.RouterGroup, *gin.RouterGroup, *gin.Engine) {
	registrar.called = true
}

type testTaskContributor struct{ name string }

func (contributor testTaskContributor) RegisterTasks(registry *platformtask.Registry) {
	registry.Register(platformtask.Method{
		Name: contributor.name,
		Run:  func(context.Context, json.RawMessage) error { return nil },
	})
}

func TestRuntimeAppliesAllContributions(t *testing.T) {
	registry := platformtask.NewRegistry()
	route := &testRouteRegistrar{}
	runtime := Runtime(RuntimeContributions{
		Routes: []module.RouteRegistrar{route},
		Tasks:  []platformtask.Contributor{testTaskContributor{name: "test.runtime"}},
	}, registry)

	if _, ok := registry.Lookup("test.runtime"); !ok {
		t.Fatal("runtime task contributor was not applied")
	}
	runtime.RegisterRoutes(nil, nil, nil)
	if !route.called {
		t.Fatal("runtime route contributor was not called")
	}
}
