package router

import (
	"context"
	"strings"
	"testing"
	"time"

	"go.uber.org/zap"
	runtime "iop/packages/go/execution"
)

type stubAdapter struct{ name string }

func (s *stubAdapter) Name() string { return s.name }
func (s *stubAdapter) Capabilities(_ context.Context) (runtime.Capabilities, error) {
	return runtime.Capabilities{AdapterName: s.name}, nil
}
func (s *stubAdapter) Execute(_ context.Context, _ runtime.ExecutionSpec, _ runtime.EventSink) error {
	return nil
}

func TestResolvePreservesCorrelationFields(t *testing.T) {
	reg := runtime.NewRegistry()
	reg.Register(&stubAdapter{name: "mock"})
	r := New(reg, zap.NewNop())

	req := runtime.RunRequest{
		RunID:      "run-1",
		Adapter:    "mock",
		Target:     "echo",
		SessionID:  "correlation-a",
		Background: true,
	}

	spec, err := r.Resolve(context.Background(), req)
	if err != nil {
		t.Fatalf("resolve: %v", err)
	}
	if spec.SessionID != req.SessionID {
		t.Errorf("SessionID: got %q want %q", spec.SessionID, req.SessionID)
	}
	if spec.Background != req.Background {
		t.Errorf("Background: got %v want %v", spec.Background, req.Background)
	}
}

func TestResolveAdapter_Found(t *testing.T) {
	reg := runtime.NewRegistry()
	expected := &stubAdapter{name: "cli"}
	reg.Register(expected)
	r := New(reg, zap.NewNop())

	req := runtime.RunRequest{
		RunID:   "run-1",
		Adapter: "cli",
		Target:  "codex",
	}

	spec, adapter, err := r.ResolveAdapter(context.Background(), req)
	if err != nil {
		t.Fatalf("ResolveAdapter: %v", err)
	}
	if spec.Adapter != "cli" {
		t.Errorf("expected adapter name 'cli', got %q", spec.Adapter)
	}
	if adapter != expected {
		t.Fatal("expected same adapter instance")
	}
}

func TestResolveAdapter_NotFound(t *testing.T) {
	reg := runtime.NewRegistry()
	r := New(reg, zap.NewNop())

	req := runtime.RunRequest{
		RunID:   "run-1",
		Adapter: "nonexistent",
		Target:  "codex",
	}

	_, _, err := r.ResolveAdapter(context.Background(), req)
	if err == nil {
		t.Fatal("expected error for unregistered adapter")
	}
	if !strings.Contains(err.Error(), "not found") {
		t.Fatalf("expected 'not found' in error, got %v", err)
	}
}

func TestResolveAdapter_RequiresExplicitAdapter(t *testing.T) {
	reg := runtime.NewRegistry()
	reg.Register(&stubAdapter{name: "mock"})
	r := New(reg, zap.NewNop())

	req := runtime.RunRequest{
		RunID:  "run-1",
		Target: "mock-echo",
	}

	_, _, err := r.ResolveAdapter(context.Background(), req)
	if err == nil {
		t.Fatal("expected error for empty adapter")
	}
	if !strings.Contains(err.Error(), "adapter is required") {
		t.Fatalf("expected adapter required error, got %v", err)
	}
}

func TestResolveAdapter_InstanceKeyLookup(t *testing.T) {
	reg := runtime.NewRegistry()
	local := &stubAdapter{name: "ollama@local"}
	dgx := &stubAdapter{name: "ollama@dgx"}
	reg.RegisterKeyed("ollama@local", "ollama", local)
	reg.RegisterKeyed("ollama@dgx", "ollama", dgx)
	r := New(reg, zap.NewNop())

	spec, adapter, err := r.ResolveAdapter(context.Background(), runtime.RunRequest{
		RunID:   "run-1",
		Adapter: "ollama@local",
		Target:  "llama3",
	})
	if err != nil {
		t.Fatalf("ResolveAdapter: %v", err)
	}
	if spec.Adapter != "ollama@local" {
		t.Errorf("spec.Adapter: got %q want %q", spec.Adapter, "ollama@local")
	}
	if adapter != local {
		t.Fatal("expected ollama@local adapter instance")
	}

	spec2, adapter2, err := r.ResolveAdapter(context.Background(), runtime.RunRequest{
		RunID:   "run-2",
		Adapter: "ollama@dgx",
		Target:  "mixtral",
	})
	if err != nil {
		t.Fatalf("ResolveAdapter dgx: %v", err)
	}
	if spec2.Adapter != "ollama@dgx" {
		t.Errorf("spec2.Adapter: got %q want %q", spec2.Adapter, "ollama@dgx")
	}
	if adapter2 != dgx {
		t.Fatal("expected ollama@dgx adapter instance")
	}
}

func TestResolveAdapter_AmbiguousLegacyLookup(t *testing.T) {
	reg := runtime.NewRegistry()
	reg.RegisterKeyed("ollama@local", "ollama", &stubAdapter{name: "ollama@local"})
	reg.RegisterKeyed("ollama@dgx", "ollama", &stubAdapter{name: "ollama@dgx"})
	r := New(reg, zap.NewNop())

	_, _, err := r.ResolveAdapter(context.Background(), runtime.RunRequest{
		RunID:   "run-1",
		Adapter: "ollama",
		Target:  "llama3",
	})
	if err == nil {
		t.Fatal("expected error for ambiguous adapter type-name with multiple instances")
	}
	if !strings.Contains(err.Error(), "ambiguous") {
		t.Errorf("expected 'ambiguous' in error, got %v", err)
	}
}

