package rustdocs

import (
	"strings"
	"testing"

	"github.com/PuerkitoBio/goquery"
)

const testStructPageHTML = `
<!DOCTYPE html>
<html>
<body>
<main>
<div class="main-heading">
  <div class="rustdoc-breadcrumbs"><a href="../index.html">std</a>::<wbr><a href="index.html">simd</a></div>
  <h1>Struct <span class="struct">Mask</span></h1>
  <span class="sub-heading"><span class="since" title="Stable since Rust version 1.0.0">1.0.0</span></span>
</div>
<pre class="rust item-decl"><code>pub struct Mask&lt;T, const N: usize&gt;(<span class="comment">/* private fields */</span>)</code></pre>
<details class="toggle top-doc" open="">
  <div class="docblock"><p>A SIMD vector mask for N elements.</p></div>
</details>
<h2 id="implementations">Implementations</h2>
<details class="toggle implementors-toggle" open="">
  <details class="toggle method-toggle" open="">
    <section id="method.test" class="method">
      <h4 class="code-header">pub fn <a href="#method.test" class="fn">test</a>(&amp;self, index: usize) -&gt; bool</h4>
    </section>
    <div class="docblock"><p>Tests the value of the specified element.</p></div>
  </details>
</details>
</main>
</body>
</html>
`

const testSearchHTML = `
<div id="results">
<ul class="search-results active">
<a class="result-method" href="../std/simd/struct.Mask.html#method.test">
  <span class="result-name">
    <span class="typename">method</span>
    <div class="path"><span>std::</span><span>simd::</span><span class="method">Mask::</span><span class="fn">test</span></div>
  </span>
  <div class="desc">Tests the value of the specified element.</div>
</a>
<a class="result-struct" href="../std/vec/struct.Vec.html">
  <span class="result-name">
    <span class="typename">struct</span>
    <div class="path"><span>std::</span><span>vec::</span><span class="struct">Vec</span></div>
  </span>
  <div class="desc">A contiguous growable array type.</div>
</a>
<a class="result-fn" href="../std/io/fn.stdout.html">
  <span class="result-name">
    <span class="typename">fn</span>
    <div class="path"><span>std::</span><span>io::</span><span class="fn">stdout</span></div>
  </span>
  <div class="desc">Constructs a new handle to the standard output.</div>
</a>
</ul>
</div>
`

const testCratePageHTML = `
<!DOCTYPE html>
<html>
<body>
<main>
<div class="main-heading">
  <h1>Crate <span>serde</span></h1>
  <span class="sub-heading"><span class="since">1.0.0</span></span>
</div>
<details class="toggle top-doc">
  <div class="docblock"><p>A framework for serializing and deserializing Rust data structures.</p></div>
</details>
<h2 id="modules">Modules</h2>
<div class="item-table">
  <div class="module-item"><a class="mod" href="de/index.html">de</a><div class="desc">Deserialize implementation.</div></div>
</div>
<h2 id="structs">Structs</h2>
<div class="item-table">
  <div class="struct"><a class="struct" href="struct.Serializer.html">Serializer</a><div class="desc">A structure for serializing Rust values.</div></div>
</div>
<h2 id="enums">Enums</h2>
<div class="item-table">
  <div class="enum"><a class="enum" href="enum.Error.html">Error</a><div class="desc">Errors during serialization.</div></div>
</div>
</main>
</body>
</html>
`

func TestParseItemPage(t *testing.T) {
	parser := NewParser()
	symbol, err := parser.ParseItemPage(testStructPageHTML, "https://docs.rs/std/simd/struct.Mask.html")
	if err != nil {
		t.Fatalf("ParseItemPage failed: %v", err)
	}

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

	if symbol.Kind != ItemKindStruct {
		t.Errorf("Expected kind 'struct', got '%s'", symbol.Kind)
	}

	if symbol.Doc == "" {
		t.Error("Expected non-empty doc")
	}

	if !strings.Contains(symbol.Signature, "struct Mask") {
		t.Errorf("Expected signature to contain 'struct Mask', got '%s'", symbol.Signature)
	}
}

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

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

	method := results[0]
	if method.Kind != "fn" {
		t.Errorf("Expected kind 'fn' for method, got '%s'", method.Kind)
	}
	if method.Description == "" {
		t.Error("Expected non-empty description")
	}

	structResult := results[1]
	if structResult.Kind != "struct" {
		t.Errorf("Expected kind 'struct', got '%s'", structResult.Kind)
	}

	fnResult := results[2]
	if fnResult.Kind != "fn" {
		t.Errorf("Expected kind 'fn', got '%s'", fnResult.Kind)
	}
}

func TestParseCratePage(t *testing.T) {
	parser := NewParser()
	crate, err := parser.ParseCratePage(testCratePageHTML, "https://docs.rs/serde")
	if err != nil {
		t.Fatalf("ParseCratePage failed: %v", err)
	}

	if crate.Name == "" {
		t.Error("Expected non-empty name")
	}

	if crate.Description == "" {
		t.Error("Expected non-empty description")
	}
}

func TestExtractKindFromClasses(t *testing.T) {
	tests := []struct {
		classes  string
		expected string
	}{
		{"result-struct", "struct"},
		{"result-enum", "enum"},
		{"result-trait", "trait"},
		{"result-fn", "fn"},
		{"result-macro", "macro"},
		{"result-const", "const"},
		{"result-static", "static"},
		{"result-mod", "mod"},
		{"result-method", "fn"},
		{"result-externcrate", "mod"},
		{"unknown-class", ""},
	}

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

func TestResolveURL(t *testing.T) {
	tests := []struct {
		base     string
		href     string
		expected string
	}{
		{"https://docs.rs", "/serde/struct.Serializer.html", "https://docs.rs/serde/struct.Serializer.html"},
		{"https://docs.rs", "https://example.com/page", "https://example.com/page"},
	}

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

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

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

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

	html := `<div class="rustdoc-breadcrumbs"><a href="../index.html">std</a>::<a href="index.html">simd</a></div>`
	doc, err := goquery.NewDocumentFromReader(strings.NewReader(html))
	if err != nil {
		t.Fatalf("Failed to parse HTML: %v", err)
	}

	got := parser.extractItemPath(doc, "https://docs.rs/std/simd/struct.Mask.html")

	if !strings.Contains(got, "std") || !strings.Contains(got, "simd") {
		t.Errorf("extractItemPath() = %q, expected to contain std and simd", got)
	}
}

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

	doc, err := goquery.NewDocumentFromReader(strings.NewReader(testStructPageHTML))
	if err != nil {
		t.Fatalf("Failed to parse HTML: %v", err)
	}

	methods := parser.ExtractMethods(doc)
	if len(methods) == 0 {
		t.Error("Expected at least one method")
		return
	}

	if methods[0].Name == "" {
		t.Error("Expected non-empty method name")
	}
}
