package opds

import (
	"testing"
)

func TestNewFeed(t *testing.T) {
	feed := NewFeed("urn:uuid:test-id", "Test Feed")

	if feed.ID != "urn:uuid:test-id" {
		t.Errorf("expected ID to be 'urn:uuid:test-id', got '%s'", feed.ID)
	}

	if feed.Title != "Test Feed" {
		t.Errorf("expected Title to be 'Test Feed', got '%s'", feed.Title)
	}

	if feed.Xmlns != "http://www.w3.org/2005/Atom" {
		t.Errorf("expected Xmlns to be 'http://www.w3.org/2005/Atom', got '%s'", feed.Xmlns)
	}

	if feed.OpdsNS != "http://opds-spec.org/2010/" {
		t.Errorf("expected OpdsNS to be 'http://opds-spec.org/2010/', got '%s'", feed.OpdsNS)
	}
}

func TestFeedAddLink(t *testing.T) {
	feed := NewFeed("urn:uuid:test-id", "Test Feed")
	feed.AddLink("http://example.com", "application/atom+xml", "self")

	if len(feed.Links) != 1 {
		t.Fatalf("expected 1 link, got %d", len(feed.Links))
	}

	link := feed.Links[0]
	if link.Href != "http://example.com" {
		t.Errorf("expected Href to be 'http://example.com', got '%s'", link.Href)
	}

	if link.Type != "application/atom+xml" {
		t.Errorf("expected Type to be 'application/atom+xml', got '%s'", link.Type)
	}

	if link.Rel != "self" {
		t.Errorf("expected Rel to be 'self', got '%s'", link.Rel)
	}
}

func TestFeedAddEntry(t *testing.T) {
	feed := NewFeed("urn:uuid:test-id", "Test Feed")
	entry := NewEntry("urn:uuid:book-id", "Test Book", "Test Author", "2023-01-01T00:00:00Z")
	feed.AddEntry(entry)

	if len(feed.Entries) != 1 {
		t.Fatalf("expected 1 entry, got %d", len(feed.Entries))
	}

	if feed.Entries[0].ID != "urn:uuid:book-id" {
		t.Errorf("expected entry ID to be 'urn:uuid:book-id', got '%s'", feed.Entries[0].ID)
	}
}

func TestNewEntry(t *testing.T) {
	entry := NewEntry("urn:uuid:test-id", "Test Title", "Test Author", "2023-01-01T00:00:00Z")

	if entry.ID != "urn:uuid:test-id" {
		t.Errorf("expected ID to be 'urn:uuid:test-id', got '%s'", entry.ID)
	}

	if entry.Title != "Test Title" {
		t.Errorf("expected Title to be 'Test Title', got '%s'", entry.Title)
	}

	if entry.Author == nil || entry.Author.Name != "Test Author" {
		t.Errorf("expected Author.Name to be 'Test Author', got %v", entry.Author)
	}

	if entry.Updated != "2023-01-01T00:00:00Z" {
		t.Errorf("expected Updated to be '2023-01-01T00:00:00Z', got '%s'", entry.Updated)
	}
}

func TestEntryAddAcquisitionLink(t *testing.T) {
	entry := NewEntry("urn:uuid:test-id", "Test Title", "Test Author", "2023-01-01T00:00:00Z")
	entry.AddAcquisitionLink("http://example.com/book.epub", "application/epub+zip")

	if len(entry.Links) != 1 {
		t.Fatalf("expected 1 link, got %d", len(entry.Links))
	}

	link := entry.Links[0]
	if link.Href != "http://example.com/book.epub" {
		t.Errorf("expected Href to be 'http://example.com/book.epub', got '%s'", link.Href)
	}

	if link.Rel != "http://opds-spec.org/acquisition/open-access" {
		t.Errorf("expected Rel to be 'http://opds-spec.org/acquisition/open-access', got '%s'", link.Rel)
	}
}

func TestEntryAddAlternateLink(t *testing.T) {
	entry := NewEntry("urn:uuid:test-id", "Test Title", "Test Author", "2023-01-01T00:00:00Z")
	entry.AddAlternateLink("http://example.com/book.kepub.epub", "application/vnd.kobo+xml+zip")

	if len(entry.Links) != 1 {
		t.Fatalf("expected 1 link, got %d", len(entry.Links))
	}

	link := entry.Links[0]
	if link.Rel != "alternate" {
		t.Errorf("expected Rel to be 'alternate', got '%s'", link.Rel)
	}
}

