package godocs

import (
	"strings"
	"testing"

	"github.com/PuerkitoBio/goquery"
)

const testPackageHTML = `
<!DOCTYPE html>
<html>
<head><title>runtime - pkg.go.dev</title></head>
<body>
<nav class="go-Breadcrumb">
  <ol>
    <li><a href="/">Discover Packages</a></li>
    <li><a href="/k8s.io/apimachinery">k8s.io/apimachinery</a></li>
    <li><a href="/k8s.io/apimachinery/pkg">pkg</a></li>
    <li><a href="/k8s.io/apimachinery/pkg/runtime">runtime</a></li>
  </ol>
</nav>

<h1 class="UnitHeader-titleHeading">runtime</h1>

<div class="go-Main-headerDetails">
  <span data-test-id="UnitHeader-version"><a href="?tab=versions">v0.35.1</a></span>
  <span data-test-id="UnitHeader-importedby"><a href="?tab=importedby">Imported by: 144,729</a></span>
  <span data-test-id="UnitHeader-licenses"><a href="?tab=licenses">Apache-2.0</a></span>
</div>

<div class="Documentation">
  <p>Package runtime defines conversions between generic types and structs to map query strings to struct objects.</p>
  <p>This is additional documentation text for the package.</p>
</div>

<div class="Documentation-function">
  <div class="Documentation-functionHeader">func DecodeInto</div>
  <pre>func DecodeInto(d Decoder, data []byte, into Object) error</pre>
  <p>DecodeInto is a helper function that decodes the given data into the provided object.</p>
</div>

<div class="Documentation-type">
  <div class="Documentation-typeHeader">type Codec</div>
  <pre>type Codec struct {
    Encoder Encoder
    Decoder Decoder
}</pre>
  <p>Codec is a struct that holds an encoder and decoder.</p>
  <div class="Documentation-method">
    <div class="Documentation-methodHeader">func (*Codec) Encode</div>
    <pre>func (c *Codec) Encode(obj Object) ([]byte, error)</pre>
    <p>Encode encodes the given object.</p>
  </div>
</div>

<div class="Documentation-constants">
  <pre>const (
    ContentTypeJSON = "application/json"
    ContentTypeYAML = "application/yaml"
)</pre>
  <p>Content types for different formats.</p>
</div>

<div class="Documentation-variables">
  <pre>var DefaultScheme = NewScheme()</pre>
  <p>DefaultScheme is the default scheme used for encoding/decoding.</p>
</div>

</body>
</html>
`

const testSearchHTML = `
<!DOCTYPE html>
<html>
<body>
<div class="SearchSnippet">
  <h2><a href="/k8s.io/apimachinery/pkg/runtime">runtime <span class="SearchSnippet-header-path">(k8s.io/apimachinery/pkg/runtime)</span></a></h2>
  <p class="SearchSnippet-synopsis">Package runtime defines conversions between generic types and structs.</p>
  <div class="SearchSnippet-infoLabel">
    <a href="?tab=importedby">Imported by: <strong>144,729</strong></a>
    <span>v0.35.1 published on <strong>Dec 4, 2025</strong></span>
    <a href="?tab=licenses">Apache-2.0</a>
  </div>
</div>

<div class="SearchSnippet">
  <h2><a href="/github.com/google/go-querystring/query">query <span class="SearchSnippet-header-path">(github.com/google/go-querystring/query)</span></a></h2>
  <p class="SearchSnippet-synopsis">Package query implements encoding of structs into URL query parameters.</p>
  <div class="SearchSnippet-infoLabel">
    <a href="?tab=importedby">Imported by: <strong>5,111</strong></a>
    <span>v1.2.0 published on <strong>Nov 10, 2025</strong></span>
    <a href="?tab=licenses">BSD-3-Clause</a>
  </div>
</div>
</body>
</html>
`

