package main

import (
	"fmt"
	"io"
	"net/http"
	"strings"
)

type trackingBody struct {
	io.ReadCloser
	closed bool
}

func (t *trackingBody) Close() error {
	t.closed = true
	return t.ReadCloser.Close()
}

type stubRoundTripper struct {
	status int
	body   *trackingBody
}

func (s stubRoundTripper) RoundTrip(_ *http.Request) (*http.Response, error) {
	return &http.Response{
		StatusCode: s.status,
		Body:       s.body,
		Header:     make(http.Header),
		Request:    &http.Request{},
	}, nil
}

func newClient(status int) (*http.Client, *trackingBody) {
	body := &trackingBody{ReadCloser: io.NopCloser(strings.NewReader("ok"))}
	return &http.Client{Transport: stubRoundTripper{status: status, body: body}}, body
}

// section: bad
func fetchBad(client *http.Client) error {
	resp, err := client.Get("https://api.example.com/users/1")
	if err != nil {
		return err
	}

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("unexpected status: %d", resp.StatusCode)
	}

	defer resp.Body.Close() // too late; early returns leak the body

	_, err = io.ReadAll(resp.Body)
	return err
}

// section: bad

// section: good
func fetchGood(client *http.Client) error {
	resp, err := client.Get("https://api.example.com/users/1")
	if err != nil {
		return fmt.Errorf("request failed: %w", err)
	}
	defer resp.Body.Close() // always defer immediately

	if resp.StatusCode != http.StatusOK {
		// drain the body so the transport can reuse the connection
		io.Copy(io.Discard, resp.Body)
		return fmt.Errorf("unexpected status: %d", resp.StatusCode)
	}

	_, err = io.ReadAll(resp.Body)
	if err != nil {
		return fmt.Errorf("read body: %w", err)
	}

	return nil
}

// section: good

// section: main
func main() {
	badClient, badBody := newClient(http.StatusServiceUnavailable)
	err := fetchBad(badClient)
	fmt.Printf("bad version error: %v\n", err)
	fmt.Printf("bad version closed body: %t\n", badBody.closed)

	goodClient, goodBody := newClient(http.StatusServiceUnavailable)
	err = fetchGood(goodClient)
	fmt.Printf("good version error: %v\n", err)
	fmt.Printf("good version closed body: %t\n", goodBody.closed)
}

// section: main

/*
// section: output
bad version error: unexpected status: 503
bad version closed body: false
good version error: unexpected status: 503
good version closed body: true
// section: output
*/
