package server

import (
	"context"
	"net/url"
	"os"

	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/credentials/stscreds"
	"github.com/aws/aws-sdk-go-v2/service/s3"
	"github.com/aws/aws-sdk-go-v2/service/sts"
	"github.com/pingcap/errors"
)

var _ LogHandler = &S3Handler{}

// S3Handler handle pushing log to s3.
type S3Handler struct {
	Config
	svc    *s3.Client
	bucket string
}

//nolint:gocritic //config will be reloaded
func NewS3Handler(config Config) (*S3Handler, error) {
	h := &S3Handler{
		Config: config,
	}

	err := h.buildS3Service(context.Background())
	if err != nil {
		return nil, errors.Annotate(err, "failed to build s3 service")
	}

	u, err := url.Parse(config.BucketURL)
	if err != nil {
		return nil, errors.Annotatef(err, "invalid url %q", config.BucketURL)
	}

	h.bucket = u.Host

	return h, nil
}

func (h *S3Handler) buildS3Service(ctx context.Context) error {
	// ref https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/credentials/stscreds#hdr-Assume_Role
	cfg, err := config.LoadDefaultConfig(
		ctx,
		config.WithRegion(h.Region),
	)
	if err != nil {
		return errors.Annotate(err, "failed to load default config")
	}

	// Only use AssumeRole when roleARN is explicitly configured.
	// Otherwise, use the default credential chain (supports IRSA, env vars, instance roles, etc.)
	if h.AWS != nil && h.AWS.RoleARN != "" {
		stsSvc := sts.NewFromConfig(cfg)
		creds := stscreds.NewAssumeRoleProvider(stsSvc, h.AWS.RoleARN, func(opts *stscreds.AssumeRoleOptions) {
			if h.AWS.ExternalID != "" {
				opts.ExternalID = aws.String(h.AWS.ExternalID)
			}
		})
		cfg.Credentials = aws.NewCredentialsCache(creds)
	}

	h.svc = s3.NewFromConfig(cfg)
	return nil
}

// Push implements LogHandler interface.
func (h *S3Handler) Push(ctx context.Context, fname, targetKey string) (err error) {
	file, err := os.Open(fname)
	if err != nil {
		return errors.Annotatef(err, "failed to open file %q", fname)
	}
	defer file.Close()

	_, err = h.svc.PutObject(ctx, &s3.PutObjectInput{
		Bucket: aws.String(h.bucket),
		Key:    aws.String(targetKey),
		Body:   file,
	})
	if err != nil {
		return err
	}

	return nil
}

// Close implements LogHandler interface.
func (h *S3Handler) Close() {
}
