package server

import (
	"context"
	"fmt"
	"net/url"
	"os"
	"regexp"
	"strings"

	"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/container"
	"github.com/pingcap/errors"
)

var _ LogHandler = &AZBlobHandler{}

// AZBlobHandler handle pushing log to Azure Blob Storage.
type AZBlobHandler struct {
	Config
	// containerURL format: https://<account>.blob.core.windows.net/<container>?<sas token>
	containerURL string
	account      string
	container    string
	client       *container.Client
}

func NewAZBlobHandler(config Config) (*AZBlobHandler, error) {
	if config.Azure == nil {
		return nil, fmt.Errorf("no sas token provided for azure blob storage")
	}
	h := &AZBlobHandler{
		Config: config,
	}
	var err error
	h.account, h.container, err = parseAccountContainer(h.BucketURL)
	if err != nil {
		return nil, errors.Annotate(err, "can't get account and container from bucket url")
	}
	h.containerURL = fmt.Sprintf("https://%s.blob.core.windows.net/%s?%s", h.account, h.container, h.Azure.SASToken)

	client, err := container.NewClientWithNoCredential(h.containerURL, nil)
	if err != nil {
		return nil, errors.Annotate(err, "failed to create container client")
	}
	h.client = client

	return h, nil
}

// Parse storage account name and container name from bucketURL
// bucketURL format: https://{account}.blob.core.windows.net/{container}/path/to/file
func parseAccountContainer(bucketURL string) (account string, container string, err error) {
	u, err := url.Parse(bucketURL)
	if err != nil {
		return "", "", errors.Annotatef(err, "invalid url %q", bucketURL)
	}
	// account name can only contain lowercase letters and numbers
	re := regexp.MustCompile(`^([a-z0-9]+)\.blob\.core\.windows\.net`)
	match := re.FindStringSubmatch(u.Host)
	if len(match) <= 1 {
		return "", "", fmt.Errorf("invalid bucket url for azure blob storage, no storage account specified")
	}
	account = match[1]

	s := strings.SplitN(u.Path, "/", 3)
	if len(s) < 2 || s[1] == "" {
		return "", "", fmt.Errorf("invalid bucket url for azure blob storage, no container specified")
	}
	container = s[1]
	return account, container, nil
}

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

	// targetKey starts with container name, blob name should exclude it
	blobName := strings.TrimLeft(targetKey, h.container+"/")
	_, err = h.client.NewBlockBlobClient(blobName).UploadFile(ctx, file, nil)
	if err != nil {
		return errors.Annotate(err, "failed to upload file to blob storage")
	}
	return nil
}

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