package client

import (
	"encoding/json"
	"io"
	"mime"
	"net/http"
	"net/http/httptest"
	"net/url"
	"os"
	"path/filepath"
	"strings"
	"testing"
)

func TestAPIError(t *testing.T) {
	err := &APIError{StatusCode: 404, Code: "not_found", Message: "PR not found"}
	if err.Error() != "[not_found] PR not found" {
		t.Fatalf("Error() = %q, want %q", err.Error(), "[not_found] PR not found")
	}
}

func TestSuggestFix(t *testing.T) {
	tests := []struct {
		code int
		want string
	}{
		{401, "请先运行 gitlink-cli auth login 登录"},
		{403, "权限不足，请确认账户权限或联系项目管理员"},
		{404, "资源不存在，请检查 owner/repo/id 是否正确"},
		{422, "参数校验失败，请检查请求参数"},
		{500, ""},
		{0, ""},
	}
	for _, tt := range tests {
		t.Run(http.StatusText(tt.code), func(t *testing.T) {
			if got := suggestFix(tt.code); got != tt.want {
				t.Fatalf("suggestFix(%d) = %q, want %q", tt.code, got, tt.want)
			}
		})
	}
}

func TestClientDoSuccess(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte(`{"ok":true,"data":{"key":"value"}}`))
	}))
	defer server.Close()

	c := &Client{HTTP: server.Client(), BaseURL: server.URL}
	env, err := c.Do("GET", "/api/test", nil, nil)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !env.OK {
		t.Fatal("expected OK=true")
	}
}

func TestClientDoJSONSuffix(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/api/test.json" {
			t.Fatalf("expected path /api/test.json, got %s", r.URL.Path)
		}
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte(`{}`))
	}))
	defer server.Close()

	c := &Client{HTTP: server.Client(), BaseURL: server.URL}
	_, err := c.Do("GET", "/api/test", nil, nil)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
}

func TestClientDoJSONSuffixPreserved(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/api/test.json" {
			t.Fatalf("expected path /api/test.json, got %s", r.URL.Path)
		}
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte(`{}`))
	}))
	defer server.Close()

	c := &Client{HTTP: server.Client(), BaseURL: server.URL}
	_, err := c.Do("GET", "/api/test.json", nil, nil)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
}

func TestClientDoQueryParams(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Query().Get("state") != "open" {
			t.Fatalf("expected state=open, got %s", r.URL.Query().Get("state"))
		}
		if r.URL.Query().Get("page") != "1" {
			t.Fatalf("expected page=1, got %s", r.URL.Query().Get("page"))
		}
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte(`{}`))
	}))
	defer server.Close()

	c := &Client{HTTP: server.Client(), BaseURL: server.URL}
	q := url.Values{}
	q.Set("state", "open")
	q.Set("page", "1")
	_, err := c.Do("GET", "/api/test", nil, q)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
}

func TestClientDoHTTPError(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusNotFound)
		w.Write([]byte("not found"))
	}))
	defer server.Close()

	c := &Client{HTTP: server.Client(), BaseURL: server.URL}
	_, err := c.Do("GET", "/api/test", nil, nil)
	if err == nil {
		t.Fatal("expected error for 404")
	}
	apiErr, ok := err.(*APIError)
	if !ok {
		t.Fatalf("expected *APIError, got %T", err)
	}
	if apiErr.StatusCode != 404 {
		t.Fatalf("StatusCode = %d, want 404", apiErr.StatusCode)
	}
}

func TestClientDoNonJSON(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("plain text response"))
	}))
	defer server.Close()

	c := &Client{HTTP: server.Client(), BaseURL: server.URL}
	env, err := c.Do("GET", "/api/test", nil, nil)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !env.OK {
		t.Fatal("expected OK=true for non-JSON response")
	}
}

func TestClientDoStatusError(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte(`{"status":403,"message":"Forbidden"}`))
	}))
	defer server.Close()

	c := &Client{HTTP: server.Client(), BaseURL: server.URL}
	env, err := c.Do("GET", "/api/test", nil, nil)
	if err == nil {
		t.Fatal("expected error for status=403")
	}
	if env == nil {
		t.Fatal("expected envelope for status error")
	}
	if env.OK {
		t.Fatal("expected OK=false for status error")
	}
}

