package querycodec

import (
	"context"
	"encoding/base64"
	"encoding/json"
	"net/url"
	"strings"
	"testing"
	"time"
)

func anchorLocator(t *testing.T, raw string, anchor json.RawMessage) Route {
	t.Helper()
	fields := map[string]any{"q": url.QueryEscape(raw), "limit": 200, "offset": 200}
	if anchor != nil {
		fields["dateAnchor"] = anchor
	}
	data, err := json.Marshal(fields)
	if err != nil {
		t.Fatal(err)
	}
	return Route{Kind: RouteMore, Version: "v63.0", Locator: base64.URLEncoding.EncodeToString(data)}
}

func TestQueryMoreRejectsIncompleteDateAnchorsBeforeDependencies(t *testing.T) {
	for _, tc := range []struct{ name, anchor string }{
		{"missing", ""}, {"null", "null"}, {"empty object", "{}"},
		{"missing timezone", `{"instant":"2026-09-08T00:30:00Z"}`},
		{"empty timezone", `{"instant":"2026-09-08T00:30:00Z","timeZone":""}`},
		{"blank timezone", `{"instant":"2026-09-08T00:30:00Z","timeZone":" "}`},
		{"host timezone", `{"instant":"2026-09-08T00:30:00Z","timeZone":"Local"}`},
		{"unknown timezone", `{"instant":"2026-09-08T00:30:00Z","timeZone":"Missing/Zone"}`},
		{"missing instant", `{"timeZone":"UTC"}`},
		{"null instant", `{"instant":null,"timeZone":"UTC"}`},
		{"zero instant", `{"instant":"0001-01-01T00:00:00Z","timeZone":"UTC"}`},
		{"outside calendar", `{"instant":"0000-01-01T00:00:00Z","timeZone":"UTC"}`},
		{"malformed instant", `{"instant":"not-a-date","timeZone":"UTC"}`},
		{"wrong type", `{"instant":"2026-09-08T00:30:00Z","timeZone":42}`},
	} {
		t.Run(tc.name, func(t *testing.T) {
			h, err := NewHandler(Executor{
				Dates: func(context.Context, *QueryRequest, *DateAnchor) (context.Context, error) {
					t.Fatal("invalid cursor reached calendar dependency")
					return nil, nil
				},
				Query: func(context.Context, QueryRequest, int) (Result, error) {
					t.Fatal("invalid cursor executed query")
					return Result{}, nil
				},
				History: func(context.Context, HistoryRequest) (HistoryResult, error) {
					t.Fatal("invalid cursor executed history")
					return HistoryResult{}, nil
				},
			})
			if err != nil {
				t.Fatal(err)
			}
			for _, raw := range []string{"SELECT Id FROM Item WHERE CloseDate = TODAY", "SELECT Id, (SELECT Id FROM Children WHERE CloseDate = TODAY) FROM Item", "SELECT Id FROM Item WHERE Id IN (SELECT ParentId FROM Child WHERE CloseDate = TODAY)"} {
				var anchor json.RawMessage
				if tc.anchor != "" {
					anchor = json.RawMessage(tc.anchor)
				}
				result := h.Handle(context.Background(), Request{Route: anchorLocator(t, raw, anchor)})
				body, _ := json.Marshal(result.Body)
				if result.Status != 400 || !strings.Contains(string(body), `"errorCode":"INVALID_QUERY_LOCATOR"`) {
					t.Fatalf("query=%s status=%d body=%s", raw, result.Status, body)
				}
			}
		})
	}
}

func TestQueryMoreUsesExplicitCalendarAndAcceptsLegacyOrdinaryLocator(t *testing.T) {
	for _, tc := range []struct{ zone, date string }{{"UTC", "2026-09-08"}, {"America/Los_Angeles", "2026-09-07"}, {"", ""}} {
		t.Run(tc.zone, func(t *testing.T) {
			calendar := testDateContext(t, "2026-10-10T12:00:00Z", "Asia/Tokyo")
			calls, dates := 0, 0
			h, _ := NewHandler(Executor{
				Dates: func(ctx context.Context, q *QueryRequest, a *DateAnchor) (context.Context, error) {
					dates++
					return ctx, ResolveRelativeDates(ctx, q, calendar, a)
				},
				Query: func(_ context.Context, q QueryRequest, _ int) (Result, error) {
					calls++
					if tc.zone != "" {
						if q.DateAnchor.TimeZone != tc.zone || q.Filter.Children[0].Right.Value != DateLiteralValue(tc.date) {
							t.Fatalf("calendar changed: %#v", q)
						}
					}
					return Result{Done: true}, nil
				},
				History: func(context.Context, HistoryRequest) (HistoryResult, error) {
					t.Fatal("unexpected history")
					return HistoryResult{}, nil
				},
			})
			raw := "SELECT Id FROM Item"
			var anchor json.RawMessage
			if tc.zone != "" {
				raw += " WHERE CloseDate = TODAY"
				instant, _ := time.Parse(time.RFC3339, "2026-09-08T00:30:00Z")
				anchor, _ = json.Marshal(DateAnchor{Instant: instant, TimeZone: tc.zone})
			}
			result := h.Handle(context.Background(), Request{Route: anchorLocator(t, raw, anchor)})
			wantDates := 1
			if tc.zone == "" {
				wantDates = 0
			}
			if result.Status != 200 || calls != 1 || dates != wantDates {
				t.Fatalf("response=%#v calls=%d dates=%d", result, calls, dates)
			}
		})
	}
}

func TestDirectDateResolutionDoesNotDefaultEmptyAnchorToUTC(t *testing.T) {
	calendar := testDateContext(t, "2026-09-08T00:30:00Z", "America/Los_Angeles")
	q, err := ParseSOQL("SELECT Id FROM Item WHERE CloseDate = TODAY")
	if err != nil {
		t.Fatal(err)
	}
	if err = ResolveRelativeDates(context.Background(), q, calendar, &DateAnchor{Instant: calendar.Now}); err == nil {
		t.Fatal("missing anchor timezone defaulted to UTC")
	}
	if q.DateAnchor != nil {
		t.Fatal("invalid anchor published")
	}
}
