package adapters

import (
	"testing"

	"google.golang.org/protobuf/types/known/structpb"

	iop "iop/proto/gen/iop"
)

func TestCLIConfFromProto_ProfileRuntimeOptions(t *testing.T) {
	m := &iop.CLIAdapterConfig{
		Profiles: map[string]*iop.CLIProfileConfig{
			"claude": {
				Command:      "claude",
				Args:         []string{"-p"},
				OutputFormat: "claude-json",
			},
		},
	}

	cfg := cliConfFromProto(m)

	prof, ok := cfg.Profiles["claude"]
	if !ok {
		t.Fatal("expected claude profile")
	}
	if prof.Command != "claude" {
		t.Fatalf("command: got %q", prof.Command)
	}
	if len(prof.Args) != 1 || prof.Args[0] != "-p" {
		t.Fatalf("args: got %#v", prof.Args)
	}
	if prof.OutputFormat != "claude-json" {
		t.Fatalf("output_format: got %q", prof.OutputFormat)
	}
}

func TestCLIConfFromProto_ClaudeHeadlessBypassProfile(t *testing.T) {
	m := &iop.CLIAdapterConfig{
		Profiles: map[string]*iop.CLIProfileConfig{
			"claude": {
				Command: "claude",
				Args:    []string{"-p", "--dangerously-skip-permissions"},
			},
		},
	}
	cfg := cliConfFromProto(m)
	prof, ok := cfg.Profiles["claude"]
	if !ok {
		t.Fatal("expected claude profile")
	}
	if len(prof.Args) != 2 || prof.Args[0] != "-p" || prof.Args[1] != "--dangerously-skip-permissions" {
		t.Fatalf("args: got %#v", prof.Args)
	}
	if prof.Persistent || prof.Terminal {
		t.Fatalf("expected persistent/terminal=false")
	}
}

func TestCLIConfFromProto_AntigravityHeadlessProfile(t *testing.T) {
	m := &iop.CLIAdapterConfig{
		Profiles: map[string]*iop.CLIProfileConfig{
			"antigravity": {
				Command: "agy",
				Args:    []string{"--dangerously-skip-permissions", "--print"},
			},
		},
	}
	cfg := cliConfFromProto(m)
	prof, ok := cfg.Profiles["antigravity"]
	if !ok {
		t.Fatal("expected antigravity profile")
	}
	if len(prof.Args) != 2 || prof.Args[0] != "--dangerously-skip-permissions" || prof.Args[1] != "--print" {
		t.Fatalf("args: got %#v", prof.Args)
	}
}

func TestCLIConfFromProto_ClineConfigProfile(t *testing.T) {
	m := &iop.CLIAdapterConfig{
		Profiles: map[string]*iop.CLIProfileConfig{
			"cline-dgx": {
				Command:      "/config/.npm-global/bin/cline",
				Args:         []string{"-y", "--json", "--config", "/config/.cline/profiles/ollama-dgx"},
				OutputFormat: "cline-json",
			},
		},
	}
	cfg := cliConfFromProto(m)
	prof, ok := cfg.Profiles["cline-dgx"]
	if !ok {
		t.Fatal("expected cline-dgx profile")
	}
	if prof.Command != "/config/.npm-global/bin/cline" {
		t.Fatalf("command: got %q", prof.Command)
	}
	if len(prof.Args) != 4 {
		t.Fatalf("args: got %#v", prof.Args)
	}
	if prof.OutputFormat != "cline-json" {
		t.Fatalf("output_format: got %q", prof.OutputFormat)
	}
}

func TestCLIConfFromProto_ProfileCompletionMarker(t *testing.T) {
	m := &iop.CLIAdapterConfig{
		Profiles: map[string]*iop.CLIProfileConfig{
			"codex": {
				Command:    "codex",
				Persistent: true,
				CompletionMarker: &iop.CLICompletionMarker{
					Line:  "[[DONE]]",
					Regex: `^\[\[DONE\]\]$`,
				},
			},
		},
	}
	cfg := cliConfFromProto(m)
	prof := cfg.Profiles["codex"]
	if prof.CompletionMarker.Line != "[[DONE]]" {
		t.Fatalf("line: got %q", prof.CompletionMarker.Line)
	}
	if prof.CompletionMarker.Regex != `^\[\[DONE\]\]$` {
		t.Fatalf("regex: got %q", prof.CompletionMarker.Regex)
	}
}

