package lambda

import (
	"context"
	"encoding/json"
	"github.com/aws/aws-lambda-go/events"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/aws/request"
	"github.com/aws/aws-sdk-go/service/lambda"
	"github.com/aws/aws-sdk-go/service/lambda/lambdaiface"
	"net/http"
)

const (
	ErrorBadRequest     = 400
	ErrorInternalServer = 500
)

// TestServer is a simple binding to locally call your lambda service in unit tests
type TestServer struct {
	lambdaiface.LambdaAPI
	Server   http.Handler
	Requests []*http.Request
}

// Invoke is a lambdaiface method for invoking a lambda, TestServer implements so you can
// make local unit tests
func (c *TestServer) Invoke(input *lambda.InvokeInput) (*lambda.InvokeOutput, error) {
	var gatewayRequest events.APIGatewayProxyRequest
	err := json.Unmarshal(input.Payload, &gatewayRequest)
	if err != nil {
		return nil, err
	}

	httpRequest, err := NewGatewayRequest(context.Background(), gatewayRequest)
	if err != nil {
		return nil, err
	}
	w := NewGatewayResponse()

	c.appendRequest(httpRequest)
	c.Server.ServeHTTP(w, httpRequest)

	gatewayResponse := w.End()

	gatewayResponseBytes, err := json.Marshal(gatewayResponse)
	if err != nil {
		return nil, err
	}

	return &lambda.InvokeOutput{
		StatusCode: aws.Int64(int64(gatewayResponse.StatusCode)),
		Payload:    gatewayResponseBytes,
	}, nil
}

// InvokeWithContext is a lambdaiface method for invoking a lambda, TestServer implements so you can
// make local unit tests
func (c *TestServer) InvokeWithContext(ctx aws.Context, input *lambda.InvokeInput, opts ...request.Option) (*lambda.InvokeOutput, error) {
	return c.Invoke(input)
}

func (c *TestServer) appendRequest(req *http.Request) {
	if c.Requests == nil {
		c.Requests = []*http.Request{req}
	} else {
		c.Requests = append(c.Requests, req)
	}
}

// LambdaInvokeErrorClient errors when it calls lambda, doesn't even try
type LambdaInvokeErrorClient struct {
	lambdaiface.LambdaAPI
}

type LambdaErrorJSON struct {
	Message    string   `json:"errorMessage"`
	Type       string   `json:"errorType"`
	StackTrace []string `json:"stackTrace"`
}

var invokeError awserr.Error
var functionError *LambdaErrorJSON

// Invoke is the lambdaiface method for invoking a lambda, TestServer implements so you can
// make local unit tests
func (c *LambdaInvokeErrorClient) Invoke(input *lambda.InvokeInput) (*lambda.InvokeOutput, error) {
	var out *lambda.InvokeOutput
	if functionError != nil {
		errorBody, err := json.Marshal(functionError)
		if err != nil {
			panic("uh-oh! failed to serialize test function error")
		}
		funcErr := "Unhandled"
		statusCode := int64(500)
		out = &lambda.InvokeOutput{
			FunctionError: &funcErr,
			StatusCode:    &statusCode,
			Payload:       errorBody,
		}
	}

	return out, invokeError
}

// InvokeWithContext is a lambdaiface method for invoking a lambda, TestServer implements so you can
// make local unit tests
func (c *LambdaInvokeErrorClient) InvokeWithContext(ctx aws.Context, input *lambda.InvokeInput, opts ...request.Option) (*lambda.InvokeOutput, error) {
	return c.Invoke(input)
}

func (c *LambdaInvokeErrorClient) SetError(aerr awserr.Error) {
	invokeError = aerr
}

func (c *LambdaInvokeErrorClient) SetFunctionError(funcErr *LambdaErrorJSON) {
	functionError = funcErr
}
