//go:build windows
// +build windows

// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"). You may
// not use this file except in compliance with the License. A copy of the
// License is located at
//
//	http://aws.amazon.com/apache2.0/
//
// or in the "license" file accompanying this file. This file is distributed
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
// express or implied. See the License for the specific language governing
// permissions and limitations under the License.

package fsxwindowsfileserver

import (
	"encoding/json"
	"fmt"
	"os/exec"
	"strings"
	"sync"
	"time"

	apicontainer "github.com/aws/amazon-ecs-agent/agent/api/container"
	"github.com/aws/amazon-ecs-agent/agent/asm"
	asmfactory "github.com/aws/amazon-ecs-agent/agent/asm/factory"
	"github.com/aws/amazon-ecs-agent/agent/config"
	"github.com/aws/amazon-ecs-agent/agent/fsx"
	fsxfactory "github.com/aws/amazon-ecs-agent/agent/fsx/factory"
	"github.com/aws/amazon-ecs-agent/agent/ssm"
	ssmfactory "github.com/aws/amazon-ecs-agent/agent/ssm/factory"
	"github.com/aws/amazon-ecs-agent/agent/taskresource"
	resourcestatus "github.com/aws/amazon-ecs-agent/agent/taskresource/status"
	"github.com/aws/amazon-ecs-agent/agent/utils"
	apicontainerstatus "github.com/aws/amazon-ecs-agent/ecs-agent/api/container/status"
	"github.com/aws/amazon-ecs-agent/ecs-agent/api/task/status"
	"github.com/aws/amazon-ecs-agent/ecs-agent/credentials"
	"github.com/aws/amazon-ecs-agent/ecs-agent/ipcompatibility"

	"github.com/aws/aws-sdk-go-v2/aws/arn"
	"github.com/cihub/seelog"
	"github.com/pkg/errors"
)

const (
	psCredentialCommandFormat = "$(New-Object System.Management.Automation.PSCredential('%s', $(ConvertTo-SecureString '%s' -AsPlainText -Force)))"
	resourceProvisioningError = "VolumeError: Agent could not create task's volume resources"
	fsxVolumeType             = "fsx"
)

// FSxWindowsFileServerResource represents a fsxwindowsfileserver resource
type FSxWindowsFileServerResource struct {
	Name                   string
	VolumeType             string
	VolumeConfig           FSxWindowsFileServerVolumeConfig
	taskARN                string
	region                 string
	executionCredentialsID string
	credentialsManager     credentials.Manager

	// ssmClientCreator is a factory interface that creates new SSM clients.
	ssmClientCreator ssmfactory.SSMClientCreator
	// asmClientCreator is a factory interface that creates new ASM clients.
	asmClientCreator asmfactory.ClientCreator
	// fsxClientCreator is a factory interface that creates new FSx clients.
	fsxClientCreator fsxfactory.FSxClientCreator
	ipCompatibility  ipcompatibility.IPCompatibility

	// fields that are set later during resource creation
	FSxWindowsFileServerDNSName string
	Credentials                 FSxWindowsFileServerCredentials

	// Fields for the common functionality of task resource. Access to these fields are protected by lock.
	createdAtUnsafe     time.Time
	knownStatusUnsafe   resourcestatus.ResourceStatus
	desiredStatusUnsafe resourcestatus.ResourceStatus
	appliedStatusUnsafe resourcestatus.ResourceStatus
	statusToTransitions map[resourcestatus.ResourceStatus]func() error
	terminalReason      string
	terminalReasonOnce  sync.Once
	lock                sync.RWMutex
}

// FSxWindowsFileServerVolumeConfig represents fsxWindowsFileServer volume configuration.
type FSxWindowsFileServerVolumeConfig struct {
	FileSystemID  string                         `json:"fileSystemId,omitempty"`
	RootDirectory string                         `json:"rootDirectory,omitempty"`
	AuthConfig    FSxWindowsFileServerAuthConfig `json:"authorizationConfig,omitempty"`
	// HostPath is used for bind mount as part of HostConfig.
	HostPath string `json:"fsxWindowsFileServerHostPath"`
}

// FSxWindowsFileServerAuthConfig contains auth config for a fsxWindowsFileServer volume.
type FSxWindowsFileServerAuthConfig struct {
	CredentialsParameter string `json:"credentialsParameter,omitempty"`
	Domain               string `json:"domain,omitempty"`
}

