package pub

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/go-fed/activity/streams"
	"net/http"
)

// HandlerFunc determines whether an incoming HTTP request is an ActivityStreams
// GET request, and if so attempts to serve ActivityStreams data.
//
// If an error is returned, then the calling function is responsible for writing
// to the ResponseWriter as part of error handling.
//
// If 'isASRequest' is false and there is no error, then the calling function
// may continue processing the request, and the HandlerFunc will not have
// written anything to the ResponseWriter. For example, a webpage may be served
// instead.
//
// If 'isASRequest' is true and there is no error, then the HandlerFunc
// successfully served the request and wrote to the ResponseWriter.
//
// Callers are responsible for authorized access to this resource.
type HandlerFunc func(c context.Context, w http.ResponseWriter, r *http.Request) (isASRequest bool, err error)

// NewActivityStreamsHandler creates a HandlerFunc to serve ActivityStreams
// requests which are coming from other clients or servers that wish to obtain
// an ActivityStreams representation of data.
//
// Strips retrieved ActivityStreams values of sensitive fields ('bto' and 'bcc')
// before responding with them. Sets the appropriate HTTP status code for
// Tombstone Activities as well.
func NewActivityStreamsHandler(db Database, clock Clock) HandlerFunc {
	return func(c context.Context, w http.ResponseWriter, r *http.Request) (isASRequest bool, err error) {
		// Do nothing if it is not an ActivityPub GET request
		if !isActivityPubGet(r) {
			return
		}
		isASRequest = true
		id := requestId(r)
		// Lock and obtain a copy of the requested ActivityStreams value
		err = db.Lock(c, id)
		if err != nil {
			return
		}
		// WARNING: Unlock not deferred
		t, err := db.Get(c, id)
		if err != nil {
			db.Unlock(c, id)
			return
		}
		db.Unlock(c, id)
		// Unlock must have been called by this point and in every
		// branch above
		//
		// Remove sensitive fields.
		clearSensitiveFields(t)
		// Serialize the fetched value.
		m, err := streams.Serialize(t)
		if err != nil {
			return
		}
		raw, err := json.Marshal(m)
		if err != nil {
			return
		}
		// Construct the response.
		addResponseHeaders(w.Header(), clock, raw)
		// Write the response.
		if streams.IsOrExtendsActivityStreamsTombstone(t) {
			w.WriteHeader(http.StatusGone)
		} else {
			w.WriteHeader(http.StatusOK)
		}
		n, err := w.Write(raw)
		if err != nil {
			return
		} else if n != len(raw) {
			err = fmt.Errorf("only wrote %d of %d bytes", n, len(raw))
			return
		}
		return
	}
}
