package dataprovider

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"path/filepath"

	"github.com/drakkan/sftpgo/kms"
	"github.com/drakkan/sftpgo/logger"
	"github.com/drakkan/sftpgo/utils"
	"github.com/drakkan/sftpgo/vfs"
)

type compatUserV2 struct {
	ID                int64    `json:"id"`
	Username          string   `json:"username"`
	Password          string   `json:"password,omitempty"`
	PublicKeys        []string `json:"public_keys,omitempty"`
	HomeDir           string   `json:"home_dir"`
	UID               int      `json:"uid"`
	GID               int      `json:"gid"`
	MaxSessions       int      `json:"max_sessions"`
	QuotaSize         int64    `json:"quota_size"`
	QuotaFiles        int      `json:"quota_files"`
	Permissions       []string `json:"permissions"`
	UsedQuotaSize     int64    `json:"used_quota_size"`
	UsedQuotaFiles    int      `json:"used_quota_files"`
	LastQuotaUpdate   int64    `json:"last_quota_update"`
	UploadBandwidth   int64    `json:"upload_bandwidth"`
	DownloadBandwidth int64    `json:"download_bandwidth"`
	ExpirationDate    int64    `json:"expiration_date"`
	LastLogin         int64    `json:"last_login"`
	Status            int      `json:"status"`
}

type compatS3FsConfigV4 struct {
	Bucket            string `json:"bucket,omitempty"`
	KeyPrefix         string `json:"key_prefix,omitempty"`
	Region            string `json:"region,omitempty"`
	AccessKey         string `json:"access_key,omitempty"`
	AccessSecret      string `json:"access_secret,omitempty"`
	Endpoint          string `json:"endpoint,omitempty"`
	StorageClass      string `json:"storage_class,omitempty"`
	UploadPartSize    int64  `json:"upload_part_size,omitempty"`
	UploadConcurrency int    `json:"upload_concurrency,omitempty"`
}

type compatGCSFsConfigV4 struct {
	Bucket               string `json:"bucket,omitempty"`
	KeyPrefix            string `json:"key_prefix,omitempty"`
	CredentialFile       string `json:"-"`
	Credentials          []byte `json:"credentials,omitempty"`
	AutomaticCredentials int    `json:"automatic_credentials,omitempty"`
	StorageClass         string `json:"storage_class,omitempty"`
}

type compatAzBlobFsConfigV4 struct {
	Container         string `json:"container,omitempty"`
	AccountName       string `json:"account_name,omitempty"`
	AccountKey        string `json:"account_key,omitempty"`
	Endpoint          string `json:"endpoint,omitempty"`
	SASURL            string `json:"sas_url,omitempty"`
	KeyPrefix         string `json:"key_prefix,omitempty"`
	UploadPartSize    int64  `json:"upload_part_size,omitempty"`
	UploadConcurrency int    `json:"upload_concurrency,omitempty"`
	UseEmulator       bool   `json:"use_emulator,omitempty"`
	AccessTier        string `json:"access_tier,omitempty"`
}

type compatFilesystemV4 struct {
	Provider     FilesystemProvider     `json:"provider"`
	S3Config     compatS3FsConfigV4     `json:"s3config,omitempty"`
	GCSConfig    compatGCSFsConfigV4    `json:"gcsconfig,omitempty"`
	AzBlobConfig compatAzBlobFsConfigV4 `json:"azblobconfig,omitempty"`
}

