//go:generate ${GOBIN}/mockgen -destination lambda_mock.go -package=lambda ${GO_MODULE}/pkg/clients/lambda Interface
package lambda

import (
	"bytes"
	"context"
	"encoding/json"
	"log/slog"
	"net/http"
	"time"

	"github.com/aws/aws-sdk-go-v2/aws"
	awsconfig "github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/service/lambda"
	"github.com/aws/aws-sdk-go-v2/service/lambda/types"
	"github.com/pkg/errors"
)

type Interface interface {
	InvokeAsync(ctx context.Context, event Event) error
}

type Client struct {
	logger       *slog.Logger
	functionName string
	lambdaClient *lambda.Client
}

type Event struct {
	Name    string `json:"name"`
	Payload []byte `json:"payload"`
}

func New(logger *slog.Logger, functionName string) (Interface, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()

	config, err := awsconfig.LoadDefaultConfig(ctx, awsconfig.WithRegion("us-west-1"))
	if err != nil {
		return nil, errors.Wrapf(err, "failed to load config")
	}

	lambdaClient := lambda.NewFromConfig(config)
	return &Client{
		logger:       logger,
		lambdaClient: lambdaClient,
		functionName: functionName,
	}, nil
}

func (c *Client) InvokeAsync(ctx context.Context, event Event) error {
	c.logger.Info("invoke async", "event", event.Name)
	eventJSON, err := json.Marshal(event)
	if err != nil {
		return errors.Wrapf(err, "failed to marshal event")
	}
	_, err = c.lambdaClient.Invoke(ctx, &lambda.InvokeInput{
		FunctionName:   aws.String(c.functionName),
		InvocationType: types.InvocationTypeEvent,
		Payload:        eventJSON,
	})
	if err != nil {
		return errors.Wrapf(err, "failed to invoke lambda(%s)", c.functionName)
	}
	return nil
}

type httpClient struct {
	logger *slog.Logger
	url    string
	client *http.Client
}

func NewHTTPClient(logger *slog.Logger, url string) Interface {
	return &httpClient{
		logger: logger,
		url:    url,
		client: &http.Client{
			Timeout: 5 * time.Second,
		},
	}
}

func (c *httpClient) InvokeAsync(ctx context.Context, event Event) error {
	c.logger.Info("invoke async via http", "event", event.Name)
	eventJSON, err := json.Marshal(event)
	if err != nil {
		return errors.Wrapf(err, "failed to marshal event")
	}

	req, err := http.NewRequestWithContext(ctx, "POST", c.url, bytes.NewBuffer(eventJSON))
	if err != nil {
		return errors.Wrapf(err, "failed to create request")
	}
	req.Header.Set("Content-Type", "application/json")

	resp, err := c.client.Do(req)
	if err != nil {
		return errors.Wrapf(err, "failed to invoke http endpoint(%s)", c.url)
	}
	defer resp.Body.Close()

	// accept 2xx as success
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		return errors.Errorf("http invoke failed with status: %d", resp.StatusCode)
	}

	return nil
}