func TestCLIConfFromProto_PersistentAndIdleTimeouts(t *testing.T) {
	m := &iop.CLIAdapterConfig{
		Profiles: map[string]*iop.CLIProfileConfig{
			"codex": {
				Command:               "codex",
				Persistent:            true,
				ResponseIdleTimeoutMs: 1500,
				StartupIdleTimeoutMs:  300,
			},
		},
	}
	cfg := cliConfFromProto(m)
	prof := cfg.Profiles["codex"]
	if !prof.Persistent {
		t.Fatal("persistent")
	}
	if prof.ResponseIdleTimeoutMS != 1500 {
		t.Fatalf("response_idle_timeout_ms: got %d", prof.ResponseIdleTimeoutMS)
	}
	if prof.StartupIdleTimeoutMS != 300 {
		t.Fatalf("startup_idle_timeout_ms: got %d", prof.StartupIdleTimeoutMS)
	}
}

func TestCLIConfFromProto_NilSettings(t *testing.T) {
	cfg := cliConfFromProto(&iop.CLIAdapterConfig{})
	if !cfg.Enabled {
		t.Fatal("expected enabled=true")
	}
	if cfg.Profiles == nil {
		t.Fatal("profiles map should be initialized")
	}
}

func TestCLIConfFromProto_OpencodeSSEMode(t *testing.T) {
	m := &iop.CLIAdapterConfig{
		Profiles: map[string]*iop.CLIProfileConfig{
			"opencode": {
				Command: "/usr/local/bin/opencode",
				Args:    []string{"--model", "m1", "--dangerously-skip-permissions"},
				Mode:    "opencode-sse",
			},
		},
	}
	cfg := cliConfFromProto(m)
	prof, ok := cfg.Profiles["opencode"]
	if !ok {
		t.Fatal("expected opencode profile")
	}
	if prof.Mode != "opencode-sse" {
		t.Errorf("mode: got %q", prof.Mode)
	}
	if len(prof.Args) != 3 {
		t.Fatalf("args: got %#v", prof.Args)
	}
}

func TestCLIConfFromStruct_OpencodeSSEModeLegacy(t *testing.T) {
	st, err := structpb.NewStruct(map[string]any{
		"profiles": map[string]any{
			"opencode": map[string]any{
				"command": "/usr/local/bin/opencode",
				"args":    []any{"--model", "m1"},
				"mode":    "opencode-sse",
			},
		},
	})
	if err != nil {
		t.Fatalf("structpb: %v", err)
	}
	cfg := cliConfFromStruct(st)
	prof, ok := cfg.Profiles["opencode"]
	if !ok {
		t.Fatal("expected opencode profile")
	}
	if prof.Mode != "opencode-sse" {
		t.Errorf("mode: got %q", prof.Mode)
	}
}

// Legacy structpb fallback regression tests — these exercise the path taken
// when an older edge sends AdapterConfig.settings instead of the typed oneof.

func TestOllamaConfFromStruct_LegacyPayload(t *testing.T) {
	st, err := structpb.NewStruct(map[string]any{
		"base_url":           "http://legacy:11434",
		"context_size":       float64(262144),
		"capacity":           float64(3),
		"max_queue":          float64(8),
		"queue_timeout_ms":   float64(1500),
		"request_timeout_ms": float64(30000),
	})
	if err != nil {
		t.Fatalf("structpb: %v", err)
	}
	cfg := ollamaConfFromStruct(st)
	if !cfg.Enabled {
		t.Fatal("expected enabled=true")
	}
	if cfg.BaseURL != "http://legacy:11434" {
		t.Fatalf("base_url: got %q", cfg.BaseURL)
	}
	if cfg.ContextSize != 262144 {
		t.Fatalf("context_size: got %d", cfg.ContextSize)
	}
	if cfg.Capacity != 3 || cfg.MaxQueue != 8 || cfg.QueueTimeoutMS != 1500 || cfg.RequestTimeoutMS != 30000 {
		t.Fatalf("queue config: got %+v", cfg)
	}
}