func TestClientDoGatewayCodeError(t *testing.T) {
	// Gateway returns {"code":N, "msg":"..."} instead of {"status":N, "message":"..."}
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte(`{"code":400,"msg":"Bad Request"}`))
	}))
	defer server.Close()

	c := &Client{HTTP: server.Client(), BaseURL: server.URL}
	env, err := c.Do("GET", "/api/test", nil, nil)
	if err == nil {
		t.Fatal("expected error for code=400")
	}
	if env == nil {
		t.Fatal("expected envelope for code error")
	}
	if env.OK {
		t.Fatal("expected OK=false for code=400")
	}
}

func TestClientDoGatewayCode201Success(t *testing.T) {
	// Gateway returns code=201 with JSON string data — should be treated as success
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte(`{"code":201,"msg":"","data":"{\"title\":\"test\"}"}`))
	}))
	defer server.Close()

	c := &Client{HTTP: server.Client(), BaseURL: server.URL}
	env, err := c.Do("POST", "/api/test", map[string]string{"title": "test"}, nil)
	if err != nil {
		t.Fatalf("unexpected error for code=201: %v", err)
	}
	if !env.OK {
		t.Fatal("expected OK=true for code=201")
	}
}

func TestClientDoStatusZero(t *testing.T) {
	// status=0, 200, 1 are treated as success
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte(`{"status":0,"data":"ok"}`))
	}))
	defer server.Close()

	c := &Client{HTTP: server.Client(), BaseURL: server.URL}
	env, err := c.Do("GET", "/api/test", nil, nil)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !env.OK {
		t.Fatal("expected OK=true for status=0")
	}
}

func TestClientDoPaginationMeta(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte(`{"total_count":100,"page":1,"limit":20,"data":[]}`))
	}))
	defer server.Close()

	c := &Client{HTTP: server.Client(), BaseURL: server.URL}
	env, err := c.Do("GET", "/api/test", nil, nil)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if env.Meta == nil {
		t.Fatal("expected Meta to be populated")
	}
	if env.Meta.TotalCount != 100 {
		t.Fatalf("TotalCount = %d, want 100", env.Meta.TotalCount)
	}
	if env.Meta.Page != 1 {
		t.Fatalf("Page = %d, want 1", env.Meta.Page)
	}
	if env.Meta.Limit != 20 {
		t.Fatalf("Limit = %d, want 20", env.Meta.Limit)
	}
}

func TestClientDoPathWithQuery(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// The path query param should be preserved
		if r.URL.Query().Get("filepath") != "test.go" {
			t.Fatalf("expected filepath=test.go, got %s", r.URL.Query().Get("filepath"))
		}
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte(`{}`))
	}))
	defer server.Close()

	c := &Client{HTTP: server.Client(), BaseURL: server.URL}
	_, err := c.Do("GET", "/api/sub_entries?filepath=test.go", nil, nil)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
}

func TestClientDoPathWithQueryAndExtraParams(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Query().Get("ref") != "master" {
			t.Fatalf("expected ref=master, got %s", r.URL.Query().Get("ref"))
		}
		if r.URL.Query().Get("filepath") != "test.go" {
			t.Fatalf("expected filepath=test.go, got %s", r.URL.Query().Get("filepath"))
		}
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte(`{}`))
	}))
	defer server.Close()

	c := &Client{HTTP: server.Client(), BaseURL: server.URL}
	q := url.Values{}
	q.Set("ref", "master")
	_, err := c.Do("GET", "/api/sub_entries?filepath=test.go", nil, q)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
}

func TestClientDoWithBody(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "POST" {
			t.Fatalf("expected POST, got %s", r.Method)
		}
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte(`{"id":123}`))
	}))
	defer server.Close()

	c := &Client{HTTP: server.Client(), BaseURL: server.URL}
	env, err := c.Do("POST", "/api/create", map[string]string{"title": "test"}, nil)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !env.OK {
		t.Fatal("expected OK=true")
	}
}

