package iflow

import (
	"context"
	"os"
	"path/filepath"
	"reflect"
	"sync"
	"testing"

	"github.com/chenhg5/cc-connect/core"
)

func TestNormalizeMode(t *testing.T) {
	cases := []struct {
		in   string
		want string
	}{
		{"", "default"},
		{"default", "default"},
		{"AUTO-EDIT", "auto-edit"},
		{"auto_edit", "auto-edit"},
		{"edit", "auto-edit"},
		{"plan", "plan"},
		{"yolo", "yolo"},
		{"force", "yolo"},
		{"unknown", "default"},
	}
	for _, tc := range cases {
		if got := normalizeMode(tc.in); got != tc.want {
			t.Fatalf("normalizeMode(%q) = %q, want %q", tc.in, got, tc.want)
		}
	}
}

func TestProviderEnvLocked(t *testing.T) {
	a := &Agent{
		providers: []core.ProviderConfig{
			{
				Name:    "custom",
				APIKey:  "k1",
				BaseURL: "https://example.com/v1",
				Env: map[string]string{
					"FOO": "bar",
				},
			},
		},
		activeIdx: 0,
	}

	got := a.providerEnvLocked()
	wantSubset := []string{
		"IFLOW_API_KEY=k1",
		"IFLOW_apiKey=k1",
		"IFLOW_BASE_URL=https://example.com/v1",
		"IFLOW_baseUrl=https://example.com/v1",
		"FOO=bar",
	}

	for _, item := range wantSubset {
		if !contains(got, item) {
			t.Fatalf("providerEnvLocked() missing %q; got=%v", item, got)
		}
	}
}

func TestIFlowProjectKey(t *testing.T) {
	path := "/Users/test/project"
	got := iflowProjectKey(path)
	if got != "-Users-test-project" {
		t.Fatalf("iflowProjectKey(%q) = %q", path, got)
	}

	if got := iflowProjectKey(""); got != "" {
		t.Fatalf("iflowProjectKey(\"\") = %q, want empty", got)
	}
}

func TestIFlowResolvedWorkDir(t *testing.T) {
	base := t.TempDir()
	realDir := filepath.Join(base, "real")
	linkDir := filepath.Join(base, "link")
	if err := os.Mkdir(realDir, 0o755); err != nil {
		t.Fatalf("Mkdir: %v", err)
	}
	if err := os.Symlink(realDir, linkDir); err != nil {
		t.Skipf("symlink unsupported: %v", err)
	}

	want, err := filepath.EvalSymlinks(realDir)
	if err != nil {
		t.Fatalf("EvalSymlinks(realDir): %v", err)
	}

	if got := iflowResolvedWorkDir(linkDir); got != want {
		t.Fatalf("iflowResolvedWorkDir(%q) = %q, want %q", linkDir, got, want)
	}
}

func TestExtractIFlowContentText(t *testing.T) {
	if got := extractIFlowContentText("hello"); got != "hello" {
		t.Fatalf("extractIFlowContentText string = %q", got)
	}

	arr := []any{map[string]any{"type": "text", "text": "from-array"}}
	if got := extractIFlowContentText(arr); got != "from-array" {
		t.Fatalf("extractIFlowContentText array = %q", got)
	}

	if got := extractIFlowContentText(123); got != "" {
		t.Fatalf("extractIFlowContentText unexpected = %q", got)
	}
}

func TestPermissionModesKeys(t *testing.T) {
	a := &Agent{}
	modes := a.PermissionModes()
	got := make([]string, 0, len(modes))
	for _, m := range modes {
		got = append(got, m.Key)
	}
	want := []string{"default", "auto-edit", "plan", "yolo"}
	if !reflect.DeepEqual(got, want) {
		t.Fatalf("PermissionModes keys = %v, want %v", got, want)
	}
}

func TestConfiguredModels_BoundaryConditions(t *testing.T) {
	a := &Agent{
		providers: []core.ProviderConfig{
			{Models: []core.ModelOption{{Name: "first"}}},
			{Models: []core.ModelOption{{Name: "second"}}},
		},
	}

	tests := []struct {
		name      string
		activeIdx int
		wantNil   bool
		wantName  string
	}{
		{name: "negative index", activeIdx: -1, wantNil: true},
		{name: "out of range", activeIdx: 2, wantNil: true},
		{name: "valid index", activeIdx: 1, wantName: "second"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			a.activeIdx = tt.activeIdx
			got := a.configuredModels()
			if tt.wantNil {
				if got != nil {
					t.Fatalf("configuredModels() = %v, want nil", got)
				}
				return
			}
			if len(got) != 1 || got[0].Name != tt.wantName {
				t.Fatalf("configuredModels() = %v, want %q", got, tt.wantName)
			}
		})
	}
}

func contains(list []string, target string) bool {
	for _, item := range list {
		if item == target {
			return true
		}
	}
	return false
}

// TestIFlowAgent_WorkDirRaceFreeReaders pins the bug where ListSessions,
// DeleteSession, and ProjectMemoryFile read a.workDir without holding
// a.mu, while SetWorkDir mutates it under the lock. Running this with
// -race flags the data race; with the fix the race detector stays quiet.
func TestIFlowAgent_WorkDirRaceFreeReaders(t *testing.T) {
	dir := t.TempDir()
	a := &Agent{workDir: dir}

	var wg sync.WaitGroup
	for i := 0; i < 20; i++ {
		wg.Add(1)
		go func(i int) {
			defer wg.Done()
			if i%2 == 0 {
				a.SetWorkDir(filepath.Join(dir, "a"))
			} else {
				a.SetWorkDir(filepath.Join(dir, "b"))
			}
		}(i)
	}
	for i := 0; i < 20; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			_, _ = a.ListSessions(context.Background())
		}()
		wg.Add(1)
		go func() {
			defer wg.Done()
			_ = a.DeleteSession(context.Background(), "no-such-session")
		}()
		wg.Add(1)
		go func() {
			defer wg.Done()
			_ = a.ProjectMemoryFile()
		}()
	}
	wg.Wait()
}
