package query

import (
	"fmt"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"
)

const (
	DefaultPage     = 1
	DefaultPageSize = 50
	MaxPageSize     = 500

	dateLayout = "2006-01-02"
)

// ListParams holds normalized list query parameters.
type ListParams struct {
	Page     int
	PageSize int
	Q        string
	Sort     string
	From     *time.Time
	To       *time.Time
}

// Offset returns the SQL/list offset derived from page and page_size.
func (p ListParams) Offset() int {
	return (p.Page - 1) * p.PageSize
}

// Limit returns the page size as a fetch limit.
func (p ListParams) Limit() int {
	return p.PageSize
}

// PaginationMeta describes list pagination in API responses.
type PaginationMeta struct {
	Page     int    `json:"page"`
	PageSize int    `json:"page_size"`
	Total    *int64 `json:"total,omitempty"`
}

// Meta builds response pagination metadata for the current params.
func (p ListParams) Meta(total *int64) PaginationMeta {
	return PaginationMeta{
		Page:     p.Page,
		PageSize: p.PageSize,
		Total:    total,
	}
}

// FieldDetail identifies a single invalid query parameter.
type FieldDetail struct {
	Field   string `json:"field"`
	Message string `json:"message"`
}

// ValidationError reports invalid query parameters using the standard API error shape.
type ValidationError struct {
	Code    string        `json:"code"`
	Message string        `json:"message"`
	Details []FieldDetail `json:"details,omitempty"`
}

func (e *ValidationError) Error() string {
	if len(e.Details) == 0 {
		return e.Message
	}
	parts := make([]string, len(e.Details))
	for i, d := range e.Details {
		parts[i] = fmt.Sprintf("%s: %s", d.Field, d.Message)
	}
	return e.Message + ": " + strings.Join(parts, "; ")
}

// ParseDate parses a YYYY-MM-DD date in UTC at midnight.
func ParseDate(raw string) (time.Time, error) {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return time.Time{}, fmt.Errorf("empty date")
	}
	t, err := time.ParseInLocation(dateLayout, raw, time.UTC)
	if err != nil {
		return time.Time{}, fmt.Errorf("expected YYYY-MM-DD")
	}
	return t, nil
}

// ParseList parses list query parameters from url.Values.
func ParseList(values url.Values) (ListParams, error) {
	var params ListParams
	var details []FieldDetail

	page, pageErr := parsePage(values.Get("page"))
	if pageErr != nil {
		details = append(details, FieldDetail{Field: "page", Message: pageErr.Error()})
	} else {
		params.Page = page
	}

	pageSize, sizeErr := parsePageSize(values.Get("page_size"))
	if sizeErr != nil {
		details = append(details, FieldDetail{Field: "page_size", Message: sizeErr.Error()})
	} else {
		params.PageSize = pageSize
	}

	params.Q = strings.TrimSpace(values.Get("q"))
	params.Sort = strings.TrimSpace(values.Get("sort"))

	fromRaw := strings.TrimSpace(values.Get("from"))
	toRaw := strings.TrimSpace(values.Get("to"))

	if fromRaw != "" {
		from, err := ParseDate(fromRaw)
		if err != nil {
			details = append(details, FieldDetail{Field: "from", Message: err.Error()})
		} else {
			params.From = &from
		}
	}

	if toRaw != "" {
		to, err := ParseDate(toRaw)
		if err != nil {
			details = append(details, FieldDetail{Field: "to", Message: err.Error()})
		} else {
			end := to.Add(24*time.Hour - time.Nanosecond)
			params.To = &end
		}
	}

	if params.From != nil && params.To != nil && params.From.After(*params.To) {
		details = append(details, FieldDetail{
			Field:   "from",
			Message: "must be on or before to",
		})
	}

	if len(details) > 0 {
		return ListParams{}, &ValidationError{
			Code:    "invalid_query_param",
			Message: "invalid query parameters",
			Details: details,
		}
	}

	return params, nil
}

// ParseListRequest parses list query parameters from an HTTP request.
func ParseListRequest(r *http.Request) (ListParams, error) {
	if r == nil {
		return ListParams{}, &ValidationError{
			Code:    "invalid_query_param",
			Message: "invalid query parameters",
			Details: []FieldDetail{{Field: "request", Message: "missing request"}},
		}
	}
	return ParseList(r.URL.Query())
}

func parsePage(raw string) (int, error) {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return DefaultPage, nil
	}
	page, err := strconv.Atoi(raw)
	if err != nil {
		return 0, fmt.Errorf("must be a positive integer")
	}
	if page < 1 {
		return 0, fmt.Errorf("must be at least 1")
	}
	return page, nil
}

func parsePageSize(raw string) (int, error) {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return DefaultPageSize, nil
	}
	size, err := strconv.Atoi(raw)
	if err != nil {
		return 0, fmt.Errorf("must be a positive integer")
	}
	if size < 1 {
		return 0, fmt.Errorf("must be at least 1")
	}
	if size > MaxPageSize {
		return 0, fmt.Errorf("must be at most %d", MaxPageSize)
	}
	return size, nil
}