func TestClientPostMultipart(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodPost {
			t.Fatalf("expected POST, got %s", r.Method)
		}
		if r.URL.Path != "/api/attachments.json" {
			t.Fatalf("expected path /api/attachments.json, got %s", r.URL.Path)
		}

		mediaType, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
		if err != nil {
			t.Fatalf("parse media type: %v", err)
		}
		if mediaType != "multipart/form-data" {
			t.Fatalf("Content-Type = %q, want multipart/form-data", mediaType)
		}

		if err := r.ParseMultipartForm(1 << 20); err != nil {
			t.Fatalf("ParseMultipartForm: %v", err)
		}
		if got := r.FormValue("description"); got != "release asset" {
			t.Fatalf("description = %q, want %q", got, "release asset")
		}

		files := r.MultipartForm.File["file"]
		if len(files) != 1 {
			t.Fatalf("expected 1 uploaded file, got %d", len(files))
		}
		if files[0].Filename != "asset.zip" {
			t.Fatalf("filename = %q, want %q", files[0].Filename, "asset.zip")
		}
		if got := files[0].Header.Get("Content-Type"); got != "application/octet-stream" {
			t.Fatalf("part Content-Type = %q, want %q", got, "application/octet-stream")
		}

		file, err := files[0].Open()
		if err != nil {
			t.Fatalf("open multipart file: %v", err)
		}
		defer file.Close()

		data, err := io.ReadAll(file)
		if err != nil {
			t.Fatalf("read multipart file: %v", err)
		}
		if string(data) != "binary content" {
			t.Fatalf("file body = %q, want %q", string(data), "binary content")
		}

		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte(`{"id":"asset-1"}`))
	}))
	defer server.Close()

	c := &Client{HTTP: server.Client(), BaseURL: server.URL}
	env, err := c.PostMultipart("/api/attachments", map[string]string{
		"description": "release asset",
	}, []MultipartFile{
		{
			FieldName:   "file",
			FileName:    "asset.zip",
			ContentType: "application/octet-stream",
			Reader:      strings.NewReader("binary content"),
		},
	})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !env.OK {
		t.Fatal("expected OK=true")
	}
}

func TestClientPostMultipartRequiresFile(t *testing.T) {
	c := &Client{HTTP: &http.Client{}, BaseURL: "https://gitlink.example.com"}
	_, err := c.PostMultipart("/attachments", nil, nil)
	if err == nil {
		t.Fatal("expected error when no multipart file is provided")
	}
}

func TestClientGet(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "GET" {
			t.Fatalf("expected GET, got %s", r.Method)
		}
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte(`{}`))
	}))
	defer server.Close()

	c := &Client{HTTP: server.Client(), BaseURL: server.URL}
	_, err := c.Get("/api/test", nil)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
}

func TestClientPost(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "POST" {
			t.Fatalf("expected POST, got %s", r.Method)
		}
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte(`{}`))
	}))
	defer server.Close()

	c := &Client{HTTP: server.Client(), BaseURL: server.URL}
	_, err := c.Post("/api/test", nil)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
}

func TestClientPut(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "PUT" {
			t.Fatalf("expected PUT, got %s", r.Method)
		}
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte(`{}`))
	}))
	defer server.Close()

	c := &Client{HTTP: server.Client(), BaseURL: server.URL}
	_, err := c.Put("/api/test", nil)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
}

func TestClientDelete(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "DELETE" {
			t.Fatalf("expected DELETE, got %s", r.Method)
		}
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte(`{}`))
	}))
	defer server.Close()

	c := &Client{HTTP: server.Client(), BaseURL: server.URL}
	_, err := c.Delete("/api/test", nil)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
}

func TestClientDoInvalidURL(t *testing.T) {
	c := &Client{HTTP: &http.Client{}, BaseURL: "://invalid"}
	_, err := c.Do("GET", "/api/test", nil, nil)
	if err == nil {
		t.Fatal("expected error for invalid URL")
	}
}

func TestClientDebug(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte(`{}`))
	}))
	defer server.Close()

	c := &Client{HTTP: server.Client(), BaseURL: server.URL, Debug: true}
	_, err := c.Do("GET", "/api/test", nil, nil)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
}

