package pkg

import (
	"fmt"
	"io"
	"net/http"
	"path"
	"unique"
)

// An httpArtifact is an [Artifact] backed by a [http] url string. The method is
// hardcoded as [http.MethodGet]. Request body is not allowed because it cannot
// be deterministically represented by Params.
type httpArtifact struct {
	// Caller-supplied url string.
	url string

	// Caller-supplied checksum of the response body. This is validated when
	// closing the [io.ReadCloser] returned by Cure.
	checksum unique.Handle[Checksum]

	// client is the address of the caller-supplied [http.Client].
	client *http.Client
}

var _ KnownChecksum = new(httpArtifact)
var _ fmt.Stringer = new(httpArtifact)

// NewHTTPGet returns a new [FileArtifact] backed by the supplied client. A GET
// request is set up for url. If c is nil, [http.DefaultClient] is used instead.
func NewHTTPGet(
	c *http.Client,
	url string,
	checksum Checksum,
) FileArtifact {
	return &httpArtifact{url: url, checksum: unique.Make(checksum), client: c}
}

// Kind returns the hardcoded [Kind] constant.
func (*httpArtifact) Kind() Kind { return KindHTTPGet }

// Params writes the backing url string. Client is not represented as it does
// not affect [Cache.Cure] outcome.
func (a *httpArtifact) Params(ctx *IContext) { ctx.WriteString(a.url) }

func init() {
	register(KindHTTPGet, func(r *IRReader) Artifact {
		url := r.ReadString()
		checksum, ok := r.Finalise()
		if !ok {
			panic(ErrExpectedChecksum)
		}
		return NewHTTPGet(nil, url, checksum.Value())
	})
}

// Inputs returns a nil slice.
func (*httpArtifact) Inputs() []Artifact { return nil }

// IsExclusive returns false: Cure returns as soon as a response is received.
func (*httpArtifact) IsExclusive() bool { return false }

// Checksum returns the caller-supplied checksum.
func (a *httpArtifact) Checksum() Checksum { return a.checksum.Value() }

// String returns [path.Base] over the backing url.
func (a *httpArtifact) String() string { return path.Base(a.url) }

// ResponseStatusError is returned for a response returned by an [http.Client]
// with a status code other than [http.StatusOK].
type ResponseStatusError int

func (e ResponseStatusError) Error() string {
	return "the requested URL returned non-OK status: " + http.StatusText(int(e))
}

// Cure sends the http request and returns the resulting response body reader
// wrapped to perform checksum validation. It is valid but not encouraged to
// close the resulting [io.ReadCloser] before it is read to EOF, as that causes
// Close to block until all remaining data is consumed and validated.
func (a *httpArtifact) Cure(r *RContext) (rc io.ReadCloser, err error) {
	var req *http.Request
	req, err = http.NewRequestWithContext(r.Unwrap(), http.MethodGet, a.url, nil)
	if err != nil {
		return
	}
	req.Header.Set("User-Agent", "Hakurei/1.1")

	c := a.client
	if c == nil {
		c = http.DefaultClient
	}

	var resp *http.Response
	if resp, err = c.Do(req); err != nil {
		return
	}

	if resp.StatusCode != http.StatusOK {
		_ = resp.Body.Close()
		return nil, ResponseStatusError(resp.StatusCode)
	}

	rc = r.NewMeasuredReader(resp.Body, a.checksum)
	return
}
