package main

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"net/url"
	"strconv"
	"strings"
	"testing"
)

func newTestClient(t *testing.T, h http.HandlerFunc) (*Client, *httptest.Server) {
	t.Helper()
	srv := httptest.NewServer(h)
	t.Cleanup(srv.Close)
	c := &Client{
		BaseURL: srv.URL,
		Key:     "test-key",
		HTTP:    srv.Client(),
	}
	return c, srv
}

func TestBasicAuthHeader(t *testing.T) {
	want := "Basic " + base64.StdEncoding.EncodeToString([]byte("test-key:"))
	var got string
	c, _ := newTestClient(t, func(w http.ResponseWriter, r *http.Request) {
		got = r.Header.Get("Authorization")
		w.WriteHeader(200)
		fmt.Fprint(w, `{}`)
	})
	if err := c.get(apiPrefix+"/accounts/1", nil, nil); err != nil {
		t.Fatalf("get: %v", err)
	}
	if got != want {
		t.Errorf("Authorization header:\n got=%q\nwant=%q", got, want)
	}
}

func TestGetErrorFormatting(t *testing.T) {
	c, _ := newTestClient(t, func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(401)
		fmt.Fprint(w, `forbidden`)
	})
	err := c.get(apiPrefix+"/accounts/1", nil, nil)
	if err == nil {
		t.Fatal("expected error")
	}
	msg := err.Error()
	for _, want := range []string{"GET ", "401", "forbidden", apiPrefix + "/accounts/1"} {
		if !strings.Contains(msg, want) {
			t.Errorf("error %q missing %q", msg, want)
		}
	}
}

// TestPathIDEncoding verifies IDs containing '/' are encoded per spec
// (e.g. john/doe -> john%2Fdoe) and the server sees the encoded form.
func TestPathIDEncoding(t *testing.T) {
	var sawPath string
	c, _ := newTestClient(t, func(w http.ResponseWriter, r *http.Request) {
		sawPath = r.URL.EscapedPath()
		w.WriteHeader(200)
		fmt.Fprint(w, `{"id":"john/doe","name":"x"}`)
	})
	path := apiPrefix + "/accounts/" + pathID("john/doe")
	if err := c.get(path, nil, nil); err != nil {
		t.Fatalf("get: %v", err)
	}
	want := apiPrefix + "/accounts/john%2Fdoe"
	if sawPath != want {
		t.Errorf("server saw path %q, want %q", sawPath, want)
	}
}

// TestListAllPaginates walks 3 pages and concatenates rows.
func TestListAllPaginates(t *testing.T) {
	c, _ := newTestClient(t, func(w http.ResponseWriter, r *http.Request) {
		q, _ := url.ParseQuery(r.URL.RawQuery)
		page, _ := strconv.Atoi(q.Get("page"))
		if page == 0 {
			page = 1
		}
		// 3 pages, 2 items each.
		const pageCount = 3
		if page > pageCount {
			fmt.Fprintf(w, `{"data":[],"paginator":{"page":%d,"page_count":%d,"limit":2,"total_count":6}}`, page, pageCount)
			return
		}
		a := (page-1)*2 + 1
		b := a + 1
		fmt.Fprintf(w, `{"data":[{"id":"%d","name":"n%d"},{"id":"%d","name":"n%d"}],"paginator":{"page":%d,"page_count":%d,"limit":2,"total_count":6}}`,
			a, a, b, b, page, pageCount)
	})
	items, p, err := c.listAll("/accounts", 0, decodeListPage)
	if err != nil {
		t.Fatalf("listAll: %v", err)
	}
	if len(items) != 6 {
		t.Errorf("got %d items, want 6", len(items))
	}
	// Verify last item is what we expect and ordering is preserved.
	var last Account
	if err := json.Unmarshal(items[5], &last); err != nil {
		t.Fatalf("decode last: %v", err)
	}
	if last.ID != "6" || last.Name != "n6" {
		t.Errorf("last item = %+v, want id=6 name=n6", last)
	}
	if p.TotalCount != 6 {
		t.Errorf("synthetic paginator total_count = %d, want 6", p.TotalCount)
	}
}

func TestListAllStopsOnEmptyPage(t *testing.T) {
	calls := 0
	c, _ := newTestClient(t, func(w http.ResponseWriter, r *http.Request) {
		calls++
		// page_count=0 means we should stop immediately on empty data.
		fmt.Fprint(w, `{"data":[],"paginator":{"page":1,"page_count":0,"limit":0,"total_count":0}}`)
	})
	items, _, err := c.listAll("/accounts", 0, decodeListPage)
	if err != nil {
		t.Fatalf("listAll: %v", err)
	}
	if len(items) != 0 {
		t.Errorf("got %d items, want 0", len(items))
	}
	if calls != 1 {
		t.Errorf("hit server %d times, want 1", calls)
	}
}

func TestValidateLimit(t *testing.T) {
	cases := []struct {
		limit   int
		wantErr bool
	}{
		{0, false},
		{100, false},
		{500, false},
		{1000, false},
		{99, true},
		{1001, true},
		{-1, true},
	}
	for _, tc := range cases {
		err := validateLimit(tc.limit)
		if (err != nil) != tc.wantErr {
			t.Errorf("validateLimit(%d) err=%v, wantErr=%v", tc.limit, err, tc.wantErr)
		}
	}
}
