package web

import (
	"bytes"
	"context"
	"html/template"
	"strings"

	"github.com/charmbracelet/soft-serve/git"
	"github.com/charmbracelet/soft-serve/pkg/backend"
	"github.com/charmbracelet/soft-serve/pkg/proto"
	"github.com/microcosm-cc/bluemonday"
	"github.com/yuin/goldmark"
	extension "github.com/yuin/goldmark/extension"
	"github.com/yuin/goldmark/parser"
	goldmarkhtml "github.com/yuin/goldmark/renderer/html"
)

// renderMarkdown converts markdown content to sanitized HTML.
func renderMarkdown(content []byte) (template.HTML, error) {
	var buf bytes.Buffer
	md := goldmark.New(
		goldmark.WithExtensions(extension.GFM),
		goldmark.WithParserOptions(
			parser.WithAutoHeadingID(),
		),
		goldmark.WithRendererOptions(
			goldmarkhtml.WithUnsafe(),
		),
	)

	if err := md.Convert(content, &buf); err != nil {
		return "", err
	}

	policy := bluemonday.UGCPolicy()
	policy.AllowStyling()
	policy.RequireNoFollowOnLinks(false)
	policy.AllowElements("center")
	sanitized := policy.SanitizeBytes(buf.Bytes())

	return template.HTML(sanitized), nil
}

// getServerReadme loads and renders the README from the .soft-serve repository.
// Returns both the raw markdown content and the rendered HTML.
func getServerReadme(ctx context.Context, be *backend.Backend) (raw string, html template.HTML, err error) {
	repos, err := be.Repositories(ctx)
	if err != nil {
		return "", "", err
	}

	for _, r := range repos {
		if r.Name() == ".soft-serve" {
			readme, _, err := backend.Readme(r, nil)
			if err != nil {
				return "", "", err
			}
			if readme != "" {
				html, err := renderMarkdown([]byte(readme))
				if err != nil {
					return "", "", err
				}
				return readme, html, nil
			}
		}
	}

	return "", "", nil
}

// openRepository opens a git repository.
func openRepository(repo proto.Repository) (*git.Repository, error) {
	return repo.Open()
}

// getDefaultBranch returns the default branch name, or empty string if none exists.
func getDefaultBranch(gr *git.Repository) string {
	head, err := gr.HEAD()
	if err != nil || head == nil {
		return ""
	}
	return head.Name().Short()
}

// truncateText truncates text to maxLength characters, respecting word boundaries.
// If truncated, appends "...". Returns empty string if text is empty or maxLength <= 0.
func truncateText(text string, maxLength int) string {
	text = strings.TrimSpace(text)
	if text == "" || maxLength <= 0 {
		return ""
	}

	if len(text) <= maxLength {
		return text
	}

	// Find last space before maxLength
	truncated := text[:maxLength]
	if lastSpace := strings.LastIndex(truncated, " "); lastSpace > 0 {
		truncated = truncated[:lastSpace]
	}

	return truncated + "..."
}

// extractPlainTextFromMarkdown converts markdown to plain text and truncates to maxLength.
// Strips markdown formatting to produce a clean description suitable for meta tags.
func extractPlainTextFromMarkdown(markdown string, maxLength int) string {
	markdown = strings.TrimSpace(markdown)
	if markdown == "" {
		return ""
	}

	// Simple markdown stripping - remove common formatting
	text := markdown

	// Remove headers
	text = strings.ReplaceAll(text, "# ", "")
	text = strings.ReplaceAll(text, "## ", "")
	text = strings.ReplaceAll(text, "### ", "")
	text = strings.ReplaceAll(text, "#### ", "")
	text = strings.ReplaceAll(text, "##### ", "")
	text = strings.ReplaceAll(text, "###### ", "")

	// Remove bold/italic markers
	text = strings.ReplaceAll(text, "**", "")
	text = strings.ReplaceAll(text, "__", "")
	text = strings.ReplaceAll(text, "*", "")
	text = strings.ReplaceAll(text, "_", "")

	// Remove code blocks and inline code
	text = strings.ReplaceAll(text, "`", "")

	// Remove links but keep text: [text](url) -> text
	for strings.Contains(text, "[") && strings.Contains(text, "](") {
		start := strings.Index(text, "[")
		mid := strings.Index(text[start:], "](")
		if mid == -1 {
			break
		}
		end := strings.Index(text[start+mid+2:], ")")
		if end == -1 {
			break
		}
		linkText := text[start+1 : start+mid]
		text = text[:start] + linkText + text[start+mid+2+end+1:]
	}

	// Replace multiple spaces/newlines with single space
	text = strings.Join(strings.Fields(text), " ")

	return truncateText(text, maxLength)
}

// getRepoDescriptionOrFallback returns the repository description if available,
// otherwise returns the fallback text. Result is truncated to 200 characters.
func getRepoDescriptionOrFallback(repo proto.Repository, fallback string) string {
	desc := strings.TrimSpace(repo.Description())
	if desc == "" {
		desc = fallback
	}
	return truncateText(desc, 200)
}