func TestClientDoStatusInt(t *testing.T) {
	// Some APIs return status as int, not float64
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte(`{"status":404,"message":"Not Found"}`))
	}))
	defer server.Close()

	c := &Client{HTTP: server.Client(), BaseURL: server.URL}
	_, err := c.Do("GET", "/api/test", nil, nil)
	if err == nil {
		t.Fatal("expected error for status=404 (int)")
	}
}

func TestClientNew(t *testing.T) {
	dir := t.TempDir()
	t.Setenv("GITLINK_CONFIG_DIR", dir)
	cfgPath := filepath.Join(dir, "config.yaml")
	os.WriteFile(cfgPath, []byte("base_url: https://gitlink.example.com/api/v1\n"), 0644)

	cli, err := New()
	if err != nil {
		t.Fatalf("New error: %v", err)
	}
	if cli.BaseURL != "https://gitlink.example.com/api/v1" {
		t.Fatalf("BaseURL = %q", cli.BaseURL)
	}
	if cli.HTTP == nil {
		t.Fatal("HTTP client is nil")
	}
}

func TestPaginateAllSinglePage(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte(`{"data":[{"id":1},{"id":2}]}`))
	}))
	defer server.Close()

	c := &Client{HTTP: server.Client(), BaseURL: server.URL}
	items, err := c.PaginateAll("/test", nil)
	if err != nil {
		t.Fatalf("PaginateAll error: %v", err)
	}
	if len(items) != 2 {
		t.Fatalf("expected 2 items, got %d", len(items))
	}
}

func TestPaginateAllMultiPage(t *testing.T) {
	callCount := 0
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		callCount++
		w.Header().Set("Content-Type", "application/json")
		page := r.URL.Query().Get("page")
		if page == "1" {
			w.Write([]byte(`{"data":[{"id":1},{"id":2}]}`))
		} else {
			w.Write([]byte(`{"data":[{"id":3}]}`))
		}
	}))
	defer server.Close()

	c := &Client{HTTP: server.Client(), BaseURL: server.URL}
	params := url.Values{}
	params.Set("limit", "2")
	items, err := c.PaginateAll("/test", params)
	if err != nil {
		t.Fatalf("PaginateAll error: %v", err)
	}
	if len(items) != 3 {
		t.Fatalf("expected 3 items, got %d", len(items))
	}
	if callCount != 2 {
		t.Fatalf("expected 2 API calls, got %d", callCount)
	}
}

func TestPaginateAllWrappedData(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte(`{"data":[{"id":1},{"id":2}],"total_count":2}`))
	}))
	defer server.Close()

	c := &Client{HTTP: server.Client(), BaseURL: server.URL}
	items, err := c.PaginateAll("/test", nil)
	if err != nil {
		t.Fatalf("PaginateAll error: %v", err)
	}
	if len(items) != 2 {
		t.Fatalf("expected 2 items, got %d", len(items))
	}
}

func TestPaginateAllSingleObject(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte(`{"name":"single-object"}`))
	}))
	defer server.Close()

	c := &Client{HTTP: server.Client(), BaseURL: server.URL}
	items, err := c.PaginateAll("/test", nil)
	if err != nil {
		t.Fatalf("PaginateAll error: %v", err)
	}
	if len(items) != 1 {
		t.Fatalf("expected 1 item, got %d", len(items))
	}
	var data map[string]interface{}
	json.Unmarshal(items[0], &data)
	if data["name"] != "single-object" {
		t.Fatalf("unexpected data: %v", data)
	}
}

func TestPaginateAllHTTPError(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusInternalServerError)
	}))
	defer server.Close()

	c := &Client{HTTP: server.Client(), BaseURL: server.URL}
	_, err := c.PaginateAll("/test", nil)
	if err == nil {
		t.Fatal("expected error for 500 response")
	}
}

func TestPaginateAllNotOK(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte(`{"status":500,"message":"error"}`))
	}))
	defer server.Close()

	c := &Client{HTTP: server.Client(), BaseURL: server.URL}
	_, err := c.PaginateAll("/test", nil)
	if err == nil {
		t.Fatal("expected error when envelope ok=false")
	}
}