type compatUserV4 struct {
	ID                int64               `json:"id"`
	Status            int                 `json:"status"`
	Username          string              `json:"username"`
	ExpirationDate    int64               `json:"expiration_date"`
	Password          string              `json:"password,omitempty"`
	PublicKeys        []string            `json:"public_keys,omitempty"`
	HomeDir           string              `json:"home_dir"`
	VirtualFolders    []vfs.VirtualFolder `json:"virtual_folders,omitempty"`
	UID               int                 `json:"uid"`
	GID               int                 `json:"gid"`
	MaxSessions       int                 `json:"max_sessions"`
	QuotaSize         int64               `json:"quota_size"`
	QuotaFiles        int                 `json:"quota_files"`
	Permissions       map[string][]string `json:"permissions"`
	UsedQuotaSize     int64               `json:"used_quota_size"`
	UsedQuotaFiles    int                 `json:"used_quota_files"`
	LastQuotaUpdate   int64               `json:"last_quota_update"`
	UploadBandwidth   int64               `json:"upload_bandwidth"`
	DownloadBandwidth int64               `json:"download_bandwidth"`
	LastLogin         int64               `json:"last_login"`
	Filters           UserFilters         `json:"filters"`
	FsConfig          compatFilesystemV4  `json:"filesystem"`
}

type backupDataV4Compat struct {
	Users   []compatUserV4          `json:"users"`
	Folders []vfs.BaseVirtualFolder `json:"folders"`
}

func createUserFromV4(u compatUserV4, fsConfig Filesystem) User {
	user := User{
		ID:                u.ID,
		Status:            u.Status,
		Username:          u.Username,
		ExpirationDate:    u.ExpirationDate,
		Password:          u.Password,
		PublicKeys:        u.PublicKeys,
		HomeDir:           u.HomeDir,
		VirtualFolders:    u.VirtualFolders,
		UID:               u.UID,
		GID:               u.GID,
		MaxSessions:       u.MaxSessions,
		QuotaSize:         u.QuotaSize,
		QuotaFiles:        u.QuotaFiles,
		Permissions:       u.Permissions,
		UsedQuotaSize:     u.UsedQuotaSize,
		UsedQuotaFiles:    u.UsedQuotaFiles,
		LastQuotaUpdate:   u.LastQuotaUpdate,
		UploadBandwidth:   u.UploadBandwidth,
		DownloadBandwidth: u.DownloadBandwidth,
		LastLogin:         u.LastLogin,
		Filters:           u.Filters,
	}
	user.FsConfig = fsConfig
	user.SetEmptySecretsIfNil()
	return user
}

func convertUserToV4(u User, fsConfig compatFilesystemV4) compatUserV4 {
	user := compatUserV4{
		ID:                u.ID,
		Status:            u.Status,
		Username:          u.Username,
		ExpirationDate:    u.ExpirationDate,
		Password:          u.Password,
		PublicKeys:        u.PublicKeys,
		HomeDir:           u.HomeDir,
		VirtualFolders:    u.VirtualFolders,
		UID:               u.UID,
		GID:               u.GID,
		MaxSessions:       u.MaxSessions,
		QuotaSize:         u.QuotaSize,
		QuotaFiles:        u.QuotaFiles,
		Permissions:       u.Permissions,
		UsedQuotaSize:     u.UsedQuotaSize,
		UsedQuotaFiles:    u.UsedQuotaFiles,
		LastQuotaUpdate:   u.LastQuotaUpdate,
		UploadBandwidth:   u.UploadBandwidth,
		DownloadBandwidth: u.DownloadBandwidth,
		LastLogin:         u.LastLogin,
		Filters:           u.Filters,
	}
	user.FsConfig = fsConfig
	return user
}

func getCGSCredentialsFromV4(config compatGCSFsConfigV4) (*kms.Secret, error) {
	secret := kms.NewEmptySecret()
	var err error
	if len(config.Credentials) > 0 {
		secret = kms.NewPlainSecret(string(config.Credentials))
		return secret, nil
	}
	if config.CredentialFile != "" {
		creds, err := ioutil.ReadFile(config.CredentialFile)
		if err != nil {
			return secret, err
		}
		secret = kms.NewPlainSecret(string(creds))
		return secret, nil
	}
	return secret, err
}