func TestResolveAdapter_LegacyTypeName_SingleInstance(t *testing.T) {
	reg := runtime.NewRegistry()
	single := &stubAdapter{name: "ollama@prod"}
	reg.RegisterKeyed("ollama@prod", "ollama", single)
	r := New(reg, zap.NewNop())

	spec, adapter, err := r.ResolveAdapter(context.Background(), runtime.RunRequest{
		RunID:   "run-1",
		Adapter: "ollama",
		Target:  "llama3",
	})
	if err != nil {
		t.Fatalf("ResolveAdapter legacy single: %v", err)
	}
	if spec.Adapter != "ollama" {
		t.Errorf("spec.Adapter: got %q want %q", spec.Adapter, "ollama")
	}
	if adapter != single {
		t.Fatal("expected the single ollama instance")
	}
}

func TestGetAdapter_AmbiguousReturnsNotFound(t *testing.T) {
	reg := runtime.NewRegistry()
	reg.RegisterKeyed("ollama@a", "ollama", &stubAdapter{name: "ollama@a"})
	reg.RegisterKeyed("ollama@b", "ollama", &stubAdapter{name: "ollama@b"})
	r := New(reg, zap.NewNop())

	_, ok := r.GetAdapter("ollama")
	if ok {
		t.Fatal("expected GetAdapter to return false for ambiguous type name")
	}
}

func TestRouterSetRegistryAffectsSubsequentResolve(t *testing.T) {
	reg1 := runtime.NewRegistry()
	reg1.Register(&stubAdapter{name: "adapter1"})
	r := New(reg1, zap.NewNop())

	// Resolving adapter1 should succeed initially.
	_, _, err := r.ResolveAdapter(context.Background(), runtime.RunRequest{
		RunID:   "run-1",
		Adapter: "adapter1",
	})
	if err != nil {
		t.Fatalf("resolve adapter1 initially: %v", err)
	}

	// Resolving adapter2 should fail initially.
	_, _, err = r.ResolveAdapter(context.Background(), runtime.RunRequest{
		RunID:   "run-2",
		Adapter: "adapter2",
	})
	if err == nil {
		t.Fatal("expected error resolving adapter2 initially")
	}

	// Swap registry with one containing only adapter2.
	reg2 := runtime.NewRegistry()
	reg2.Register(&stubAdapter{name: "adapter2"})
	r.SetRegistry(reg2)

	// Resolving adapter1 should now fail.
	_, _, err = r.ResolveAdapter(context.Background(), runtime.RunRequest{
		RunID:   "run-3",
		Adapter: "adapter1",
	})
	if err == nil {
		t.Fatal("expected error resolving adapter1 after swap")
	}

	// Resolving adapter2 should now succeed.
	_, _, err = r.ResolveAdapter(context.Background(), runtime.RunRequest{
		RunID:   "run-4",
		Adapter: "adapter2",
	})
	if err != nil {
		t.Fatalf("resolve adapter2 after swap: %v", err)
	}
}

func TestRouterResolvesProviderIDInstanceKey(t *testing.T) {
	reg := runtime.NewRegistry()
	cliAdapter := &stubAdapter{name: "claude-api"}
	reg.RegisterKeyed("claude-api", "cli", cliAdapter)
	r := New(reg, zap.NewNop())

	spec, adapter, err := r.ResolveAdapter(context.Background(), runtime.RunRequest{
		RunID:   "run-1",
		Adapter: "claude-api",
		Target:  "claude-api",
	})
	if err != nil {
		t.Fatalf("ResolveAdapter: %v", err)
	}
	if spec.Adapter != "claude-api" {
		t.Errorf("spec.Adapter: got %q, want %q", spec.Adapter, "claude-api")
	}
	if adapter != cliAdapter {
		t.Fatal("expected 'claude-api' adapter instance")
	}
}

func TestRouterResolveAdapterConcurrentSetRegistryDoesNotDeadlock(t *testing.T) {
	reg1 := runtime.NewRegistry()
	reg1.Register(&stubAdapter{name: "cli"})
	r := New(reg1, zap.NewNop())

	ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
	defer cancel()

	done := make(chan struct{})

	go func() {
		for {
			select {
			case <-ctx.Done():
				return
			default:
				_, _, err := r.ResolveAdapter(ctx, runtime.RunRequest{
					RunID:   "run-1",
					Adapter: "cli",
				})
				_ = err
			}
		}
	}()

	go func() {
		reg2 := runtime.NewRegistry()
		reg2.Register(&stubAdapter{name: "cli"})
		emptyReg := runtime.NewRegistry()
		for {
			select {
			case <-ctx.Done():
				close(done)
				return
			default:
				r.SetRegistry(reg2)
				r.SetRegistry(emptyReg)
			}
		}
	}()

	select {
	case <-done:
		// Success! Did not deadlock within timeout.
	}
}
