package utils

import (
	"errors"
	"math"
)

// PaginationConfig holds pagination configuration
type PaginationConfig struct {
	DefaultLimit int `json:"default_limit"`
	MaxLimit     int `json:"max_limit"`
	MinLimit     int `json:"min_limit"`
}

// DefaultPaginationConfig returns default pagination settings
func DefaultPaginationConfig() PaginationConfig {
	return PaginationConfig{
		DefaultLimit: 10,
		MaxLimit:     100,
		MinLimit:     1,
	}
}

// Pagination represents pagination metadata and data
type Pagination struct {
	Limit      int         `json:"limit"`
	Page       int         `json:"page"`
	TotalRows  int64       `json:"total_rows"`
	TotalPages int         `json:"total_pages"`
	Rows       interface{} `json:"rows"`
	HasNext    bool        `json:"has_next"`
	HasPrev    bool        `json:"has_prev"`
	NextPage   *int        `json:"next_page,omitempty"`
	PrevPage   *int        `json:"prev_page,omitempty"`
}

// PaginationMeta represents pagination metadata only
type PaginationMeta struct {
	Limit      int   `json:"limit"`
	Page       int   `json:"page"`
	TotalRows  int64 `json:"total_rows"`
	TotalPages int   `json:"total_pages"`
	HasNext    bool  `json:"has_next"`
	HasPrev    bool  `json:"has_prev"`
	NextPage   *int  `json:"next_page,omitempty"`
	PrevPage   *int  `json:"prev_page,omitempty"`
}

// NewPagination creates a new pagination instance with validation
func NewPagination(page, limit int, totalRows int64) (*Pagination, error) {
	config := DefaultPaginationConfig()
	return NewPaginationWithConfig(page, limit, totalRows, config)
}

// NewPaginationWithConfig creates pagination with custom configuration
func NewPaginationWithConfig(page, limit int, totalRows int64, config PaginationConfig) (*Pagination, error) {
	p := &Pagination{
		Page:      page,
		Limit:     limit,
		TotalRows: totalRows,
	}

	if err := p.ValidateWithConfig(config); err != nil {
		return nil, err
	}

	p.calculateMetadata()
	return p, nil
}

// Validate validates pagination parameters with default config
func (p *Pagination) Validate() error {
	config := DefaultPaginationConfig()
	return p.ValidateWithConfig(config)
}

// ValidateWithConfig validates pagination parameters with custom config
func (p *Pagination) ValidateWithConfig(config PaginationConfig) error {
	// Validate and fix page
	if p.Page < 1 {
		p.Page = 1
	}

	// Validate and fix limit
	if p.Limit < config.MinLimit {
		p.Limit = config.DefaultLimit
	}
	if p.Limit > config.MaxLimit {
		p.Limit = config.MaxLimit
	}

	// Validate total rows
	if p.TotalRows < 0 {
		return errors.New("total rows cannot be negative")
	}

	return nil
}

// GetOffset calculates the SQL offset for the current page
func (p *Pagination) GetOffset() int {
	return (p.Page - 1) * p.Limit
}

// calculateMetadata calculates pagination metadata
func (p *Pagination) calculateMetadata() {
	// Calculate total pages
	if p.TotalRows == 0 {
		p.TotalPages = 0
	} else {
		p.TotalPages = int(math.Ceil(float64(p.TotalRows) / float64(p.Limit)))
	}

	// Calculate has next/prev
	p.HasNext = p.Page < p.TotalPages
	p.HasPrev = p.Page > 1

	// Calculate next/prev page numbers
	if p.HasNext {
		nextPage := p.Page + 1
		p.NextPage = &nextPage
	}
	if p.HasPrev {
		prevPage := p.Page - 1
		p.PrevPage = &prevPage
	}
}

// SetData sets the data for this pagination
func (p *Pagination) SetData(data interface{}) {
	p.Rows = data
}

// GetMeta returns only the metadata without data
func (p *Pagination) GetMeta() PaginationMeta {
	return PaginationMeta{
		Limit:      p.Limit,
		Page:       p.Page,
		TotalRows:  p.TotalRows,
		TotalPages: p.TotalPages,
		HasNext:    p.HasNext,
		HasPrev:    p.HasPrev,
		NextPage:   p.NextPage,
		PrevPage:   p.PrevPage,
	}
}

// IsEmpty returns true if there are no rows
func (p *Pagination) IsEmpty() bool {
	return p.TotalRows == 0
}

// IsFirstPage returns true if this is the first page
func (p *Pagination) IsFirstPage() bool {
	return p.Page == 1
}

// IsLastPage returns true if this is the last page
func (p *Pagination) IsLastPage() bool {
	return p.Page == p.TotalPages || p.TotalPages == 0
}

// GetStartIndex returns the starting index for current page (1-based)
func (p *Pagination) GetStartIndex() int {
	if p.TotalRows == 0 {
		return 0
	}
	return p.GetOffset() + 1
}

// GetEndIndex returns the ending index for current page (1-based)
func (p *Pagination) GetEndIndex() int {
	if p.TotalRows == 0 {
		return 0
	}

	endIndex := p.GetOffset() + p.Limit
	if int64(endIndex) > p.TotalRows {
		return int(p.TotalRows)
	}
	return endIndex
}

// GetPageRange returns a range of page numbers for pagination UI
func (p *Pagination) GetPageRange(maxPages int) []int {
	if p.TotalPages <= maxPages {
		// Show all pages
		pages := make([]int, p.TotalPages)
		for i := 0; i < p.TotalPages; i++ {
			pages[i] = i + 1
		}
		return pages
	}

	// Calculate range around current page
	half := maxPages / 2
	start := p.Page - half
	end := p.Page + half

	// Adjust if we're near the beginning
	if start < 1 {
		start = 1
		end = maxPages
	}

	// Adjust if we're near the end
	if end > p.TotalPages {
		end = p.TotalPages
		start = p.TotalPages - maxPages + 1
		if start < 1 {
			start = 1
		}
	}

	// Generate page range
	pages := make([]int, 0, end-start+1)
	for i := start; i <= end; i++ {
		pages = append(pages, i)
	}

	return pages
}

// Clone creates a copy of the pagination with different page/limit
func (p *Pagination) Clone(page, limit int) (*Pagination, error) {
	return NewPagination(page, limit, p.TotalRows)
}