func getCGSCredentialsFromV6(config vfs.GCSFsConfig, username string) (string, error) {
	if config.Credentials == nil {
		config.Credentials = kms.NewEmptySecret()
	}
	if config.Credentials.IsEmpty() {
		config.CredentialFile = filepath.Join(credentialsDirPath, fmt.Sprintf("%v_gcs_credentials.json",
			username))
		creds, err := ioutil.ReadFile(config.CredentialFile)
		if err != nil {
			return "", err
		}
		err = json.Unmarshal(creds, &config.Credentials)
		if err != nil {
			return "", err
		}
	}
	if config.Credentials.IsEncrypted() {
		err := config.Credentials.Decrypt()
		if err != nil {
			return "", err
		}
		// in V4 GCS credentials were not encrypted
		return config.Credentials.GetPayload(), nil
	}
	return "", nil
}

func convertFsConfigToV4(fs Filesystem, username string) (compatFilesystemV4, error) {
	fsV4 := compatFilesystemV4{
		Provider:     fs.Provider,
		S3Config:     compatS3FsConfigV4{},
		AzBlobConfig: compatAzBlobFsConfigV4{},
		GCSConfig:    compatGCSFsConfigV4{},
	}
	switch fs.Provider {
	case S3FilesystemProvider:
		fsV4.S3Config = compatS3FsConfigV4{
			Bucket:            fs.S3Config.Bucket,
			KeyPrefix:         fs.S3Config.KeyPrefix,
			Region:            fs.S3Config.Region,
			AccessKey:         fs.S3Config.AccessKey,
			AccessSecret:      "",
			Endpoint:          fs.S3Config.Endpoint,
			StorageClass:      fs.S3Config.StorageClass,
			UploadPartSize:    fs.S3Config.UploadPartSize,
			UploadConcurrency: fs.S3Config.UploadConcurrency,
		}
		if fs.S3Config.AccessSecret.IsEncrypted() {
			err := fs.S3Config.AccessSecret.Decrypt()
			if err != nil {
				return fsV4, err
			}
			secretV4, err := utils.EncryptData(fs.S3Config.AccessSecret.GetPayload())
			if err != nil {
				return fsV4, err
			}
			fsV4.S3Config.AccessSecret = secretV4
		}
	case AzureBlobFilesystemProvider:
		fsV4.AzBlobConfig = compatAzBlobFsConfigV4{
			Container:         fs.AzBlobConfig.Container,
			AccountName:       fs.AzBlobConfig.AccountName,
			AccountKey:        "",
			Endpoint:          fs.AzBlobConfig.Endpoint,
			SASURL:            fs.AzBlobConfig.SASURL,
			KeyPrefix:         fs.AzBlobConfig.KeyPrefix,
			UploadPartSize:    fs.AzBlobConfig.UploadPartSize,
			UploadConcurrency: fs.AzBlobConfig.UploadConcurrency,
			UseEmulator:       fs.AzBlobConfig.UseEmulator,
			AccessTier:        fs.AzBlobConfig.AccessTier,
		}
		if fs.AzBlobConfig.AccountKey.IsEncrypted() {
			err := fs.AzBlobConfig.AccountKey.Decrypt()
			if err != nil {
				return fsV4, err
			}
			secretV4, err := utils.EncryptData(fs.AzBlobConfig.AccountKey.GetPayload())
			if err != nil {
				return fsV4, err
			}
			fsV4.AzBlobConfig.AccountKey = secretV4
		}
	case GCSFilesystemProvider:
		fsV4.GCSConfig = compatGCSFsConfigV4{
			Bucket:               fs.GCSConfig.Bucket,
			KeyPrefix:            fs.GCSConfig.KeyPrefix,
			CredentialFile:       fs.GCSConfig.CredentialFile,
			AutomaticCredentials: fs.GCSConfig.AutomaticCredentials,
			StorageClass:         fs.GCSConfig.StorageClass,
		}
		if fs.GCSConfig.AutomaticCredentials == 0 {
			creds, err := getCGSCredentialsFromV6(fs.GCSConfig, username)
			if err != nil {
				return fsV4, err
			}
			fsV4.GCSConfig.Credentials = []byte(creds)
		}
	default:
		// a provider not supported in v4, the configuration will be lost
		providerLog(logger.LevelWarn, "provider %v was not supported in v4, the configuration for the user %#v will be lost",
			fs.Provider, username)
		fsV4.Provider = 0
	}
	return fsV4, nil
}

