package render

import (
	"encoding/base64"
	"encoding/json"
	"net/url"
	"strconv"
	"strings"
)

type PageLink struct {
	Number   int
	URL      string
	Active   bool
	Ellipsis bool
	Disabled bool
}

type Pagination struct {
	Page         int
	PageSize     int
	ItemCount    int
	Total        int
	TotalKnown   bool
	TotalPages   int
	HasMore      bool
	HasPrev      bool
	TotalDisplay string
	TotalExact   bool
	RangeStart   int
	RangeEnd     int
	PrevURL      string
	NextURL      string
	FirstURL     string
	Links        []PageLink
}

func ParsePage(q url.Values) int {
	p, err := strconv.Atoi(q.Get("page"))
	if err != nil || p < 1 {
		return 1
	}
	return p
}

func ParseCursorHistory(q url.Values) []string {
	raw := strings.TrimSpace(q.Get("pch"))
	if raw == "" {
		return nil
	}
	decoded, err := base64.RawURLEncoding.DecodeString(raw)
	if err != nil {
		return nil
	}
	var hist []string
	if err := json.Unmarshal(decoded, &hist); err != nil {
		return nil
	}
	return hist
}

func EncodeCursorHistory(hist []string) string {
	if len(hist) == 0 {
		return ""
	}
	b, err := json.Marshal(hist)
	if err != nil {
		return ""
	}
	return base64.RawURLEncoding.EncodeToString(b)
}

func BuildBuscaPageURL(base url.Values, page int, cursor string, hist []string) string {
	return buildListingPageURL("/busca", base, page, cursor, hist)
}

func BuildAppBuscaPageURL(base url.Values, page int, cursor string, hist []string) string {
	return buildListingPageURL("/app/busca", base, page, cursor, hist)
}

func buildListingPageURL(basePath string, base url.Values, page int, cursor string, hist []string) string {
	q := cloneSearchQuery(base)
	if page > 1 {
		q.Set("page", strconv.Itoa(page))
	}
	if page > 1 && cursor != "" {
		q.Set("cursor", cursor)
	}
	if page > 2 && len(hist) > 0 {
		if enc := EncodeCursorHistory(hist); enc != "" {
			q.Set("pch", enc)
		}
	}
	return basePath + "?" + q.Encode()
}

func cloneSearchQuery(base url.Values) url.Values {
	q := url.Values{}
	for k, vals := range base {
		if k == "page" || k == "cursor" || k == "pch" {
			continue
		}
		for _, v := range vals {
			q.Add(k, v)
		}
	}
	return q
}

func BuildPagination(r *url.URL, page, pageSize, itemCount int, total *int, hasMore bool, nextCursor string) Pagination {
	return BuildPaginationForPath(r, "/busca", page, pageSize, itemCount, total, hasMore, nextCursor)
}

func BuildPaginationForPath(r *url.URL, basePath string, page, pageSize, itemCount int, total *int, hasMore bool, nextCursor string) Pagination {
	base := r.Query()
	hist := ParseCursorHistory(base)
	if page < 1 {
		page = 1
	}
	if pageSize < 1 {
		pageSize = 10
	}

	totalKnown := total != nil
	totalVal := 0
	if totalKnown {
		totalVal = *total
	}

	totalPages := 0
	if totalKnown && totalVal > 0 && pageSize > 0 {
		totalPages = (totalVal + pageSize - 1) / pageSize
	}

	rangeStart := 0
	rangeEnd := 0
	if itemCount > 0 {
		rangeStart = (page-1)*pageSize + 1
		rangeEnd = rangeStart + itemCount - 1
		if totalKnown && totalVal > 0 && rangeEnd > totalVal {
			rangeEnd = totalVal
		}
	}

	totalDisplay := ""
	if totalKnown {
		totalDisplay = FormatNumber(totalVal)
	}

	p := Pagination{
		Page:         page,
		PageSize:     pageSize,
		ItemCount:    itemCount,
		Total:        totalVal,
		TotalKnown:   totalKnown,
		TotalPages:   totalPages,
		HasMore:      hasMore,
		HasPrev:      page > 1,
		TotalDisplay: totalDisplay,
		TotalExact:   totalKnown,
		RangeStart:   rangeStart,
		RangeEnd:     rangeEnd,
		FirstURL:     buildListingPageURL(basePath, base, 1, "", nil),
	}

	if page > 1 {
		prevPage := page - 1
		cur := base.Get("cursor")
		p.PrevURL = buildListingPageURL(basePath, base, prevPage, cursorForPage(prevPage, page, hist, cur), histForPage(prevPage, hist))
	}

	if hasMore && nextCursor != "" {
		nextHist := append([]string{}, hist...)
		if page > 1 {
			nextHist = append(nextHist, base.Get("cursor"))
		}
		p.NextURL = buildListingPageURL(basePath, base, page+1, nextCursor, nextHist)
	}

	p.Links = buildPageLinksForPath(p, basePath, base, hist)
	return p
}

func buildPageLinks(p Pagination, base url.Values, hist []string) []PageLink {
	return buildPageLinksForPath(p, "/busca", base, hist)
}

func buildPageLinksForPath(p Pagination, basePath string, base url.Values, hist []string) []PageLink {
	var links []PageLink

	addPage := func(num int) {
		if num < 1 {
			return
		}
		cursor := cursorForPage(num, p.Page, hist, base.Get("cursor"))
		h := histForPage(num, hist)
		links = append(links, PageLink{
			Number: num,
			URL:    buildListingPageURL(basePath, base, num, cursor, h),
			Active: num == p.Page,
		})
	}

	addEllipsis := func() {
		links = append(links, PageLink{Ellipsis: true})
	}

	if p.Page > 2 {
		addPage(1)
		if p.Page > 3 {
			addEllipsis()
		}
	}

	if p.Page > 1 {
		addPage(p.Page - 1)
	}

	addPage(p.Page)

	if p.HasMore && (p.TotalPages == 0 || p.Page+1 <= p.TotalPages) {
		addPage(p.Page + 1)
	}

	if p.TotalPages > 1 && p.Page < p.TotalPages {
		lastIsNext := p.HasMore && p.Page+1 == p.TotalPages
		if !lastIsNext {
			if p.Page+2 < p.TotalPages {
				addEllipsis()
			}
			links = append(links, PageLink{
				Number:   p.TotalPages,
				Disabled: true,
			})
		}
	}

	return links
}

func cursorForPage(num, currentPage int, hist []string, currentCursor string) string {
	if num <= 1 {
		return ""
	}
	if num == currentPage {
		return currentCursor
	}
	if num-2 >= 0 && num-2 < len(hist) {
		return hist[num-2]
	}
	return ""
}

func histForPage(num int, hist []string) []string {
	if num <= 2 {
		return nil
	}
	end := num - 2
	if end > len(hist) {
		end = len(hist)
	}
	if end <= 0 {
		return nil
	}
	return hist[:end]
}

func FormatNumber(n int) string {
	if n < 0 {
		n = -n
	}
	s := strconv.Itoa(n)
	if len(s) <= 3 {
		return s
	}
	var parts []string
	for len(s) > 3 {
		parts = append([]string{s[len(s)-3:]}, parts...)
		s = s[:len(s)-3]
	}
	parts = append([]string{s}, parts...)
	return strings.Join(parts, ".")
}
