package rag

import (
	"context"
	"errors"
	"os/exec"
	"reflect"
	"testing"
	"time"

	"gitcode-mcp/internal/config"
)

type fakeRuntime struct {
	executablePath    string
	live              bool
	models            []string
	pullErr           error
	smokeErr          error
	pullCalls         int
	pullTimeout       time.Duration
	modelOnPullErr    bool
	smokeCalls        int
	startCalls        int
	startedExecutable string
}

func (r *fakeRuntime) LookPath(string) (string, error) {
	if r.executablePath == "" {
		return "", errors.New("not found")
	}
	return r.executablePath, nil
}

func (r *fakeRuntime) IsLive(context.Context, string, time.Duration) (bool, string) {
	if r.live {
		return true, ""
	}
	return false, "not live"
}

func (r *fakeRuntime) ListModels(context.Context, string, time.Duration) ([]string, error) {
	return append([]string(nil), r.models...), nil
}

func (r *fakeRuntime) PullModel(_ context.Context, _, model string, timeout time.Duration) error {
	r.pullCalls++
	r.pullTimeout = timeout
	if r.pullErr != nil {
		if r.modelOnPullErr {
			r.models = append(r.models, model)
		}
		return r.pullErr
	}
	r.models = append(r.models, model)
	return nil
}

func (r *fakeRuntime) EmbeddingSmoke(context.Context, string, string, time.Duration) error {
	r.smokeCalls++
	return r.smokeErr
}

func (r *fakeRuntime) Start(_ context.Context, provider config.RAGProviderConfig) (string, error) {
	r.startCalls++
	r.startedExecutable = provider.Executable
	r.live = true
	return "started", nil
}

func TestSetupScenarios(t *testing.T) {
	cfg := config.Default()

	t.Run("missing provider is actionable", func(t *testing.T) {
		runtime := &fakeRuntime{}
		result, err := Setup(context.Background(), SetupRequest{Config: cfg, Runtime: runtime})
		if err != nil {
			t.Fatal(err)
		}
		if result.Status != "missing_provider" || len(result.Actions) == 0 || result.ProviderInstalled {
			t.Fatalf("result=%#v", result)
		}
	})

	t.Run("dry-run plans provider start without mutation", func(t *testing.T) {
		runtime := &fakeRuntime{executablePath: "/usr/local/bin/ollama"}
		result, err := Setup(context.Background(), SetupRequest{Config: cfg, Runtime: runtime, DryRun: true})
		if err != nil {
			t.Fatal(err)
		}
		if result.Status != "provider_not_running" || runtime.startCalls != 0 {
			t.Fatalf("result=%#v runtime=%#v", result, runtime)
		}
	})

	t.Run("missing model requires confirmation", func(t *testing.T) {
		runtime := &fakeRuntime{executablePath: "/usr/local/bin/ollama", live: true}
		result, err := Setup(context.Background(), SetupRequest{Config: cfg, Runtime: runtime})
		if err != nil {
			t.Fatal(err)
		}
		if result.Status != "missing_model" || runtime.pullCalls != 0 || result.PullAttempted {
			t.Fatalf("result=%#v runtime=%#v", result, runtime)
		}
	})

	t.Run("yes pulls model and runs smoke", func(t *testing.T) {
		runtime := &fakeRuntime{executablePath: "/usr/local/bin/ollama", live: true}
		var progress []SetupProgress
		result, err := Setup(context.Background(), SetupRequest{Config: cfg, Runtime: runtime, Yes: true, Progress: func(event SetupProgress) {
			progress = append(progress, event)
		}})
		if err != nil {
			t.Fatal(err)
		}
		if result.Status != "ready" || !result.PullAttempted || runtime.pullCalls != 1 || runtime.smokeCalls != 1 || !result.ModelAvailable || runtime.pullTimeout < minimumModelPullTimeout {
			t.Fatalf("result=%#v runtime=%#v", result, runtime)
		}
		if len(progress) != 2 || progress[0].Phase != "model_pull_started" || progress[1].Phase != "model_pull_finished" {
			t.Fatalf("progress=%#v", progress)
		}
		if len(result.NextActions) == 0 {
			t.Fatalf("missing next actions: %#v", result)
		}
	})

	t.Run("pull transport failure recovers when model became available", func(t *testing.T) {
		runtime := &fakeRuntime{executablePath: "/usr/local/bin/ollama", live: true, pullErr: context.DeadlineExceeded, modelOnPullErr: true}
		result, err := Setup(context.Background(), SetupRequest{Config: cfg, Runtime: runtime, Yes: true})
		if err != nil {
			t.Fatal(err)
		}
		if result.Status != "ready" || !result.ModelAvailable || result.EmbeddingSmoke != "ok" {
			t.Fatalf("result=%#v runtime=%#v", result, runtime)
		}
	})
}

