package fdk

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

	"github.com/fnproject/fdk-go/utils"
)

type Handler interface {
	Serve(ctx context.Context, in io.Reader, out io.Writer)
}

type HandlerFunc func(ctx context.Context, in io.Reader, out io.Writer)

func (f HandlerFunc) Serve(ctx context.Context, in io.Reader, out io.Writer) {
	f(ctx, in, out)
}

// Context will return an *fn.Ctx that can be used to read configuration and
// request information from an incoming request.
func Context(ctx context.Context) *Ctx {
	utilsCtx := utils.Context(ctx)
	return &Ctx{
		HTTPHeader: utilsCtx.HTTPHeader,
		Header:     utilsCtx.Header,
		Config:     utilsCtx.Config,
		RequestURL: utilsCtx.RequestURL,
		Method:     utilsCtx.Method,
	}
}

func WithContext(ctx context.Context, fnctx *Ctx) context.Context {
	utilsCtx := &utils.Ctx{
		HTTPHeader: fnctx.HTTPHeader,
		Header:     fnctx.Header,
		Config:     fnctx.Config,
		RequestURL: fnctx.RequestURL,
		Method:     fnctx.Method,
	}
	return utils.WithContext(ctx, utilsCtx)
}

// Ctx provides access to Config and Headers from fn.
type Ctx struct {
	// Header are the unmodified headers as sent to the container, see
	// HTTPHeader for specific trigger headers
	Header http.Header
	// HTTPHeader are the request headers as they appear on the original HTTP request,
	// for an http trigger.
	HTTPHeader http.Header
	Config     map[string]string
	RequestURL string
	Method     string
}

// AddHeader will add a header on the function response, for hot function
// formats.
func AddHeader(out io.Writer, key, value string) {
	if resp, ok := out.(*utils.Response); ok {
		resp.Header.Add(key, value)
	}
}

// SetHeader will set a header on the function response, for hot function
// formats.
func SetHeader(out io.Writer, key, value string) {
	if resp, ok := out.(*utils.Response); ok {
		resp.Header.Set(key, value)
	}
}

// WriteStatus will set the status code to return in the function response, for
// hot function formats.
func WriteStatus(out io.Writer, status int) {
	if resp, ok := out.(*utils.Response); ok {
		resp.Status = status
	}
}

// Handle will run the event loop for a function. Handle should be invoked
// through main() in a user's function and can handle communication between the
// function and fn server via any of the supported formats.
func Handle(handler Handler) {
	format, _ := os.LookupEnv("FN_FORMAT")

	path := os.Getenv("FN_LISTENER")
	if path != "" {
		utils.StartHTTPServer(handler, path, format)
		return
	}

	utils.Do(handler, format, os.Stdin, os.Stdout)
}
