package docs

import (
	"bytes"
	"fmt"
	"io/fs"
	"os"
	"strings"

	"bookhoard/templates"

	"github.com/yuin/goldmark"
	highlighting "github.com/yuin/goldmark-highlighting"
	"github.com/yuin/goldmark/extension"
	"github.com/yuin/goldmark/parser"
	"github.com/yuin/goldmark/renderer/html"
	"golang.org/x/text/cases"
	"golang.org/x/text/language"
)

type DocsHandler struct {
	markdown goldmark.Markdown
	docsPath string
	docsFS   fs.FS
}

func NewDocsHandler(docsPath string) *DocsHandler {
	// Create goldmark renderer with syntax highlighting
	md := goldmark.New(
		goldmark.WithExtensions(
			extension.GFM,
			extension.Table,
			highlighting.NewHighlighting(
				highlighting.WithStyle("github"),
			),
		),
		goldmark.WithParserOptions(
			parser.WithAutoHeadingID(),
		),
		goldmark.WithRendererOptions(
			html.WithHardWraps(),
			html.WithXHTML(),
			html.WithUnsafe(),
		),
	)

	return &DocsHandler{
		markdown: md,
		docsPath: docsPath,
		docsFS:   os.DirFS(docsPath),
	}
}

// LoadDocument loads and renders a markdown document
func (h *DocsHandler) LoadDocument(docPath string) (*templates.Document, error) {
	// Clean the path
	docPath = strings.TrimPrefix(docPath, "/")
	docPath = strings.TrimSuffix(docPath, "/")

	// Default to INDEX.md if root
	if docPath == "" || docPath == "docs" {
		docPath = "index.md"
	} else {
		// Remove /docs prefix if present
		docPath = strings.TrimPrefix(docPath, "docs/")

		// Add .md if not present
		if !strings.HasSuffix(docPath, ".md") {
			docPath += ".md"
		}
	}

	// Read the markdown file
	content, err := fs.ReadFile(h.docsFS, docPath)
	if err != nil {
		return nil, fmt.Errorf("failed to read document: %w", err)
	}

	// Convert markdown to HTML
	var buf bytes.Buffer
	context := parser.NewContext()
	if err := h.markdown.Convert(content, &buf, parser.WithContext(context)); err != nil {
		return nil, fmt.Errorf("failed to convert markdown: %w", err)
	}

	// Debug: Log the raw HTML output from goldmark
	htmlOutput := buf.String()
	fmt.Printf("DEBUG: Goldmark output length: %d, first 200 chars: %q\n", len(htmlOutput), htmlOutput[:min(200, len(htmlOutput))])

	// Extract title from first heading
	title := h.extractTitle(content)

	// Generate table of contents
	toc := h.generateTOC(content)

	// Generate breadcrumb
	breadcrumb := h.generateBreadcrumb(docPath)

	// Determine category
	category := h.getCategory(docPath)

	return &templates.Document{
		Title:      title,
		Content:    buf.String(),
		TOC:        toc,
		Breadcrumb: breadcrumb,
		Category:   category,
		SourceFile: docPath,
	}, nil
}

// extractTitle extracts the first heading from markdown content
func (h *DocsHandler) extractTitle(content []byte) string {
	lines := bytes.Split(content, []byte("\n"))
	for _, line := range lines {
		line = bytes.TrimSpace(line)
		if bytes.HasPrefix(line, []byte("# ")) {
			return string(bytes.TrimPrefix(line, []byte("# ")))
		}
	}
	return "Documentation"
}

// generateTOC generates a table of contents from markdown headings
func (h *DocsHandler) generateTOC(content []byte) []templates.TOCItem {
	var toc []templates.TOCItem
	lines := bytes.Split(content, []byte("\n"))

	for _, line := range lines {
		line = bytes.TrimSpace(line)
		if bytes.HasPrefix(line, []byte("#")) && !bytes.HasPrefix(line, []byte("# ")) {
			// Count heading level
			level := 0
			for _, c := range line {
				if c == '#' {
					level++
				} else {
					break
				}
			}

			if level > 1 && level <= 4 { // Only ## ### ####
				title := strings.TrimSpace(string(bytes.TrimLeft(line, "#")))
				anchor := h.slugify(title)

				toc = append(toc, templates.TOCItem{
					Level:  level,
					Title:  title,
					Anchor: anchor,
				})
			}
		}
	}

	return toc
}

// generateBreadcrumb generates breadcrumb navigation
func (h *DocsHandler) generateBreadcrumb(docPath string) []templates.BreadcrumbItem {
	parts := strings.Split(strings.TrimSuffix(docPath, ".md"), "/")
	breadcrumb := []templates.BreadcrumbItem{
		{Title: "Docs", URL: "/docs"},
	}

	path := ""
	for i, part := range parts {
		if part == "" {
			continue
		}
		path += "/" + part
		// Don't add the last part (current page)
		if i < len(parts)-1 {
			breadcrumb = append(breadcrumb, templates.BreadcrumbItem{
				Title: cases.Title(language.English).String(strings.ReplaceAll(part, "-", " ")),
				URL:   "/docs" + path,
			})
		}
	}

	return breadcrumb
}

// getCategory determines the category of a document
func (h *DocsHandler) getCategory(docPath string) string {
	if strings.Contains(docPath, "api") || strings.Contains(docPath, "API") {
		return "API Reference"
	}
	if strings.Contains(docPath, "devices") {
		return "Device Setup"
	}
	if strings.Contains(docPath, "contributing") || strings.Contains(docPath, "DEVELOPMENT") {
		return "Contributing"
	}
	if strings.Contains(docPath, "SYNC") {
		return "Sync Guide"
	}
	return "Documentation"
}