func TestVllmConfFromStruct_LegacyPayload(t *testing.T) {
	st, err := structpb.NewStruct(map[string]any{
		"endpoint":           "http://legacy:8000",
		"capacity":           float64(4),
		"max_queue":          float64(10),
		"queue_timeout_ms":   float64(2000),
		"request_timeout_ms": float64(45000),
	})
	if err != nil {
		t.Fatalf("structpb: %v", err)
	}
	cfg := vllmConfFromStruct(st)
	if !cfg.Enabled {
		t.Fatal("expected enabled=true")
	}
	if cfg.Endpoint != "http://legacy:8000" {
		t.Fatalf("endpoint: got %q", cfg.Endpoint)
	}
	if cfg.Capacity != 4 || cfg.MaxQueue != 10 || cfg.QueueTimeoutMS != 2000 || cfg.RequestTimeoutMS != 45000 {
		t.Fatalf("queue config: got %+v", cfg)
	}
}

func TestOpenAICompatConfFromProto(t *testing.T) {
	cfg := openAICompatConfFromProto(&iop.OpenAICompatAdapterConfig{
		Provider:         "lemonade",
		Endpoint:         "http://typed:8000",
		Headers:          map[string]string{"Authorization": "Bearer t"},
		Capacity:         4,
		MaxQueue:         10,
		QueueTimeoutMs:   2000,
		RequestTimeoutMs: 45000,
	})
	if !cfg.Enabled {
		t.Fatal("expected enabled=true")
	}
	if cfg.Provider != "lemonade" || cfg.Endpoint != "http://typed:8000" {
		t.Fatalf("config: got %+v", cfg)
	}
	if cfg.Headers["Authorization"] != "Bearer t" {
		t.Fatalf("headers: got %+v", cfg.Headers)
	}
	if cfg.Capacity != 4 || cfg.MaxQueue != 10 || cfg.QueueTimeoutMS != 2000 || cfg.RequestTimeoutMS != 45000 {
		t.Fatalf("queue config: got %+v", cfg)
	}
}

func TestOpenAICompatConfFromStruct_LegacyPayload(t *testing.T) {
	st, err := structpb.NewStruct(map[string]any{
		"provider":           "lemonade",
		"endpoint":           "http://legacy:8000",
		"headers":            map[string]any{"Authorization": "Bearer legacy"},
		"capacity":           float64(4),
		"max_queue":          float64(10),
		"queue_timeout_ms":   float64(2000),
		"request_timeout_ms": float64(45000),
	})
	if err != nil {
		t.Fatalf("structpb: %v", err)
	}
	cfg := openAICompatConfFromStruct(st)
	if !cfg.Enabled {
		t.Fatal("expected enabled=true")
	}
	if cfg.Provider != "lemonade" || cfg.Endpoint != "http://legacy:8000" {
		t.Fatalf("config: got %+v", cfg)
	}
	if cfg.Headers["Authorization"] != "Bearer legacy" {
		t.Fatalf("headers: got %+v", cfg.Headers)
	}
	if cfg.Capacity != 4 || cfg.MaxQueue != 10 || cfg.QueueTimeoutMS != 2000 || cfg.RequestTimeoutMS != 45000 {
		t.Fatalf("queue config: got %+v", cfg)
	}
}

func TestProviderConfsFromProto_QueueConfig(t *testing.T) {
	ollamaCfg := ollamaConfFromProto(&iop.OllamaAdapterConfig{
		BaseUrl:          "http://typed:11434",
		ContextSize:      8192,
		Capacity:         3,
		MaxQueue:         8,
		QueueTimeoutMs:   1500,
		RequestTimeoutMs: 30000,
	})
	if ollamaCfg.BaseURL != "http://typed:11434" || ollamaCfg.ContextSize != 8192 {
		t.Fatalf("ollama config: got %+v", ollamaCfg)
	}
	if ollamaCfg.Capacity != 3 || ollamaCfg.MaxQueue != 8 || ollamaCfg.QueueTimeoutMS != 1500 || ollamaCfg.RequestTimeoutMS != 30000 {
		t.Fatalf("ollama queue config: got %+v", ollamaCfg)
	}

	vllmCfg := vllmConfFromProto(&iop.VllmAdapterConfig{
		Endpoint:         "http://typed:8000",
		Capacity:         4,
		MaxQueue:         10,
		QueueTimeoutMs:   2000,
		RequestTimeoutMs: 45000,
	})
	if vllmCfg.Endpoint != "http://typed:8000" {
		t.Fatalf("vllm config: got %+v", vllmCfg)
	}
	if vllmCfg.Capacity != 4 || vllmCfg.MaxQueue != 10 || vllmCfg.QueueTimeoutMS != 2000 || vllmCfg.RequestTimeoutMS != 45000 {
		t.Fatalf("vllm queue config: got %+v", vllmCfg)
	}
}

