//go:build integration

package integrationtest

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"strings"
)

// Client performs authenticated HTTP requests against the test server.
type Client struct {
	baseURL string
	token   string
	http    *http.Client
}

func NewClient(baseURL, token string) *Client {
	return &Client{
		baseURL: strings.TrimSuffix(baseURL, "/"),
		token:   token,
		http:    &http.Client{},
	}
}

type Response struct {
	Status int
	Body   []byte
	Header http.Header
}

func (c *Client) Do(method, path string, body any) (*Response, error) {
	var reader io.Reader
	if body != nil {
		data, err := json.Marshal(body)
		if err != nil {
			return nil, err
		}
		reader = bytes.NewReader(data)
	}

	req, err := http.NewRequest(method, c.baseURL+path, reader)
	if err != nil {
		return nil, err
	}
	if body != nil {
		req.Header.Set("Content-Type", "application/json")
	}
	if c.token != "" {
		req.Header.Set("Authorization", "Bearer "+c.token)
	}

	resp, err := c.http.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	data, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	return &Response{Status: resp.StatusCode, Body: data, Header: resp.Header}, nil
}

func (c *Client) Get(path string) (*Response, error)    { return c.Do(http.MethodGet, path, nil) }
func (c *Client) Post(path string, body any) (*Response, error) {
	return c.Do(http.MethodPost, path, body)
}
func (c *Client) Put(path string, body any) (*Response, error) { return c.Do(http.MethodPut, path, body) }
func (c *Client) Patch(path string, body any) (*Response, error) {
	return c.Do(http.MethodPatch, path, body)
}
func (c *Client) Delete(path string) (*Response, error) { return c.Do(http.MethodDelete, path, nil) }

func (r *Response) JSON(v any) error {
	return json.Unmarshal(r.Body, v)
}

func (r *Response) MustStatus(want int) error {
	if r.Status != want {
		return fmt.Errorf("status = %d, want %d; body = %s", r.Status, want, string(r.Body))
	}
	return nil
}

// WithToken returns a copy using a different bearer token (e.g. API token).
func (c *Client) WithToken(token string) *Client {
	return &Client{baseURL: c.baseURL, token: token, http: c.http}
}