// slugify converts a string to a URL-safe slug
func (h *DocsHandler) slugify(s string) string {
	s = strings.ToLower(s)
	s = strings.ReplaceAll(s, " ", "-")
	s = strings.ReplaceAll(s, "/", "-")

	// Remove non-alphanumeric characters (except hyphens)
	var result strings.Builder
	for _, c := range s {
		if (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' {
			result.WriteRune(c)
		}
	}

	return result.String()
}

// ListDocuments returns all markdown files in the docs directory
func (h *DocsHandler) ListDocuments() ([]string, error) {
	var docs []string

	err := fs.WalkDir(h.docsFS, ".", func(path string, d fs.DirEntry, err error) error {
		if err != nil {
			return err
		}
		if d.IsDir() {
			return nil
		}
		if strings.HasSuffix(path, ".md") {
			docs = append(docs, strings.TrimSuffix(path, ".md"))
		}
		return nil
	})

	return docs, err
}

// GetAPIEndpointData returns endpoint data for the API explorer
func (h *DocsHandler) GetAPIEndpointData(endpointPath string) (*templates.EndpointInfo, error) {
	// Map endpoint path to data
	// For now, return static examples based on the endpoint path
	// In a full implementation, this would parse the markdown files

	// Clean the path
	endpointPath = strings.TrimPrefix(endpointPath, "api/")
	endpointPath = strings.TrimSuffix(endpointPath, ".md")

	// Map paths to endpoints
	endpoints := map[string]templates.EndpointInfo{
		"authentication/register": {
			Method: "POST",
			Path:   "/api/auth/register",
			RequestBody: `{
  "email": "user@example.com",
  "username": "john",
  "password": "SecureP@ss123!",
  "first_name": "John",
  "last_name": "Doe"
}`,
			Response: `{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "d4f5g6h7...",
  "user": {
    "id": "uuid-here",
    "email": "user@example.com",
    "username": "john",
    "role": "user",
    "theme": "tokyo-night",
    "created_at": "2026-01-31T10:00:00Z"
  }
}`,
			Description: "Create a new user account",
		},
		"authentication/login": {
			Method: "POST",
			Path:   "/api/auth/login",
			RequestBody: `{
  "email": "user@example.com",
  "password": "SecureP@ss123!"
}`,
			Response: `{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "d4f5g6h7...",
  "user": {
    "id": "uuid-here",
    "email": "user@example.com",
    "username": "john",
    "role": "user"
  }
}`,
			Description: "Authenticate with email and password",
		},
		"authentication/refresh_token": {
			Method: "POST",
			Path:   "/api/auth/refresh",
			RequestBody: `{
  "refresh_token": "d4f5g6h7..."
}`,
			Response: `{
  "token": "new-jwt-token",
  "refresh_token": "new-refresh-token"
}`,
			Description: "Obtain a new JWT token using a refresh token",
		},
		"authentication/logout": {
			Method:      "POST",
			Path:        "/api/auth/logout",
			RequestBody: `{}`,
			Response:    `{}`,
			Description: "Invalidate the current JWT token",
		},
		"users/get_profile": {
			Method:      "GET",
			Path:        "/api/users/me",
			RequestBody: `{}`,
			Response: `{
  "id": "uuid",
  "email": "user@example.com",
  "username": "john",
  "first_name": "John",
  "last_name": "Doe",
  "theme": "tokyo-night",
  "role": "user",
  "max_devices": 10,
  "created_at": "2026-01-31T10:00:00Z"
}`,
			Description: "Retrieve the current authenticated user's profile",
		},
		"users/update_profile": {
			Method: "PUT",
			Path:   "/api/users/me/profile",
			RequestBody: `{
  "first_name": "John",
  "last_name": "Smith"
}`,
			Response: `{
  "id": "uuid",
  "email": "user@example.com",
  "username": "john",
  "first_name": "John",
  "last_name": "Smith",
  "theme": "tokyo-night",
  "role": "user"
}`,
			Description: "Update the current user's profile information",
		},
		"users/change_password": {
			Method: "PUT",
			Path:   "/api/users/me/password",
			RequestBody: `{
  "current_password": "oldPassword",
  "new_password": "NewSecureP@ss123!"
}`,
			Response:    `{}`,
			Description: "Change the current user's password",
		},
	}

	if endpoint, ok := endpoints[endpointPath]; ok {
		return &endpoint, nil
	}

	return nil, fmt.Errorf("endpoint not found: %s", endpointPath)
}

// SearchDoc represents a document for search indexing
type SearchDoc struct {
	ID      string `json:"id"`
	Title   string `json:"title"`
	Content string `json:"content"`
	URL     string `json:"url"`
}

// GenerateSearchIndex generates a search index for all documentation
func (h *DocsHandler) GenerateSearchIndex() ([]SearchDoc, error) {
	docs, err := h.ListDocuments()
	if err != nil {
		return nil, fmt.Errorf("failed to list documents: %w", err)
	}

	var searchDocs []SearchDoc
	for _, path := range docs {
		doc, err := h.LoadDocument(path)
		if err != nil {
			continue
		}

		// Strip HTML tags for better search
		content := h.stripHTML(doc.Content)

		searchDocs = append(searchDocs, SearchDoc{
			ID:      path,
			Title:   doc.Title,
			Content: content,
			URL:     "/docs/" + strings.TrimSuffix(path, ".md"),
		})
	}

	return searchDocs, nil
}

// stripHTML removes HTML tags from string
func (h *DocsHandler) stripHTML(html string) string {
	var result strings.Builder
	inTag := false
	for _, r := range html {
		if r == '<' {
			inTag = true
			continue
		}
		if r == '>' {
			inTag = false
			continue
		}
		if !inTag {
			result.WriteRune(r)
		}
	}
	return result.String()
}