// FSxWindowsFileServerCredentials represents user credentials for accessing the fsxWindowsFileServer volume
type FSxWindowsFileServerCredentials struct {
	Username string `json:"username"`
	Password string `json:"password"`
}

// NewFSxWindowsFileServerResource creates a new FSxWindowsFileServerResource object
func NewFSxWindowsFileServerResource(
	taskARN,
	region string,
	name string,
	volumeType string,
	volumeConfig *FSxWindowsFileServerVolumeConfig,
	executionCredentialsID string,
	credentialsManager credentials.Manager,
	ssmClientCreator ssmfactory.SSMClientCreator,
	asmClientCreator asmfactory.ClientCreator,
	fsxClientCreator fsxfactory.FSxClientCreator,
	ipCompatibility ipcompatibility.IPCompatibility) (*FSxWindowsFileServerResource, error) {

	fv := &FSxWindowsFileServerResource{
		Name:       name,
		VolumeType: volumeType,
		VolumeConfig: FSxWindowsFileServerVolumeConfig{
			FileSystemID:  volumeConfig.FileSystemID,
			RootDirectory: volumeConfig.RootDirectory,
			AuthConfig:    volumeConfig.AuthConfig,
		},
		taskARN:                taskARN,
		region:                 region,
		executionCredentialsID: executionCredentialsID,
		credentialsManager:     credentialsManager,
		ssmClientCreator:       ssmClientCreator,
		asmClientCreator:       asmClientCreator,
		fsxClientCreator:       fsxClientCreator,
		ipCompatibility:        ipCompatibility,
	}

	fv.initStatusToTransition()
	return fv, nil
}

func (fv *FSxWindowsFileServerResource) Initialize(
	config *config.Config,
	resourceFields *taskresource.ResourceFields,
	taskKnownStatus status.TaskStatus,
	taskDesiredStatus status.TaskStatus) {

	fv.credentialsManager = resourceFields.CredentialsManager
	fv.ssmClientCreator = resourceFields.SSMClientCreator
	fv.asmClientCreator = resourceFields.ASMClientCreator
	fv.fsxClientCreator = resourceFields.FSxClientCreator
	fv.ipCompatibility = config.InstanceIPCompatibility
	fv.initStatusToTransition()
}

func (fv *FSxWindowsFileServerResource) initStatusToTransition() {
	statusToTransitions := map[resourcestatus.ResourceStatus]func() error{
		resourcestatus.ResourceStatus(FSxWindowsFileServerVolumeCreated): fv.Create,
	}

	fv.statusToTransitions = statusToTransitions
}

// DesiredTerminal returns true if the fsxwindowsfileserver's desired status is REMOVED
func (fv *FSxWindowsFileServerResource) DesiredTerminal() bool {
	fv.lock.RLock()
	defer fv.lock.RUnlock()

	return fv.desiredStatusUnsafe == resourcestatus.ResourceStatus(FSxWindowsFileServerVolumeRemoved)
}

// GetTerminalReason returns an error string to propagate up through to task
// state change messages
func (fv *FSxWindowsFileServerResource) GetTerminalReason() string {
	if fv.terminalReason == "" {
		return resourceProvisioningError
	}
	return fv.terminalReason
}

func (fv *FSxWindowsFileServerResource) setTerminalReason(reason string) {
	fv.terminalReasonOnce.Do(func() {
		seelog.Debugf("fsxwindowsfileserver resource [%s]: setting terminal reason for fsxwindowsfileserver resource in task: [%s]", fv.Name, fv.taskARN)
		fv.terminalReason = reason
	})
}

// GetDesiredStatus safely returns the desired status of the task
func (fv *FSxWindowsFileServerResource) GetDesiredStatus() resourcestatus.ResourceStatus {
	fv.lock.RLock()
	defer fv.lock.RUnlock()

	return fv.desiredStatusUnsafe
}

// SetDesiredStatus safely sets the desired status of the resource
func (fv *FSxWindowsFileServerResource) SetDesiredStatus(status resourcestatus.ResourceStatus) {
	fv.lock.Lock()
	defer fv.lock.Unlock()

	fv.desiredStatusUnsafe = status
}

