package query

import (
	"errors"
	"net/http"
	"net/url"
	"testing"
	"time"
)

func TestParseList_defaults(t *testing.T) {
	params, err := ParseList(url.Values{})
	if err != nil {
		t.Fatalf("ParseList() error = %v", err)
	}
	if params.Page != DefaultPage {
		t.Fatalf("Page = %d, want %d", params.Page, DefaultPage)
	}
	if params.PageSize != DefaultPageSize {
		t.Fatalf("PageSize = %d, want %d", params.PageSize, DefaultPageSize)
	}
	if params.Q != "" || params.Sort != "" {
		t.Fatalf("Q/Sort should be empty, got q=%q sort=%q", params.Q, params.Sort)
	}
	if params.From != nil || params.To != nil {
		t.Fatal("From/To should be nil")
	}
	if params.Offset() != 0 {
		t.Fatalf("Offset() = %d, want 0", params.Offset())
	}
	if params.Limit() != DefaultPageSize {
		t.Fatalf("Limit() = %d, want %d", params.Limit(), DefaultPageSize)
	}
}

func TestParseList_valid(t *testing.T) {
	values := url.Values{
		"page":      {"3"},
		"page_size": {"25"},
		"q":         {"  hello  "},
		"sort":      {"-created_at"},
		"from":      {"2026-01-01"},
		"to":        {"2026-01-31"},
	}

	params, err := ParseList(values)
	if err != nil {
		t.Fatalf("ParseList() error = %v", err)
	}
	if params.Page != 3 || params.PageSize != 25 {
		t.Fatalf("page/page_size = %d/%d", params.Page, params.PageSize)
	}
	if params.Q != "hello" || params.Sort != "-created_at" {
		t.Fatalf("q/sort = %q/%q", params.Q, params.Sort)
	}
	if params.Offset() != 50 {
		t.Fatalf("Offset() = %d, want 50", params.Offset())
	}
	if params.From == nil || params.To == nil {
		t.Fatal("expected From/To to be set")
	}
	if params.From.Format(dateLayout) != "2026-01-01" {
		t.Fatalf("From = %s", params.From.Format(time.RFC3339))
	}
	if params.To.Format(dateLayout) != "2026-01-31" {
		t.Fatalf("To day = %s", params.To.Format(dateLayout))
	}
}

func TestParseList_invalidPage(t *testing.T) {
	tests := []struct {
		name  string
		value string
	}{
		{"zero", "0"},
		{"negative", "-1"},
		{"non_numeric", "abc"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			_, err := ParseList(url.Values{"page": {tt.value}})
			assertValidationError(t, err, "page")
		})
	}
}

func TestParseList_invalidPageSize(t *testing.T) {
	tests := []struct {
		name  string
		value string
	}{
		{"zero", "0"},
		{"negative", "-5"},
		{"too_large", "501"},
		{"non_numeric", "large"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			_, err := ParseList(url.Values{"page_size": {tt.value}})
			assertValidationError(t, err, "page_size")
		})
	}
}

func TestParseList_invalidDates(t *testing.T) {
	tests := []struct {
		name   string
		values url.Values
		field  string
	}{
		{
			name:   "invalid from format",
			values: url.Values{"from": {"01-01-2026"}},
			field:  "from",
		},
		{
			name:   "invalid to format",
			values: url.Values{"to": {"2026/01/31"}},
			field:  "to",
		},
		{
			name:   "from after to",
			values: url.Values{"from": {"2026-02-01"}, "to": {"2026-01-01"}},
			field:  "from",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			_, err := ParseList(tt.values)
			assertValidationError(t, err, tt.field)
		})
	}
}

func TestParseList_multipleErrors(t *testing.T) {
	_, err := ParseList(url.Values{
		"page":      {"0"},
		"page_size": {"501"},
		"from":      {"bad-date"},
	})
	var verr *ValidationError
	if !errors.As(err, &verr) {
		t.Fatalf("expected ValidationError, got %T", err)
	}
	if len(verr.Details) < 3 {
		t.Fatalf("Details len = %d, want >= 3", len(verr.Details))
	}
	if verr.Code != "invalid_query_param" {
		t.Fatalf("Code = %q", verr.Code)
	}
}

func TestParseDate(t *testing.T) {
	got, err := ParseDate("2026-05-22")
	if err != nil {
		t.Fatalf("ParseDate() error = %v", err)
	}
	if got.Location() != time.UTC {
		t.Fatalf("location = %v, want UTC", got.Location())
	}
	if got.Format(dateLayout) != "2026-05-22" {
		t.Fatalf("date = %s", got.Format(dateLayout))
	}

	if _, err := ParseDate("22-05-2026"); err == nil {
		t.Fatal("expected error for invalid format")
	}
}

func TestMeta(t *testing.T) {
	params := ListParams{Page: 2, PageSize: 10}
	total := int64(42)
	meta := params.Meta(&total)

	if meta.Page != 2 || meta.PageSize != 10 {
		t.Fatalf("meta page/page_size = %d/%d", meta.Page, meta.PageSize)
	}
	if meta.Total == nil || *meta.Total != 42 {
		t.Fatalf("meta total = %v", meta.Total)
	}
}

func TestParseListRequest(t *testing.T) {
	req, err := http.NewRequest(http.MethodGet, "/items?page=2&page_size=10&q=test", nil)
	if err != nil {
		t.Fatal(err)
	}

	params, err := ParseListRequest(req)
	if err != nil {
		t.Fatalf("ParseListRequest() error = %v", err)
	}
	if params.Page != 2 || params.PageSize != 10 || params.Q != "test" {
		t.Fatalf("params = %+v", params)
	}
}

func assertValidationError(t *testing.T, err error, field string) {
	t.Helper()
	var verr *ValidationError
	if !errors.As(err, &verr) {
		t.Fatalf("expected ValidationError, got %T: %v", err, err)
	}
	for _, detail := range verr.Details {
		if detail.Field == field {
			return
		}
	}
	t.Fatalf("expected detail for field %q, got %+v", field, verr.Details)
}