func convertFsConfigFromV4(compatFs compatFilesystemV4, username string) (Filesystem, error) {
	fsConfig := Filesystem{
		Provider:     compatFs.Provider,
		S3Config:     vfs.S3FsConfig{},
		AzBlobConfig: vfs.AzBlobFsConfig{},
		GCSConfig:    vfs.GCSFsConfig{},
	}
	switch compatFs.Provider {
	case S3FilesystemProvider:
		fsConfig.S3Config = vfs.S3FsConfig{
			Bucket:            compatFs.S3Config.Bucket,
			KeyPrefix:         compatFs.S3Config.KeyPrefix,
			Region:            compatFs.S3Config.Region,
			AccessKey:         compatFs.S3Config.AccessKey,
			AccessSecret:      kms.NewEmptySecret(),
			Endpoint:          compatFs.S3Config.Endpoint,
			StorageClass:      compatFs.S3Config.StorageClass,
			UploadPartSize:    compatFs.S3Config.UploadPartSize,
			UploadConcurrency: compatFs.S3Config.UploadConcurrency,
		}
		if compatFs.S3Config.AccessSecret != "" {
			secret, err := kms.GetSecretFromCompatString(compatFs.S3Config.AccessSecret)
			if err != nil {
				providerLog(logger.LevelError, "unable to convert v4 filesystem for user %#v: %v", username, err)
				return fsConfig, err
			}
			fsConfig.S3Config.AccessSecret = secret
		}
	case AzureBlobFilesystemProvider:
		fsConfig.AzBlobConfig = vfs.AzBlobFsConfig{
			Container:         compatFs.AzBlobConfig.Container,
			AccountName:       compatFs.AzBlobConfig.AccountName,
			AccountKey:        kms.NewEmptySecret(),
			Endpoint:          compatFs.AzBlobConfig.Endpoint,
			SASURL:            compatFs.AzBlobConfig.SASURL,
			KeyPrefix:         compatFs.AzBlobConfig.KeyPrefix,
			UploadPartSize:    compatFs.AzBlobConfig.UploadPartSize,
			UploadConcurrency: compatFs.AzBlobConfig.UploadConcurrency,
			UseEmulator:       compatFs.AzBlobConfig.UseEmulator,
			AccessTier:        compatFs.AzBlobConfig.AccessTier,
		}
		if compatFs.AzBlobConfig.AccountKey != "" {
			secret, err := kms.GetSecretFromCompatString(compatFs.AzBlobConfig.AccountKey)
			if err != nil {
				providerLog(logger.LevelError, "unable to convert v4 filesystem for user %#v: %v", username, err)
				return fsConfig, err
			}
			fsConfig.AzBlobConfig.AccountKey = secret
		}
	case GCSFilesystemProvider:
		fsConfig.GCSConfig = vfs.GCSFsConfig{
			Bucket:               compatFs.GCSConfig.Bucket,
			KeyPrefix:            compatFs.GCSConfig.KeyPrefix,
			CredentialFile:       compatFs.GCSConfig.CredentialFile,
			AutomaticCredentials: compatFs.GCSConfig.AutomaticCredentials,
			StorageClass:         compatFs.GCSConfig.StorageClass,
		}
		if compatFs.GCSConfig.AutomaticCredentials == 0 {
			compatFs.GCSConfig.CredentialFile = filepath.Join(credentialsDirPath, fmt.Sprintf("%v_gcs_credentials.json",
				username))
		}
		secret, err := getCGSCredentialsFromV4(compatFs.GCSConfig)
		if err != nil {
			providerLog(logger.LevelError, "unable to convert v4 filesystem for user %#v: %v", username, err)
			return fsConfig, err
		}
		fsConfig.GCSConfig.Credentials = secret
	}
	return fsConfig, nil
}