// GetKnownStatus safely returns the currently known status of the task
func (fv *FSxWindowsFileServerResource) GetKnownStatus() resourcestatus.ResourceStatus {
	fv.lock.RLock()
	defer fv.lock.RUnlock()

	return fv.knownStatusUnsafe
}

// SetKnownStatus safely sets the currently known status of the resource
func (fv *FSxWindowsFileServerResource) SetKnownStatus(status resourcestatus.ResourceStatus) {
	fv.lock.Lock()
	defer fv.lock.Unlock()

	fv.knownStatusUnsafe = status
	fv.updateAppliedStatusUnsafe(status)
}

// KnownCreated returns true if the fsxwindowsfileserver's known status is CREATED
func (fv *FSxWindowsFileServerResource) KnownCreated() bool {
	fv.lock.RLock()
	defer fv.lock.RUnlock()

	return fv.knownStatusUnsafe == resourcestatus.ResourceStatus(FSxWindowsFileServerVolumeCreated)
}

// TerminalStatus returns the last transition state of fsxwindowsfileserver
func (fv *FSxWindowsFileServerResource) TerminalStatus() resourcestatus.ResourceStatus {
	return resourcestatus.ResourceStatus(FSxWindowsFileServerVolumeRemoved)
}

// NextKnownState returns the state that the resource should
// progress to based on its `KnownState`.
func (fv *FSxWindowsFileServerResource) NextKnownState() resourcestatus.ResourceStatus {
	return fv.GetKnownStatus() + 1
}

// SteadyState returns the transition state of the resource defined as "ready"
func (fv *FSxWindowsFileServerResource) SteadyState() resourcestatus.ResourceStatus {
	return resourcestatus.ResourceStatus(FSxWindowsFileServerVolumeCreated)
}

// ApplyTransition calls the function required to move to the specified status
func (fv *FSxWindowsFileServerResource) ApplyTransition(nextState resourcestatus.ResourceStatus) error {
	transitionFunc, ok := fv.statusToTransitions[nextState]
	if !ok {
		err := errors.Errorf("resource [%s]: transition to %s impossible", fv.Name,
			fv.StatusString(nextState))
		fv.setTerminalReason(err.Error())
		return err
	}

	return transitionFunc()
}

// SetAppliedStatus sets the applied status of resource and returns whether
// the resource is already in a transition
func (fv *FSxWindowsFileServerResource) SetAppliedStatus(status resourcestatus.ResourceStatus) bool {
	fv.lock.Lock()
	defer fv.lock.Unlock()

	if fv.appliedStatusUnsafe != resourcestatus.ResourceStatus(FSxWindowsFileServerVolumeStatusNone) {
		// return false to indicate the set operation failed
		return false
	}

	fv.appliedStatusUnsafe = status
	return true
}

// StatusString returns the string of the fsxwindowsfileserver resource status
func (fv *FSxWindowsFileServerResource) StatusString(status resourcestatus.ResourceStatus) string {
	return FSxWindowsFileServerVolumeStatus(status).String()
}

// GetCreatedAt gets the timestamp for resource's creation time
func (fv *FSxWindowsFileServerResource) GetCreatedAt() time.Time {
	fv.lock.RLock()
	defer fv.lock.RUnlock()

	return fv.createdAtUnsafe
}

// SetCreatedAt sets the timestamp for resource's creation time
func (fv *FSxWindowsFileServerResource) SetCreatedAt(createdAt time.Time) {
	if createdAt.IsZero() {
		return
	}
	fv.lock.Lock()
	defer fv.lock.Unlock()

	fv.createdAtUnsafe = createdAt
}

// Source returns the host path of the fsxwindowsfileserver resource which is used as the source of the volume mount
func (cfg *FSxWindowsFileServerVolumeConfig) Source() string {
	return utils.GetCanonicalPath(cfg.HostPath)
}

func (cfg *FSxWindowsFileServerVolumeConfig) GetType() string {
	return fsxVolumeType
}

func (cfg *FSxWindowsFileServerVolumeConfig) GetVolumeId() string {
	return cfg.FileSystemID
}

// Note: The name is within the FSxWindowsFileServerResource struct. In order to use this in the future, this needs to be modified.
// Currently not meant for use
func (cfg *FSxWindowsFileServerVolumeConfig) GetVolumeName() string {
	return ""
}

