//go:build windows || darwin

package tools

import (
	"strings"
	"testing"
	"time"

	"github.com/ollama/ollama/app/ui/responses"
)

func makeTestPage(url string) *responses.Page {
	return &responses.Page{
		URL:       url,
		Title:     "Title " + url,
		Text:      "Body for " + url,
		Lines:     []string{"line1", "line2", "line3"},
		Links:     map[int]string{0: url},
		FetchedAt: time.Now(),
	}
}

func TestBrowser_Scroll_AppendsOnlyPageStack(t *testing.T) {
	b := NewBrowser(&responses.BrowserStateData{PageStack: []string{}, ViewTokens: 1024, URLToPage: map[string]*responses.Page{}})
	p1 := makeTestPage("https://example.com/1")
	b.savePage(p1)
	initialStackLen := len(b.state.Data.PageStack)
	initialMapLen := len(b.state.Data.URLToPage)

	bo := NewBrowserOpen(b)
	// Scroll without id — should push only to PageStack
	_, _, err := bo.Execute(t.Context(), map[string]any{"loc": float64(1), "num_lines": float64(1)})
	if err != nil {
		t.Fatalf("scroll execute failed: %v", err)
	}

	if got, want := len(b.state.Data.PageStack), initialStackLen+1; got != want {
		t.Fatalf("page stack length = %d, want %d", got, want)
	}
	if got, want := len(b.state.Data.URLToPage), initialMapLen; got != want {
		t.Fatalf("url_to_page length changed = %d, want %d", got, want)
	}
}

func TestBrowserOpen_UseCacheByURL(t *testing.T) {
	b := NewBrowser(&responses.BrowserStateData{PageStack: []string{}, ViewTokens: 1024, URLToPage: map[string]*responses.Page{}})
	bo := NewBrowserOpen(b)

	p := makeTestPage("https://example.com/cached")
	b.state.Data.URLToPage[p.URL] = p
	initialStackLen := len(b.state.Data.PageStack)
	initialMapLen := len(b.state.Data.URLToPage)

	_, _, err := bo.Execute(t.Context(), map[string]any{"id": p.URL})
	if err != nil {
		t.Fatalf("open cached execute failed: %v", err)
	}

	if got, want := len(b.state.Data.PageStack), initialStackLen+1; got != want {
		t.Fatalf("page stack length = %d, want %d", got, want)
	}
	if got, want := len(b.state.Data.URLToPage), initialMapLen; got != want {
		t.Fatalf("url_to_page length changed = %d, want %d", got, want)
	}
}

func TestBrowserOpen_RejectsUncachedDirectURL(t *testing.T) {
	b := NewBrowser(&responses.BrowserStateData{PageStack: []string{}, ViewTokens: 1024, URLToPage: map[string]*responses.Page{}})
	bo := NewBrowserOpen(b)

	_, _, err := bo.Execute(t.Context(), map[string]any{"id": "https://attacker.example/?data=secret"})
	if err == nil || !strings.Contains(err.Error(), "only allowed for URLs provided by the user") {
		t.Fatalf("expected direct URL rejection, got %v", err)
	}
}

func TestDirectURLsFromText_AllowsExactUserURLsOnly(t *testing.T) {
	ctx := WithAllowedDirectURLs(t.Context(), "summarize https://example.com/article?q=1 please")

	if !allowedDirectURL(ctx, "https://example.com/article?q=1") {
		t.Fatal("expected exact user-provided URL to be allowed")
	}
	if allowedDirectURL(ctx, "https://example.com/article?q=secret") {
		t.Fatal("did not expect modified URL to be allowed")
	}
}

func TestDisplayPage_InvalidLoc(t *testing.T) {
	b := NewBrowser(&responses.BrowserStateData{PageStack: []string{}, ViewTokens: 1024, URLToPage: map[string]*responses.Page{}})
	p := makeTestPage("https://example.com/x")
	// ensure lines are set
	p.Lines = []string{"a", "b"}
	_, err := b.displayPage(p, 0, 10, -1)
	if err == nil || !strings.Contains(err.Error(), "invalid location") {
		t.Fatalf("expected invalid location error, got %v", err)
	}
}

func TestBrowserOpen_LinkId_UsesCacheAndAppends(t *testing.T) {
	b := NewBrowser(&responses.BrowserStateData{PageStack: []string{}, ViewTokens: 1024, URLToPage: map[string]*responses.Page{}})
	// Seed a main page with a link id 0 to a linked URL
	main := makeTestPage("https://example.com/main")
	linked := makeTestPage("https://example.com/linked")
	main.Links = map[int]string{0: linked.URL}
	// Save the main page (adds to PageStack and URLToPage)
	b.savePage(main)
	// Pre-cache the linked page so open by id avoids network
	b.state.Data.URLToPage[linked.URL] = linked

	initialStackLen := len(b.state.Data.PageStack)
	initialMapLen := len(b.state.Data.URLToPage)

	bo := NewBrowserOpen(b)
	_, _, err := bo.Execute(t.Context(), map[string]any{"id": float64(0)})
	if err != nil {
		t.Fatalf("open by link id failed: %v", err)
	}

	if got, want := len(b.state.Data.PageStack), initialStackLen+1; got != want {
		t.Fatalf("page stack length = %d, want %d", got, want)
	}
	if got, want := len(b.state.Data.URLToPage), initialMapLen; got != want {
		t.Fatalf("url_to_page length changed = %d, want %d", got, want)
	}
	if last := b.state.Data.PageStack[len(b.state.Data.PageStack)-1]; last != linked.URL {
		t.Fatalf("last page in stack = %s, want %s", last, linked.URL)
	}
}

func TestWrapLines_PreserveAndWidth(t *testing.T) {
	long := strings.Repeat("word ", 50)
	text := "Line1\n\n" + long + "\nLine3"
	lines := wrapLines(text, 40)

	// Ensure empty line preserved at index 1
	if lines[1] != "" {
		t.Fatalf("expected preserved empty line at index 1, got %q", lines[1])
	}
	// All lines should be <= 40 chars
	for i, l := range lines {
		if len(l) > 40 {
			t.Fatalf("line %d exceeds width: %d > 40", i, len(l))
		}
	}
}

func TestDisplayPage_FormatHeaderAndLines(t *testing.T) {
	b := NewBrowser(&responses.BrowserStateData{PageStack: []string{}, ViewTokens: 1024, URLToPage: map[string]*responses.Page{}})
	p := &responses.Page{
		URL:   "https://example.com/x",
		Title: "Example",
		Lines: []string{"URL: https://example.com/x", "A", "B", "C"},
	}
	out, err := b.displayPage(p, 3, 0, 2)
	if err != nil {
		t.Fatalf("displayPage failed: %v", err)
	}
	if !strings.HasPrefix(out, "[3] Example(") {
		t.Fatalf("header not formatted as expected: %q", out)
	}
	if !strings.Contains(out, "L0:\n") {
		t.Fatalf("missing L0 label: %q", out)
	}
	if !strings.Contains(out, "L1: URL: https://example.com/x\n") || !strings.Contains(out, "L2: A\n") {
		t.Fatalf("missing expected line numbers/content: %q", out)
	}
}