func TestEntrySetIdentifier(t *testing.T) {
	entry := NewEntry("urn:uuid:test-id", "Test Title", "Test Author", "2023-01-01T00:00:00Z")
	entry.SetIdentifier("book-uuid-123")

	if entry.Identifier == nil {
		t.Fatal("expected Identifier to be set, but it was nil")
	}

	if entry.Identifier.ID != "bookhoard" {
		t.Errorf("expected Identifier ID to be 'bookhoard', got '%s'", entry.Identifier.ID)
	}

	if entry.Identifier.Content != "book-uuid-123" {
		t.Errorf("expected Identifier Content to be 'book-uuid-123', got '%s'", entry.Identifier.Content)
	}
}

func TestEntryAddMetadata(t *testing.T) {
	entry := NewEntry("urn:uuid:test-id", "Test Title", "Test Author", "2023-01-01T00:00:00Z")
	entry.AddMetadata("bookhoard:sha256", "abc123...")

	if len(entry.Metadata) != 1 {
		t.Fatalf("expected 1 metadata item, got %d", len(entry.Metadata))
	}

	meta := entry.Metadata[0]
	if meta.Property != "bookhoard:sha256" {
		t.Errorf("expected Property to be 'bookhoard:sha256', got '%s'", meta.Property)
	}

	if meta.Content != "abc123..." {
		t.Errorf("expected Content to be 'abc123...', got '%s'", meta.Content)
	}
}

func TestEntryAddCategory(t *testing.T) {
	entry := NewEntry("urn:uuid:test-id", "Test Title", "Test Author", "2023-01-01T00:00:00Z")
	entry.AddCategory("http://example.com/collections", "Science Fiction")

	if len(entry.Categories) != 1 {
		t.Fatalf("expected 1 category, got %d", len(entry.Categories))
	}

	category := entry.Categories[0]
	if category.Scheme != "http://example.com/collections" {
		t.Errorf("expected Scheme to be 'http://example.com/collections', got '%s'", category.Scheme)
	}

	if category.Term != "Science Fiction" {
		t.Errorf("expected Term to be 'Science Fiction', got '%s'", category.Term)
	}
}

func TestEntrySetSummary(t *testing.T) {
	entry := NewEntry("urn:uuid:test-id", "Test Title", "Test Author", "2023-01-01T00:00:00Z")
	entry.SetSummary("This is a test book")

	if entry.Summary != "This is a test book" {
		t.Errorf("expected Summary to be 'This is a test book', got '%s'", entry.Summary)
	}
}

func TestFeedGenerateXML(t *testing.T) {
	feed := NewFeed("urn:uuid:test-id", "Test Feed")
	feed.AddLink("http://example.com", "application/atom+xml", "self")

	entry := NewEntry("urn:uuid:book-id", "Test Book", "Test Author", "2023-01-01T00:00:00Z")
	entry.AddAcquisitionLink("http://example.com/book.epub", "application/epub+zip")
	entry.SetIdentifier("book-uuid-123")
	entry.AddMetadata("bookhoard:sha256", "abc123...")
	feed.AddEntry(entry)

	output, err := feed.GenerateXML()
	if err != nil {
		t.Fatalf("failed to generate XML: %v", err)
	}

	outputStr := string(output)

	// Check for essential OPDS elements
	requiredStrings := []string{
		`<feed`,
		`xmlns="http://www.w3.org/2005/Atom"`,
		`xmlns:opds="http://opds-spec.org/2010/"`,
		`xmlns:dc="http://purl.org/dc/elements/1.1/"`,
		`<id>urn:uuid:test-id</id>`,
		`<title>Test Feed</title>`,
		`<entry>`,
		`<id>urn:uuid:book-id</id>`,
		`<title>Test Book</title>`,
		`<name>Test Author</name>`,
		`<link href="http://example.com/book.epub"`,
		`rel="http://opds-spec.org/acquisition/open-access"`,
		`<dc:identifier id="bookhoard">book-uuid-123</dc:identifier>`,
		`<meta property="bookhoard:sha256">abc123...</meta>`,
	}

	for _, required := range requiredStrings {
		if !contains(outputStr, required) {
			t.Errorf("generated XML missing required string: %s", required)
		}
	}
}

func TestNewErrorFeed(t *testing.T) {
	feed := NewErrorFeed("Test error message")

	if feed.Title != "Error" {
		t.Errorf("expected Title to be 'Error', got '%s'", feed.Title)
	}

	if len(feed.Entries) != 1 {
		t.Fatalf("expected 1 entry in error feed, got %d", len(feed.Entries))
	}

	if feed.Entries[0].Summary != "Test error message" {
		t.Errorf("expected error message to be 'Test error message', got '%s'", feed.Entries[0].Summary)
	}
}

