package httptoo

import (
	"context"
	"io"
	"net/http"
	"sync"

	"github.com/anacrolix/missinggo/v2"
)

type responseWriter struct {
	mu            sync.Mutex
	r             http.Response
	headerWritten missinggo.Event
	bodyWriter    io.WriteCloser
	bodyClosed    missinggo.SynchronizedEvent
}

var _ interface {
	http.ResponseWriter
	// We're able to emulate this easily enough.
	http.CloseNotifier
} = &responseWriter{}

// Use Request.Context.Done instead.
func (me *responseWriter) CloseNotify() <-chan bool {
	ret := make(chan bool, 1)
	go func() {
		<-me.bodyClosed.C()
		ret <- true
	}()
	return ret
}

func (me *responseWriter) Header() http.Header {
	if me.r.Header == nil {
		me.r.Header = make(http.Header)
	}
	return me.r.Header
}

func (me *responseWriter) Write(b []byte) (int, error) {
	me.mu.Lock()
	if !me.headerWritten.IsSet() {
		me.writeHeader(200)
	}
	me.mu.Unlock()
	return me.bodyWriter.Write(b)
}

func (me *responseWriter) WriteHeader(status int) {
	me.mu.Lock()
	me.writeHeader(status)
	me.mu.Unlock()
}

func (me *responseWriter) writeHeader(status int) {
	if me.headerWritten.IsSet() {
		return
	}
	me.r.StatusCode = status
	me.headerWritten.Set()
}

func (me *responseWriter) runHandler(h http.Handler, req *http.Request) {
	var pr *io.PipeReader
	pr, me.bodyWriter = io.Pipe()
	me.r.Body = struct {
		io.Reader
		io.Closer
	}{pr, eventCloser{pr, &me.bodyClosed}}
	// Shouldn't be writing to the response after the handler returns.
	defer me.bodyWriter.Close()
	// Send a 200 if nothing was written yet.
	defer me.WriteHeader(200)
	// Wrap the context in the given Request with one that closes when either
	// the handler returns, or the response body is closed.
	ctx, cancel := context.WithCancel(req.Context())
	defer cancel()
	go func() {
		<-me.bodyClosed.C()
		cancel()
	}()
	h.ServeHTTP(me, req.WithContext(ctx))
}

type eventCloser struct {
	c      io.Closer
	closed *missinggo.SynchronizedEvent
}

func (me eventCloser) Close() (err error) {
	err = me.c.Close()
	me.closed.Set()
	return
}

func RoundTripHandler(req *http.Request, h http.Handler) (*http.Response, error) {
	rw := responseWriter{}
	go rw.runHandler(h, req)
	<-rw.headerWritten.LockedChan(&rw.mu)
	return &rw.r, nil
}

type InProcRoundTripper struct {
	Handler http.Handler
}

func (me *InProcRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
	return RoundTripHandler(req, me.Handler)
}