func TestClientDownload(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/api/attachments/7" {
			t.Fatalf("unexpected path: %s", r.URL.Path)
		}
		w.Header().Set("Content-Type", "application/octet-stream")
		w.Write([]byte("asset bytes"))
	}))
	defer server.Close()

	c := &Client{HTTP: server.Client(), BaseURL: server.URL + "/api"}
	result, err := c.Download("/api/attachments/7")
	if err != nil {
		t.Fatalf("Download error: %v", err)
	}
	if string(result.Data) != "asset bytes" {
		t.Fatalf("data = %q", result.Data)
	}
	if result.ContentType != "application/octet-stream" {
		t.Fatalf("content type = %q", result.ContentType)
	}
}

func TestClientDownloadAPIPathWithV1BaseURL(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/api/attachments/7" {
			t.Fatalf("unexpected path: %s", r.URL.Path)
		}
		w.Write([]byte("asset bytes"))
	}))
	defer server.Close()

	c := &Client{HTTP: server.Client(), BaseURL: server.URL + "/api/v1"}
	result, err := c.Download("/api/attachments/7")
	if err != nil {
		t.Fatalf("Download error: %v", err)
	}
	if string(result.Data) != "asset bytes" {
		t.Fatalf("data = %q", result.Data)
	}
}

func TestClientDownloadAbsoluteURL(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/files/release.zip" {
			t.Fatalf("unexpected path: %s", r.URL.Path)
		}
		w.Write([]byte("zip bytes"))
	}))
	defer server.Close()

	c := &Client{HTTP: server.Client(), BaseURL: "https://gitlink.example.com/api"}
	result, err := c.Download(server.URL + "/files/release.zip")
	if err != nil {
		t.Fatalf("Download error: %v", err)
	}
	if string(result.Data) != "zip bytes" {
		t.Fatalf("data = %q", result.Data)
	}
}

func TestClientDownloadWebRelativeURL(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/owner/repo/releases/download/v1/app.zip" {
			t.Fatalf("unexpected path: %s", r.URL.Path)
		}
		w.Write([]byte("asset bytes"))
	}))
	defer server.Close()

	c := &Client{HTTP: server.Client(), BaseURL: server.URL + "/api"}
	result, err := c.Download("/owner/repo/releases/download/v1/app.zip")
	if err != nil {
		t.Fatalf("Download error: %v", err)
	}
	if string(result.Data) != "asset bytes" {
		t.Fatalf("data = %q", result.Data)
	}
}

func TestClientDownloadHTTPError(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusNotFound)
		w.Write([]byte("missing"))
	}))
	defer server.Close()

	c := &Client{HTTP: server.Client(), BaseURL: server.URL}
	_, err := c.Download("/missing")
	if err == nil {
		t.Fatal("expected download error")
	}
}

func TestShouldAppendJSONSuffixSkipsRawFilePath(t *testing.T) {
	if shouldAppendJSONSuffix("/Gitlink/forgeplus/raw/master/README.md") {
		t.Fatal("raw file path should not get .json suffix")
	}
}

func TestShouldAppendJSONSuffixKeepsRawRepositoryName(t *testing.T) {
	if !shouldAppendJSONSuffix("/users/raw/projects") {
		t.Fatal("regular API path should get .json suffix")
	}
}

func TestShouldAppendJSONSuffixSkipsExistingJSONPath(t *testing.T) {
	if shouldAppendJSONSuffix("/projects.json") {
		t.Fatal("existing .json path should not get another suffix")
	}
}

func TestShouldAppendJSONSuffixSkipsWikiOpenPaths(t *testing.T) {
	paths := []string{
		"/wiki/open/createWiki",
		"/wiki/open/getWiki",
		"/wiki/open/updateWiki",
		"/wiki/open/deleteWiki",
		"/wiki/open/wikiPages",
	}
	for _, p := range paths {
		if shouldAppendJSONSuffix(p) {
			t.Errorf("wiki/open path %q should not get .json suffix", p)
		}
	}
}