func TestFeedSetPagination(t *testing.T) {
	feed := NewFeed("urn:uuid:test-id", "Test Feed")
	feed.SetPagination(1814, 50, 51)

	if feed.TotalResults == nil || *feed.TotalResults != 1814 {
		t.Errorf("expected TotalResults to be 1814, got %v", feed.TotalResults)
	}
	if feed.ItemsPerPage == nil || *feed.ItemsPerPage != 50 {
		t.Errorf("expected ItemsPerPage to be 50, got %v", feed.ItemsPerPage)
	}
	if feed.StartIndex == nil || *feed.StartIndex != 51 {
		t.Errorf("expected StartIndex to be 51, got %v", feed.StartIndex)
	}
}

func TestFeedGenerateXMLPagination(t *testing.T) {
	feed := NewFeed("urn:uuid:test-id", "Test Feed")
	feed.AddLink("http://example.com/catalog?page=1", "application/atom+xml", "first")
	feed.AddLink("http://example.com/catalog?page=1", "application/atom+xml", "previous")
	feed.AddLink("http://example.com/catalog?page=2", "application/atom+xml", "self")
	feed.AddLink("http://example.com/catalog?page=3", "application/atom+xml", "next")
	feed.AddLink("http://example.com/catalog?page=37", "application/atom+xml", "last")
	feed.SetPagination(1814, 50, 51)

	output, err := feed.GenerateXML()
	if err != nil {
		t.Fatalf("failed to generate XML: %v", err)
	}
	outputStr := string(output)

	requiredStrings := []string{
		`xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/"`,
		`<opensearch:totalResults>1814</opensearch:totalResults>`,
		`<opensearch:itemsPerPage>50</opensearch:itemsPerPage>`,
		`<opensearch:startIndex>51</opensearch:startIndex>`,
		`rel="first"`,
		`rel="previous"`,
		`rel="next"`,
		`rel="last"`,
		`page=3`,
	}

	for _, required := range requiredStrings {
		if !contains(outputStr, required) {
			t.Errorf("generated XML missing required string: %s", required)
		}
	}
}

func TestFeedGenerateXMLOmitsPaginationWhenUnset(t *testing.T) {
	feed := NewFeed("urn:uuid:test-id", "Test Feed")

	output, err := feed.GenerateXML()
	if err != nil {
		t.Fatalf("failed to generate XML: %v", err)
	}
	outputStr := string(output)

	if contains(outputStr, "opensearch:totalResults") {
		t.Errorf("expected no totalResults when pagination unset, but found it")
	}
	if contains(outputStr, "opensearch:itemsPerPage") {
		t.Errorf("expected no itemsPerPage when pagination unset, but found it")
	}
}

func TestNewSearchDescription(t *testing.T) {
	template := "http://example.com/opds/devices/abc/search?q={searchTerms}&token=xyz"
	desc := NewSearchDescription("Bookhoard", "Search the library", template)

	if desc.ShortName != "Bookhoard" {
		t.Errorf("expected ShortName 'Bookhoard', got '%s'", desc.ShortName)
	}
	if desc.Url.Template != template {
		t.Errorf("expected template '%s', got '%s'", template, desc.Url.Template)
	}
}

func TestSearchDescriptionGenerateXML(t *testing.T) {
	template := "http://example.com/opds/devices/abc/search?q={searchTerms}"
	desc := NewSearchDescription("Bookhoard", "Search the library", template)

	output, err := desc.GenerateXMLString()
	if err != nil {
		t.Fatalf("failed to generate XML: %v", err)
	}

	requiredStrings := []string{
		`<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">`,
		`<ShortName>Bookhoard</ShortName>`,
		`<Url type="application/atom+xml;profile=opds-catalog;kind=acquisition"`,
		`template="http://example.com/opds/devices/abc/search?q={searchTerms}"`,
	}

	for _, required := range requiredStrings {
		if !contains(output, required) {
			t.Errorf("generated OpenSearch XML missing required string: %s", required)
		}
	}
}

func contains(s, substr string) bool {
	return len(s) >= len(substr) && indexOf(s, substr) >= 0
}

func indexOf(s, substr string) int {
	for i := 0; i <= len(s)-len(substr); i++ {
		if s[i:i+len(substr)] == substr {
			return i
		}
	}
	return -1
}