// GetName safely returns the name of the fsxwindowsfileserver resource
func (fv *FSxWindowsFileServerResource) GetName() string {
	fv.lock.RLock()
	defer fv.lock.RUnlock()

	return fv.Name
}

// GetVolumeConfig safely returns the volume config of the fsxwindowsfileserver resource
func (fv *FSxWindowsFileServerResource) GetVolumeConfig() FSxWindowsFileServerVolumeConfig {
	fv.lock.RLock()
	defer fv.lock.RUnlock()

	return fv.VolumeConfig
}

// GetCredentials safely returns the user credentials of the fsxwindowsfileserver resource
func (fv *FSxWindowsFileServerResource) GetCredentials() FSxWindowsFileServerCredentials {
	fv.lock.RLock()
	defer fv.lock.RUnlock()

	return fv.Credentials
}

// GetFileSystemDNSName safely returns the filesystem dns name of the fsxwindowsfileserver resource
func (fv *FSxWindowsFileServerResource) GetFileSystemDNSName() string {
	fv.lock.RLock()
	defer fv.lock.RUnlock()

	return fv.FSxWindowsFileServerDNSName
}

// getExecutionCredentialsID safely returns the execution role's credential ID
func (fv *FSxWindowsFileServerResource) GetExecutionCredentialsID() string {
	fv.lock.RLock()
	defer fv.lock.RUnlock()

	return fv.executionCredentialsID
}

// SetCredentials safely updates the user credentials of the fsxwindowsfileserver resource
func (fv *FSxWindowsFileServerResource) SetCredentials(credentials FSxWindowsFileServerCredentials) {
	fv.lock.Lock()
	defer fv.lock.Unlock()

	fv.Credentials = credentials
}

// SetFileSystemDNSName safely updates the filesystem dns name of the fsxwindowsfileserver resource
func (fv *FSxWindowsFileServerResource) SetFileSystemDNSName(DNSName string) {
	fv.lock.Lock()
	defer fv.lock.Unlock()

	fv.FSxWindowsFileServerDNSName = DNSName
}

// SetFileSystemDNSName safely updates volume config's root directory of the fsxwindowsfileserver resource
func (fv *FSxWindowsFileServerResource) SetRootDirectory(rootDirectory string) {
	fv.lock.Lock()
	defer fv.lock.Unlock()

	fv.VolumeConfig.RootDirectory = rootDirectory
}

// GetHostPath safely returns the volume config's host path
func (fv *FSxWindowsFileServerResource) GetHostPath() string {
	fv.lock.RLock()
	defer fv.lock.RUnlock()

	return fv.VolumeConfig.HostPath
}

// SetHostPath safely updates volume config's host path
func (fv *FSxWindowsFileServerResource) SetHostPath(hostPath string) {
	fv.lock.Lock()
	defer fv.lock.Unlock()

	fv.VolumeConfig.HostPath = hostPath
}