func TestParsePackagePage(t *testing.T) {
	parser := NewParser()
	pkg, err := parser.ParsePackagePage(testPackageHTML, "https://pkg.go.dev/k8s.io/apimachinery/pkg/runtime")
	if err != nil {
		t.Fatalf("ParsePackagePage failed: %v", err)
	}

	if pkg.Name != "runtime" {
		t.Errorf("Expected name 'runtime', got '%s'", pkg.Name)
	}

	if pkg.ImportPath != "k8s.io/apimachinery/pkg/runtime" {
		t.Errorf("Expected import path 'k8s.io/apimachinery/pkg/runtime', got '%s'", pkg.ImportPath)
	}

	if pkg.Version != "v0.35.1" {
		t.Errorf("Expected version 'v0.35.1', got '%s'", pkg.Version)
	}

	if pkg.ImportedBy != 144729 {
		t.Errorf("Expected imported by 144729, got %d", pkg.ImportedBy)
	}

	if pkg.Synopsis == "" {
		t.Error("Expected non-empty synopsis")
	}

	if len(pkg.Functions) == 0 {
		t.Error("Expected at least one function")
	}

	if len(pkg.Types) == 0 {
		t.Error("Expected at least one type")
	}

	if len(pkg.Constants) == 0 {
		t.Error("Expected at least one constant")
	}

	if len(pkg.Variables) == 0 {
		t.Error("Expected at least one variable")
	}
}

func TestParseSearchResults(t *testing.T) {
	parser := NewParser()
	results, err := parser.ParseSearchResults(testSearchHTML)
	if err != nil {
		t.Fatalf("ParseSearchResults failed: %v", err)
	}

	if len(results) < 2 {
		t.Fatalf("Expected at least 2 results, got %d", len(results))
	}

	first := results[0]
	if first.Synopsis == "" {
		t.Error("Expected non-empty synopsis")
	}

	if first.Path == "" {
		t.Error("Expected non-empty path")
	}

	if first.URL == "" {
		t.Error("Expected non-empty URL")
	}
}

func TestIsExported(t *testing.T) {
	tests := []struct {
		name     string
		expected bool
	}{
		{"Exported", true},
		{"unexported", false},
		{"", false},
		{"CamelCase", true},
		{"camelCase", false},
		{"X", true},
		{"x", false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := isExported(tt.name); got != tt.expected {
				t.Errorf("isExported(%q) = %v, want %v", tt.name, got, tt.expected)
			}
		})
	}
}

func TestCleanWhitespace(t *testing.T) {
	tests := []struct {
		input    string
		expected string
	}{
		{"  hello   world  ", "hello world"},
		{"single", "single"},
		{"multiple   spaces   here", "multiple spaces here"},
		{"\n\ttabs\t\n", "tabs"},
	}

	for _, tt := range tests {
		t.Run(tt.input, func(t *testing.T) {
			if got := cleanWhitespace(tt.input); got != tt.expected {
				t.Errorf("cleanWhitespace(%q) = %q, want %q", tt.input, got, tt.expected)
			}
		})
	}
}

func TestParseCount(t *testing.T) {
	tests := []struct {
		input    string
		expected int
	}{
		{"144729", 144729},
		{"5,111", 5111},
		{"0", 0},
		{"1,234,567", 1234567},
		{"abc", 0},
		{"", 0},
	}

	for _, tt := range tests {
		t.Run(tt.input, func(t *testing.T) {
			if got := parseCount(tt.input); got != tt.expected {
				t.Errorf("parseCount(%q) = %d, want %d", tt.input, got, tt.expected)
			}
		})
	}
}

func TestExtractImportPath(t *testing.T) {
	parser := NewParser()

	tests := []struct {
		html     string
		url      string
		expected string
	}{
		{
			html:     `<nav class="go-Breadcrumb"><li><a href="/">Discover</a></li><li><a href="/k8s.io/apimachinery">k8s.io/apimachinery</a></li><li><a href="/k8s.io/apimachinery/pkg">pkg</a></li><li><a href="/k8s.io/apimachinery/pkg/runtime">runtime</a></li></nav>`,
			url:      "https://pkg.go.dev/k8s.io/apimachinery/pkg/runtime",
			expected: "k8s.io/apimachinery/pkg/runtime",
		},
		{
			html:     `<nav class="go-Breadcrumb"><li><a href="/github.com/user/repo">github.com/user/repo</a></li></nav>`,
			url:      "https://pkg.go.dev/github.com/user/repo@v1.0.0",
			expected: "github.com/user/repo",
		},
	}

	for _, tt := range tests {
		t.Run(tt.expected, func(t *testing.T) {
			doc, err := goquery.NewDocumentFromReader(strings.NewReader(tt.html))
			if err != nil {
				t.Fatalf("Failed to parse HTML: %v", err)
			}

			got := parser.extractImportPath(doc, tt.url)
			if got != tt.expected {
				t.Errorf("extractImportPath() = %q, want %q", got, tt.expected)
			}
		})
	}
}
