// Copyright (c) Acrosync LLC. All rights reserved.
// Free for personal use and commercial trial
// Commercial use requires per-user licenses available from https://duplicacy.com

package duplicacy

import (
	"fmt"
	"strings"

	"github.com/gilbertchen/azure-sdk-for-go/storage"
)

type AzureStorage struct {
	StorageBase

	containers []*storage.Container
}

func CreateAzureStorage(accountName string, accountKey string,
	containerName string, threads int) (azureStorage *AzureStorage, err error) {

	var containers []*storage.Container
	for i := 0; i < threads; i++ {

		client, err := storage.NewBasicClient(accountName, accountKey)

		if err != nil {
			return nil, err
		}

		blobService := client.GetBlobService()
		container := blobService.GetContainerReference(containerName)
		containers = append(containers, container)
	}

	exist, err := containers[0].Exists()
	if err != nil {
		return nil, err
	}

	if !exist {
		return nil, fmt.Errorf("container %s does not exist", containerName)
	}

	azureStorage = &AzureStorage{
		containers: containers,
	}

	azureStorage.DerivedStorage = azureStorage
	azureStorage.SetDefaultNestingLevels([]int{0}, 0)
	return
}

// ListFiles return the list of files and subdirectories under 'dir' (non-recursively)
func (azureStorage *AzureStorage) ListFiles(threadIndex int, dir string) (files []string, sizes []int64, err error) {

	type ListBlobsParameters struct {
		Prefix     string
		Delimiter  string
		Marker     string
		Include    string
		MaxResults uint
		Timeout    uint
	}

	if len(dir) > 0 && dir[len(dir)-1] != '/' {
		dir += "/"
	}
	dirLength := len(dir)

	parameters := storage.ListBlobsParameters{
		Prefix:    dir,
		Delimiter: "",
	}

	subDirs := make(map[string]bool)

	for {

		results, err := azureStorage.containers[threadIndex].ListBlobs(parameters)
		if err != nil {
			return nil, nil, err
		}

		if dir == "snapshots/" {
			for _, blob := range results.Blobs {
				name := strings.Split(blob.Name[dirLength:], "/")[0]
				subDirs[name+"/"] = true
			}
		} else {
			for _, blob := range results.Blobs {
				files = append(files, blob.Name[dirLength:])
				sizes = append(sizes, blob.Properties.ContentLength)
			}
		}

		if results.NextMarker == "" {
			break
		}

		parameters.Marker = results.NextMarker
	}

	if dir == "snapshots/" {

		for subDir := range subDirs {
			files = append(files, subDir)
		}

	}

	return files, sizes, nil

}

// DeleteFile deletes the file or directory at 'filePath'.
func (storage *AzureStorage) DeleteFile(threadIndex int, filePath string) (err error) {
	_, err = storage.containers[threadIndex].GetBlobReference(filePath).DeleteIfExists(nil)
	return err
}

// MoveFile renames the file.
func (storage *AzureStorage) MoveFile(threadIndex int, from string, to string) (err error) {
	source := storage.containers[threadIndex].GetBlobReference(from)
	destination := storage.containers[threadIndex].GetBlobReference(to)
	err = destination.Copy(source.GetURL(), nil)
	if err != nil {
		return err
	}
	return storage.DeleteFile(threadIndex, from)
}

// CreateDirectory creates a new directory.
func (storage *AzureStorage) CreateDirectory(threadIndex int, dir string) (err error) {
	return nil
}

// GetFileInfo returns the information about the file or directory at 'filePath'.
func (storage *AzureStorage) GetFileInfo(threadIndex int, filePath string) (exist bool, isDir bool, size int64, err error) {
	blob := storage.containers[threadIndex].GetBlobReference(filePath)
	err = blob.GetProperties(nil)
	if err != nil {
		if strings.Contains(err.Error(), "404") {
			return false, false, 0, nil
		} else {
			return false, false, 0, err
		}
	}

	return true, false, blob.Properties.ContentLength, nil
}

// DownloadFile reads the file at 'filePath' into the chunk.
func (storage *AzureStorage) DownloadFile(threadIndex int, filePath string, chunk *Chunk) (err error) {
	readCloser, err := storage.containers[threadIndex].GetBlobReference(filePath).Get(nil)
	if err != nil {
		return err
	}

	defer readCloser.Close()

	_, err = RateLimitedCopy(chunk, readCloser, storage.DownloadRateLimit/len(storage.containers))
	return err
}

// UploadFile writes 'content' to the file at 'filePath'.
func (storage *AzureStorage) UploadFile(threadIndex int, filePath string, content []byte) (err error) {

	tries := 0

	for {
		reader := CreateRateLimitedReader(content, storage.UploadRateLimit/len(storage.containers))
		blob := storage.containers[threadIndex].GetBlobReference(filePath)
		err = blob.CreateBlockBlobFromReader(reader, nil)

		if err == nil || !strings.Contains(err.Error(), "write: broken pipe") || tries >= 3 {
			return err
		}

		LOG_INFO("AZURE_RETRY", "Connection unexpectedly terminated: %v; retrying", err)
		tries++
	}

}

// If a local snapshot cache is needed for the storage to avoid downloading/uploading chunks too often when
// managing snapshots.
func (storage *AzureStorage) IsCacheNeeded() bool { return true }

// If the 'MoveFile' method is implemented.
func (storage *AzureStorage) IsMoveFileImplemented() bool { return true }

// If the storage can guarantee strong consistency.
func (storage *AzureStorage) IsStrongConsistent() bool { return true }

// If the storage supports fast listing of files names.
func (storage *AzureStorage) IsFastListing() bool { return true }

// Enable the test mode.
func (storage *AzureStorage) EnableTestMode() {}