// Create is used to create all the fsxwindowsfileserver resources for a given task
func (fv *FSxWindowsFileServerResource) Create() error {
	var err error
	volumeConfig := fv.GetVolumeConfig()

	var iamCredentials credentials.IAMRoleCredentials
	executionCredentials, ok := fv.credentialsManager.GetTaskCredentials(fv.GetExecutionCredentialsID())
	if !ok {
		err = errors.New("fsxwindowsfileserver resource: unable to find execution role credentials")
		fv.setTerminalReason(err.Error())
		return err
	}
	iamCredentials = executionCredentials.GetIAMRoleCredentials()

	err = fv.retrieveCredentials(volumeConfig.AuthConfig.CredentialsParameter, iamCredentials)
	if err != nil {
		fv.setTerminalReason(err.Error())
		return err
	}

	err = fv.retrieveFileSystemDNSName(volumeConfig.FileSystemID, iamCredentials)
	if err != nil {
		fv.setTerminalReason(err.Error())
		return err
	}

	fv.handleRootDirectory(volumeConfig.RootDirectory)

	creds := fv.GetCredentials()
	if creds.Username == "" || creds.Password == "" {
		return errors.New("invalid credentials for mounting fsxwindowsfileserver volume on the container instance")
	}
	// formatting to keep powershell happy
	username := volumeConfig.AuthConfig.Domain + `\` + creds.Username
	remotePath := fmt.Sprintf(`\\%s`, fv.GetFileSystemDNSName())
	if volumeConfig.RootDirectory != "" {
		remotePath = fmt.Sprintf(`%s\%s`, remotePath, volumeConfig.RootDirectory)
	} else {
		remotePath = fmt.Sprintf(`%s\share`, remotePath)
	}

	password := creds.Password

	err = fv.performHostMount(remotePath, username, password)
	if err != nil {
		fv.setTerminalReason(err.Error())
		return err
	}

	return nil
}

func (fv *FSxWindowsFileServerResource) retrieveCredentials(credentialsParameterARN string, iamCredentials credentials.IAMRoleCredentials) error {
	parsedARN, err := arn.Parse(credentialsParameterARN)
	if err != nil {
		fv.setTerminalReason(err.Error())
		return err
	}

	parsedARNService := parsedARN.Service
	switch parsedARNService {
	case "ssm":
		err = fv.retrieveSSMCredentials(credentialsParameterARN, iamCredentials)
		if err != nil {
			seelog.Errorf("Failed to retrieve credentials from ssm: %v", err)
			fv.setTerminalReason(err.Error())
			return err
		}
	case "secretsmanager":
		err = fv.retrieveASMCredentials(credentialsParameterARN, iamCredentials)
		if err != nil {
			seelog.Errorf("Failed to retrieve credentials from asm: %v", err)
			fv.setTerminalReason(err.Error())
			return err
		}
	default:
		err = errors.New("unsupported credentialsParameter, only ssm/secretsmanager ARNs are valid")
		fv.setTerminalReason(err.Error())
		return err
	}

	return nil
}

func (fv *FSxWindowsFileServerResource) retrieveSSMCredentials(credentialsParameterARN string, iamCredentials credentials.IAMRoleCredentials) error {
	parsedARN, err := arn.Parse(credentialsParameterARN)
	if err != nil {
		return err
	}

	ssmClient, err := fv.ssmClientCreator.NewSSMClient(fv.region, iamCredentials, fv.ipCompatibility)
	if err != nil {
		return err
	}
	// parsedARN.Resource looks like "arn:aws:ssm:us-west-2:123456789012:parameter/sample1/sample2/parameter1"
	// We cut by parameter and get "arn:aws:ssm:us-west-2:123456789012:parameter", "/sample1/sample2/parameter1", True/False
	_, ssmParamName, found := strings.Cut(parsedARN.Resource, "parameter")
	if !found {
		err = errors.New("unxpected error. expected fsx credential ssm arn but did not find string 'parameter' in the arn")
		fv.setTerminalReason(err.Error())
		return err

	}

	ssmParams := []string{ssmParamName}

	ssmParamMap, err := ssm.GetParametersFromSSM(ssmParams, ssmClient)
	if err != nil {
		return err
	}

	ssmParamData, _ := ssmParamMap[ssmParamName]
	creds := FSxWindowsFileServerCredentials{}

	if err := json.Unmarshal([]byte(ssmParamData), &creds); err != nil {
		return err
	}

	fv.SetCredentials(creds)
	return nil
}

func (fv *FSxWindowsFileServerResource) retrieveASMCredentials(credentialsParameterARN string, iamCredentials credentials.IAMRoleCredentials) error {
	_, err := arn.Parse(credentialsParameterARN)
	if err != nil {
		return err
	}

	asmClient, err := fv.asmClientCreator.NewASMClient(fv.region, iamCredentials)
	if err != nil {
		return err
	}
	asmData, err := asm.GetSecretFromASM(credentialsParameterARN, asmClient)
	if err != nil {
		return err
	}

	creds := FSxWindowsFileServerCredentials{}
	if err := json.Unmarshal([]byte(asmData), &creds); err != nil {
		return err
	}

	fv.SetCredentials(creds)
	return nil
}

func (fv *FSxWindowsFileServerResource) retrieveFileSystemDNSName(fileSystemId string, iamCredentials credentials.IAMRoleCredentials) error {
	fileSystemIds := []string{fileSystemId}
	fsxClient, err := fv.fsxClientCreator.NewFSxClient(fv.region, iamCredentials)
	if err != nil {
		fv.setTerminalReason(err.Error())
		return err
	}
	fileSystemDNSMap, err := fsx.GetFileSystemDNSNames(fileSystemIds, fsxClient)
	if err != nil {
		fv.setTerminalReason(err.Error())
		return err
	}

	fv.SetFileSystemDNSName(fileSystemDNSMap[fileSystemId])
	return nil
}

var mountLock sync.Mutex
var DriveLetterAvailable = utils.IsAvailableDriveLetter
var execCommand = exec.Command

func (fv *FSxWindowsFileServerResource) performHostMount(remotePath string, username string, password string) error {
	// mountLock is a package-level mutex lock.
	// It is used to avoid a scenario where concurrent tasks get the same drive letter and perform host mount at the same time.
	// A drive letter is free until host mount is performed, a lock prevents multiple tasks getting the same drive letter.
	mountLock.Lock()
	defer mountLock.Unlock()

	hostPath, err := utils.FindUnusedDriveLetter()
	if err != nil {
		return err
	}
	fv.SetHostPath(hostPath)

	// formatting to keep powershell happy
	localPathArg := fmt.Sprintf("-LocalPath \"%s\"", strings.Trim(hostPath, `\`))

	// remove mount if localPath is not available
	// handling case where agent crashes after mount and before resource status is updated
	if !(DriveLetterAvailable(hostPath)) {
		err = fv.removeHostMount(localPathArg)
		if err != nil {
			seelog.Errorf("Failed to remove existing fsxwindowsfileserver resource mount on the container instance: %v", err)
			fv.setTerminalReason(err.Error())
			return err
		}
	}

	// formatting to keep powershell happy
	// Replace ' with '' so that Powershell would convert it back to '.
	password = strings.ReplaceAll(password, "'", "''")
	credsCommand := fmt.Sprintf(psCredentialCommandFormat, username, password)
	credsArg := fmt.Sprintf("-Credential %s", credsCommand)

	remotePathArg := fmt.Sprintf("-RemotePath '%s'", remotePath)

	// New-SmbGlobalMapping cmdlet creates an SMB mapping between the container instance
	// and SMB share (FSx for Windows File Server file-system)

	args := []string{
		"New-SmbGlobalMapping",
		localPathArg,
		remotePathArg,
		credsArg,
		"-Persistent $true",
		"-RequirePrivacy $true",
		"-ErrorAction Stop",
	}
	seelog.Debugf("Executing mapping of fsxwindowsfileserver with cmd: %v %v", strings.Join(args[:3], " "), strings.Join(args[4:], " "))

	cmd := execCommand("powershell.exe", args...)
	out, err := cmd.CombinedOutput()
	if err != nil {
		safeOutput := strings.ReplaceAll(string(out), password, "<pass>")
		seelog.Errorf("Failed to map fsxwindowsfileserver resource on the container instance error: %v, out: %v", err, safeOutput)
		fv.setTerminalReason(err.Error())
		return err
	}

	// Get-SmbGlobalMapping cmdlet retrieves existing SMB mappings
	cmd = execCommand("powershell.exe", "Get-SmbGlobalMapping", localPathArg)

	_, err = cmd.CombinedOutput()
	if err != nil {
		return errors.New("failed to verify fsxwindowsfileserver resource map on the container instance")
	}

	return nil
}

func (fv *FSxWindowsFileServerResource) handleRootDirectory(rootDirectory string) {
	dir := utils.GetCanonicalPath(rootDirectory)
	dir = strings.Trim(dir, "\\")

	fv.SetRootDirectory(dir)
	return
}

func (fv *FSxWindowsFileServerResource) removeHostMount(localPathArg string) error {
	// Remove-SmbGlobalMapping cmdlet removes the existing Server Message Block (SMB) mapping between the
	// container instance and SMB share (FSx for Windows File Server file-system)
	cmd := execCommand("powershell.exe", "Remove-SmbGlobalMapping", localPathArg, "-Force")

	_, err := cmd.CombinedOutput()
	if err != nil {
		return err
	}
	return nil
}

func (fv *FSxWindowsFileServerResource) Cleanup() error {
	localPath := fv.GetHostPath()
	// formatting to keep powershell happy
	localPathArg := fmt.Sprintf("-LocalPath \"%s\"", strings.Trim(localPath, `\`))
	err := fv.removeHostMount(localPathArg)
	if err != nil {
		seelog.Warnf("Unable to clear fsxwindowsfileserver host path %s for task %s: %s", localPath, fv.taskARN, err)
	}
	return nil
}

// FSxWindowsFileServerResourceJSON is the json representation of the fsxwindowsfileserver resource
type FSxWindowsFileServerResourceJSON struct {
	Name                   string                            `json:"name"`
	VolumeConfig           FSxWindowsFileServerVolumeConfig  `json:"fsxWindowsFileServerVolumeConfiguration"`
	TaskARN                string                            `json:"taskARN"`
	ExecutionCredentialsID string                            `json:"executionCredentialsID"`
	CreatedAt              *time.Time                        `json:"createdAt,omitempty"`
	DesiredStatus          *FSxWindowsFileServerVolumeStatus `json:"desiredStatus"`
	KnownStatus            *FSxWindowsFileServerVolumeStatus `json:"knownStatus"`
}

// MarshalJSON serialises the FSxWindowsFileServerResourceJSON struct to JSON
func (fv *FSxWindowsFileServerResource) MarshalJSON() ([]byte, error) {
	if fv == nil {
		return nil, errors.New("fsxwindowfileserver resource is nil")
	}
	createdAt := fv.GetCreatedAt()
	return json.Marshal(FSxWindowsFileServerResourceJSON{
		Name:                   fv.Name,
		VolumeConfig:           fv.VolumeConfig,
		TaskARN:                fv.taskARN,
		ExecutionCredentialsID: fv.executionCredentialsID,
		CreatedAt:              &createdAt,
		DesiredStatus: func() *FSxWindowsFileServerVolumeStatus {
			desiredState := fv.GetDesiredStatus()
			s := FSxWindowsFileServerVolumeStatus(desiredState)
			return &s
		}(),
		KnownStatus: func() *FSxWindowsFileServerVolumeStatus {
			knownState := fv.GetKnownStatus()
			s := FSxWindowsFileServerVolumeStatus(knownState)
			return &s
		}(),
	})
}

// UnmarshalJSON deserialises the raw JSON to a FSxWindowsFileServerResourceJSON struct
func (fv *FSxWindowsFileServerResource) UnmarshalJSON(b []byte) error {
	temp := FSxWindowsFileServerResourceJSON{}

	if err := json.Unmarshal(b, &temp); err != nil {
		return err
	}

	fv.Name = temp.Name
	fv.VolumeConfig = temp.VolumeConfig
	fv.taskARN = temp.TaskARN
	fv.executionCredentialsID = temp.ExecutionCredentialsID
	if temp.DesiredStatus != nil {
		fv.SetDesiredStatus(resourcestatus.ResourceStatus(*temp.DesiredStatus))
	}
	if temp.KnownStatus != nil {
		fv.SetKnownStatus(resourcestatus.ResourceStatus(*temp.KnownStatus))
	}
	if temp.CreatedAt != nil && !temp.CreatedAt.IsZero() {
		fv.SetCreatedAt(*temp.CreatedAt)
	}
	return nil
}

// updateAppliedStatusUnsafe updates the resource transitioning status
func (fv *FSxWindowsFileServerResource) updateAppliedStatusUnsafe(knownStatus resourcestatus.ResourceStatus) {
	if fv.appliedStatusUnsafe == resourcestatus.ResourceStatus(FSxWindowsFileServerVolumeStatusNone) {
		return
	}

	// Check if the resource transition has already finished
	if fv.appliedStatusUnsafe <= knownStatus {
		fv.appliedStatusUnsafe = resourcestatus.ResourceStatus(FSxWindowsFileServerVolumeStatusNone)
	}
}

// GetAppliedStatus safely returns the currently applied status of the resource
func (fv *FSxWindowsFileServerResource) GetAppliedStatus() resourcestatus.ResourceStatus {
	fv.lock.RLock()
	defer fv.lock.RUnlock()

	return fv.appliedStatusUnsafe
}

func (fv *FSxWindowsFileServerResource) DependOnTaskNetwork() bool {
	return false
}

// BuildContainerDependency sets the container dependencies of the resource.
func (fv *FSxWindowsFileServerResource) BuildContainerDependency(containerName string, satisfied apicontainerstatus.ContainerStatus,
	dependent resourcestatus.ResourceStatus) {
	return
}

// GetContainerDependencies returns the container dependencies of the resource.
func (fv *FSxWindowsFileServerResource) GetContainerDependencies(dependent resourcestatus.ResourceStatus) []apicontainer.ContainerDependency {
	return nil
}
