//go:build test
// +build test

package httptest

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

type TestClient struct {
	client    *http.Client
	authToken string
	baseURL   string
}

func NewTestClient(handler http.Handler) *TestClient {
	server := httptest.NewServer(handler)
	return &TestClient{
		client:  server.Client(),
		baseURL: server.URL,
	}
}

func (c *TestClient) Server() *httptest.Server {
	return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		c.client.Transport.RoundTrip(r)
	}))
}

func (c *TestClient) SetAuthToken(token string) {
	c.authToken = token
}

func (c *TestClient) ClearAuthToken() {
	c.authToken = ""
}

func (c *TestClient) getAuthHeader() string {
	if c.authToken == "" {
		return ""
	}
	return "Bearer " + c.authToken
}

func (c *TestClient) Get(path string) (*http.Response, error) {
	req, err := http.NewRequest("GET", c.baseURL+path, nil)
	if err != nil {
		return nil, err
	}
	if auth := c.getAuthHeader(); auth != "" {
		req.Header.Set("Authorization", auth)
	}
	return c.client.Do(req)
}

func (c *TestClient) Post(path string, body interface{}) (*http.Response, error) {
	var bodyReader io.Reader
	if body != nil {
		jsonBytes, err := json.Marshal(body)
		if err != nil {
			return nil, err
		}
		bodyReader = bytes.NewReader(jsonBytes)
	}

	req, err := http.NewRequest("POST", c.baseURL+path, bodyReader)
	if err != nil {
		return nil, err
	}
	if auth := c.getAuthHeader(); auth != "" {
		req.Header.Set("Authorization", auth)
	}
	req.Header.Set("Content-Type", "application/json")
	return c.client.Do(req)
}

func (c *TestClient) Put(path string, body interface{}) (*http.Response, error) {
	var bodyReader io.Reader
	if body != nil {
		jsonBytes, err := json.Marshal(body)
		if err != nil {
			return nil, err
		}
		bodyReader = bytes.NewReader(jsonBytes)
	}

	req, err := http.NewRequest("PUT", c.baseURL+path, bodyReader)
	if err != nil {
		return nil, err
	}
	if auth := c.getAuthHeader(); auth != "" {
		req.Header.Set("Authorization", auth)
	}
	req.Header.Set("Content-Type", "application/json")
	return c.client.Do(req)
}

func (c *TestClient) Delete(path string) (*http.Response, error) {
	req, err := http.NewRequest("DELETE", c.baseURL+path, nil)
	if err != nil {
		return nil, err
	}
	if auth := c.getAuthHeader(); auth != "" {
		req.Header.Set("Authorization", auth)
	}
	return c.client.Do(req)
}

func (c *TestClient) Patch(path string, body interface{}) (*http.Response, error) {
	var bodyReader io.Reader
	if body != nil {
		jsonBytes, err := json.Marshal(body)
		if err != nil {
			return nil, err
		}
		bodyReader = bytes.NewReader(jsonBytes)
	}

	req, err := http.NewRequest("PATCH", c.baseURL+path, bodyReader)
	if err != nil {
		return nil, err
	}
	if auth := c.getAuthHeader(); auth != "" {
		req.Header.Set("Authorization", auth)
	}
	req.Header.Set("Content-Type", "application/json")
	return c.client.Do(req)
}

func (c *TestClient) ReadResponse(resp *http.Response, dest interface{}) error {
	defer resp.Body.Close()
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	return json.Unmarshal(body, dest)
}

func (c *TestClient) GetBody(resp *http.Response) (string, error) {
	defer resp.Body.Close()
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}
	return string(body), nil
}

func (c *TestClient) Close() {
	c.client.CloseIdleConnections()
}

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

func (c *TestClient) Do(req *http.Request) (*Response, error) {
	resp, err := c.client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	return &Response{
		StatusCode: resp.StatusCode,
		Body:       body,
		Header:     resp.Header,
	}, nil
}

func (c *TestClient) NewRequest(method, path string, body interface{}) (*http.Request, error) {
	var bodyReader io.Reader
	if body != nil {
		jsonBytes, err := json.Marshal(body)
		if err != nil {
			return nil, err
		}
		bodyReader = bytes.NewReader(jsonBytes)
	}

	req, err := http.NewRequest(method, c.baseURL+path, bodyReader)
	if err != nil {
		return nil, err
	}

	if auth := c.getAuthHeader(); auth != "" {
		req.Header.Set("Authorization", auth)
	}

	if body != nil {
		req.Header.Set("Content-Type", "application/json")
	}

	return req, nil
}

func MustParseJSON(body []byte, v interface{}) {
	if err := json.Unmarshal(body, v); err != nil {
		panic("failed to parse JSON: " + err.Error() + "\nBody: " + string(body))
	}
}

func JSONBody(v interface{}) io.Reader {
	jsonBytes, err := json.Marshal(v)
	if err != nil {
		panic("failed to marshal JSON: " + err.Error())
	}
	return bytes.NewReader(jsonBytes)
}

func SetDefaultTimeout(client *http.Client) {
	client.Timeout = 10 * time.Second
}

func Contains(s, substr string) bool {
	return strings.Contains(s, substr)
}