func TestSetupLiveEndpointWithUnavailableExecutable(t *testing.T) {
	cfg := config.Default()
	for _, dryRun := range []bool{true, false} {
		runtime := &fakeRuntime{live: true, models: []string{cfg.RAG.Profiles[config.DefaultRAGProfile].Model}}
		result, err := Setup(context.Background(), SetupRequest{Config: cfg, Runtime: runtime, DryRun: dryRun, Yes: true})
		if err != nil {
			t.Fatal(err)
		}
		if result.Status != "provider_executable_unavailable" || result.ProviderInstalled || !result.ProviderLive || !result.ModelAvailable {
			t.Fatalf("readiness must distinguish executable lookup from live endpoint: status=%s installed=%t live=%t model=%t", result.Status, result.ProviderInstalled, result.ProviderLive, result.ModelAvailable)
		}
		if runtime.startCalls != 0 || runtime.pullCalls != 0 || runtime.smokeCalls != 0 {
			t.Fatal("unresolved executable must not trigger setup mutations")
		}
	}
}

func TestProviderExecutableDiscovery(t *testing.T) {
	for _, tc := range []struct {
		name, goos, executable, available, want string
		lookupErr                               error
		calls                                   []string
	}{
		{name: "restricted PATH Apple Silicon Homebrew", goos: "darwin", executable: "ollama", available: "/opt/homebrew/bin/ollama", want: "/opt/homebrew/bin/ollama", calls: []string{"ollama", "/opt/homebrew/bin/ollama"}},
		{name: "restricted PATH Intel Homebrew", goos: "darwin", executable: "ollama", available: "/usr/local/bin/ollama", want: "/usr/local/bin/ollama", calls: []string{"ollama", "/opt/homebrew/bin/ollama", "/usr/local/bin/ollama"}},
		{name: "PATH has precedence", goos: "darwin", executable: "ollama", available: "ollama", want: "/configured/bin/ollama", calls: []string{"ollama"}},
		{name: "explicit absolute path", goos: "darwin", executable: "/configured/bin/ollama", available: "/configured/bin/ollama", want: "/configured/bin/ollama", calls: []string{"/configured/bin/ollama"}},
		{name: "missing explicit path does not fall back", goos: "darwin", executable: "/configured/bin/ollama", available: "/opt/homebrew/bin/ollama", calls: []string{"/configured/bin/ollama"}},
		{name: "custom name does not fall back", goos: "darwin", executable: "custom-provider", available: "/opt/homebrew/bin/ollama", calls: []string{"custom-provider"}},
		{name: "relative path does not fall back", goos: "darwin", executable: "bin/ollama", available: "/opt/homebrew/bin/ollama", calls: []string{"bin/ollama"}},
		{name: "other OS does not fall back", goos: "linux", executable: "ollama", available: "/opt/homebrew/bin/ollama", calls: []string{"ollama"}},
		{name: "ErrDot does not fall back", goos: "darwin", executable: "ollama", lookupErr: exec.ErrDot, available: "/opt/homebrew/bin/ollama", calls: []string{"ollama"}},
		{name: "missing everywhere", goos: "darwin", executable: "ollama", calls: []string{"ollama", "/opt/homebrew/bin/ollama", "/usr/local/bin/ollama"}},
	} {
		t.Run(tc.name, func(t *testing.T) {
			var calls []string
			lookup := func(executable string) (string, error) {
				calls = append(calls, executable)
				if executable == tc.available {
					if executable == "ollama" {
						return tc.want, nil
					}
					return executable, nil
				}
				if tc.lookupErr != nil {
					return "", tc.lookupErr
				}
				return "", exec.ErrNotFound
			}
			path, err := resolveProviderExecutable(tc.executable, tc.goos, lookup)
			if path != tc.want || (err != nil) != (tc.want == "") || !reflect.DeepEqual(calls, tc.calls) {
				t.Fatalf("unexpected bounded executable discovery result: path=%q err=%v calls=%v", path, err, calls)
			}
		})
	}
}

func TestSetupManagedStartUsesResolvedExecutable(t *testing.T) {
	cfg := config.Default()
	runtime := &fakeRuntime{executablePath: "/opt/homebrew/bin/ollama", models: []string{cfg.RAG.Profiles[config.DefaultRAGProfile].Model}}
	result, err := Setup(context.Background(), SetupRequest{Config: cfg, Runtime: runtime, Yes: true})
	if err != nil {
		t.Fatal(err)
	}
	if result.Status != "ready" || runtime.startCalls != 1 || runtime.startedExecutable != runtime.executablePath {
		t.Fatal("managed start did not use the resolved executable")
	}
	if cfg.RAG.Providers["ollama"].Executable != "ollama" {
		t.Fatal("setup mutated executable configuration")
	}
}