func TestCLIConfFromStruct_LegacyPayload(t *testing.T) {
	st, err := structpb.NewStruct(map[string]any{
		"profiles": map[string]any{
			"codex": map[string]any{
				"command":                  "codex",
				"args":                     []any{"--legacy"},
				"env":                      []any{"K=V"},
				"persistent":               true,
				"terminal":                 false,
				"response_idle_timeout_ms": 1500,
				"startup_idle_timeout_ms":  300,
				"output_format":            "codex-json",
				"completion_marker": map[string]any{
					"line":  "[[DONE]]",
					"regex": `^\[\[DONE\]\]$`,
				},
			},
		},
	})
	if err != nil {
		t.Fatalf("structpb: %v", err)
	}
	cfg := cliConfFromStruct(st)
	prof, ok := cfg.Profiles["codex"]
	if !ok {
		t.Fatal("expected codex profile")
	}
	if prof.Command != "codex" {
		t.Fatalf("command: got %q", prof.Command)
	}
	if len(prof.Args) != 1 || prof.Args[0] != "--legacy" {
		t.Fatalf("args: got %#v", prof.Args)
	}
	if len(prof.Env) != 1 || prof.Env[0] != "K=V" {
		t.Fatalf("env: got %#v", prof.Env)
	}
	if !prof.Persistent {
		t.Fatal("persistent")
	}
	if prof.ResponseIdleTimeoutMS != 1500 {
		t.Fatalf("response_idle_timeout_ms: got %d", prof.ResponseIdleTimeoutMS)
	}
	if prof.StartupIdleTimeoutMS != 300 {
		t.Fatalf("startup_idle_timeout_ms: got %d", prof.StartupIdleTimeoutMS)
	}
	if prof.OutputFormat != "codex-json" {
		t.Fatalf("output_format: got %q", prof.OutputFormat)
	}
	if prof.CompletionMarker.Line != "[[DONE]]" || prof.CompletionMarker.Regex != `^\[\[DONE\]\]$` {
		t.Fatalf("completion_marker: got %+v", prof.CompletionMarker)
	}
}

// End-to-end fallback through BuildFromPayload: oneof empty, settings populated.
func TestBuildFromPayload_LegacySettingsFallback(t *testing.T) {
	ollamaSt, _ := structpb.NewStruct(map[string]any{"base_url": "http://legacy:11434"})
	cliSt, _ := structpb.NewStruct(map[string]any{
		"profiles": map[string]any{
			"codex": map[string]any{
				"command": "codex",
				"args":    []any{"--legacy"},
			},
		},
	})
	payload := &iop.NodeConfigPayload{
		Adapters: []*iop.AdapterConfig{
			{Type: "ollama", Enabled: true, Settings: ollamaSt},
			{Type: "cli", Enabled: true, Settings: cliSt},
		},
	}
	reg, err := BuildFromPayload(payload, nil)
	if err != nil {
		t.Fatalf("build: %v", err)
	}
	for _, name := range []string{"ollama", "cli"} {
		if _, ok := reg.Get(name); !ok {
			t.Fatalf("expected %s adapter registered from legacy settings", name)
		}
	}
}

func TestBuildFromPayload_LegacyVllmSettingsRegistered(t *testing.T) {
	vllmSt, _ := structpb.NewStruct(map[string]any{"endpoint": "http://legacy:8000"})
	reg, err := BuildFromPayload(&iop.NodeConfigPayload{
		Adapters: []*iop.AdapterConfig{
			{Type: "vllm", Enabled: true, Settings: vllmSt},
		},
	}, nil)
	if err != nil {
		t.Fatalf("build from payload: %v", err)
	}
	if _, ok := reg.Get("vllm"); !ok {
		t.Fatal("expected vllm adapter registered from legacy settings")
	}
}

func TestBuildFromPayload_TypedMockConfig(t *testing.T) {
	reg, err := BuildFromPayload(&iop.NodeConfigPayload{
		Adapters: []*iop.AdapterConfig{
			{
				Type:    "mock",
				Enabled: true,
				Config:  &iop.AdapterConfig_Mock{Mock: &iop.MockAdapterConfig{}},
			},
		},
	}, nil)
	if err != nil {
		t.Fatalf("build: %v", err)
	}
	if _, ok := reg.Get("mock"); !ok {
		t.Fatal("expected mock adapter registered from typed MockAdapterConfig")
	}
}
