$ErrorActionPreference = 'Stop';

Set-StrictMode -Version '2.0'

$currentScriptLocation = Split-Path -Parent $MyInvocation.MyCommand.Path
Import-Module -Force (Join-Path $currentScriptLocation '..\..\modules\FileSets.psm1')
Import-Module -Force (Join-Path $currentScriptLocation '..\..\modules\Hash.psm1')

. $currentScriptLocation\TestExtensions.ps1

Describe 'File sets' {

	$testDrive = Convert-Path 'TestDrive:\' # required as context changes from PS to C#

	function Get-TestBackupPath($basePath, $destPath) {
		return (Join-Path $basePath ('backup\' + (Get-ContentHash $destPath -Algorithm 'MD5')))
	}
	
	BeforeEach {
		Remove-Item -Recurse 'TestDrive:\*'
		mkdir 'TestDrive:\backup' | Out-Null;
	}

	Context 'when I create a fileset' {
		It 'should fail if the base path does not exist' {
			{ New-FileSet -SourceFolderPath 'foo' -DestinationFolderPath 'bar' -BasePath "${testDrive}notexist" } |
				Should Throw "Supplied BasePath does not exist: ${testDrive}notexist"
		}

		It 'should fail if the files folder does not exist under the base path' {
			mkdir "$testDrive\base" | Out-Null 
			
			{ New-FileSet -SourceFolderPath 'foo' -DestinationFolderPath 'bar' -BasePath "${testDrive}base" } |
				Should Throw "The fileset source folder doesn't exist, no files can be copied."
		}
		
		It 'should fail if the source path could not be found' {
			mkdir "$testDrive\base\files" -Force | Out-Null 

			{ New-FileSet -SourceFolderPath 'foo' -DestinationFolderPath 'bar' -BasePath "${testDrive}base" } |
				Should Throw "The fileset source folder doesn't exist, no files can be copied."
		}
	}

	Context 'when I backup a fileset to its backup location' {

		It 'should copy any files that already exist in the destination folder to the backup location' {

			$sourcePath = (Join-Path $testDrive 'files\test')
			$destPath = (Join-Path $testDrive 'testdest')
			$backupPath = (Get-TestBackupPath $testDrive $destPath)

			# setup files for scenario
			New-TestFiles `
				$sourcePath `
				@('d11\d21\f31.txt','d11\d21\f32.txt','d11\f21.txt','f11.txt')
			New-TestFiles `
				$destPath `
				@('d11\f21.txt','f12.txt') # one overlap, one not

			# create the fileset to test
			$fs = New-FileSet 'test' $destPath -BasePath $testDrive

			# preconditions
			$destPath | Should Exist
			@(Get-ChildItem $destPath -Recurse).Count | Should Be 3

			# act
			Backup-FileSet $fs

			# assert
			$backupPath | Should ContainFilesFrom $destPath,@('d11\f21.txt')
			@(Get-ChildItem -Path $backupPath -Recurse).Count | Should Be 2
		}

		It 'should copy files to the backup file only once in the case of multiple backup invocations' {

			$sourcePath = (Join-Path $testDrive 'files\test')
			$destPath = (Join-Path $testDrive 'testdest')
			$backupPath = (Get-TestBackupPath $testDrive $destPath)

			# setup files for scenario
			New-TestFiles `
				$sourcePath `
				@('d11\d21\f31.txt','d11\d21\f32.txt','d11\f21.txt','f11.txt')
			New-TestFiles `
				$destPath `
				@('d11\f21.txt','f12.txt') # one overlap, one not

			# create the fileset to test
			$fs = New-FileSet 'test' $destPath -BasePath $testDrive

			# preconditions
			$destPath | Should Exist
			@(Get-ChildItem $destPath -Recurse).Count | Should Be 3

			# act
			Backup-FileSet $fs
			Copy-FileSet $fs
			Backup-FileSet $fs

			# assert
			$backupPath | Should ContainFilesFrom $destPath, @('d11\f21.txt')
			@('d11\d21\f31.txt','d11\d21\f32.txt','f11.txt') |
				ForEach-Object { Join-Path $backupPath $_ } | 
				Should Not Exist
			@(Get-ChildItem -Path $backupPath -Recurse).Count | Should Be 2
		}

		It 'given two filesets using the same source data, a unique backup will be made of each destination' {
			$sourcePath = (Join-Path $testDrive 'files\test')

			$destPath = (Join-Path $testDrive 'testdest')
			$destPath2 = (Join-Path $testDrive 'testdest2')

			$backupPath = (Get-TestBackupPath $testDrive $destPath)
			$backupPath2 = (Get-TestBackupPath $testDrive $destPath2)

			New-TestFiles `
				$sourcePath `
				@('d11\d21\f31.txt','d11\d21\f32.txt','d11\f21.txt','f11.txt')

			New-TestFiles `
				$destPath `
				@('d11\f21.txt','f12.txt') # one overlap, one not
			New-TestFiles `
				$destPath2 `
				@('d11\d21\f31.txt','f13.txt') # one overlap, one not

			$fs = New-FileSet 'test' $destPath -BasePath $testDrive
			$fs2 = New-FileSet 'test' $destPath2 -BasePath $testDrive

			$destPath,$destPath2 | Should Exist
			@(Get-ChildItem $destPath -Recurse).Count | Should Be 3
			@(Get-ChildItem $destPath2 -Recurse).Count | Should Be 4

			Backup-FileSet $fs
			Backup-FileSet $fs2

			$backupPath | Should ContainFilesFrom $destPath,@('d11\f21.txt')
			$backupPath2 | Should ContainFilesFrom $destPath2,@('d11\d21\f31.txt')
			@(Get-ChildItem -Path $backupPath -Recurse).Count | Should Be 2
			@(Get-ChildItem -Path $backupPath2 -Recurse).Count | Should Be 3
		}

		It 'will make a record of the files present originally in the destination, in case of a partial copy' {

			$sourcePath = (Join-Path $testDrive 'files\test')
			$destPath = (Join-Path $testDrive 'testdest')
			$backupPath = (Get-TestBackupPath $testDrive $destPath)

			# setup files for scenario
			New-TestFiles `
				$sourcePath `
				@('d11\d21\f31.txt','d11\d21\f32.txt','d11\f21.txt','f11.txt')
			New-TestFiles `
				$destPath `
				@('d11\d21\f31.txt','d11\f21.txt','f12.txt') # two overlaps, one not

			# create the fileset to test
			$fs = New-FileSet 'test' $destPath -BasePath $testDrive

			# preconditions
			$destPath | Should Exist
			@(Get-ChildItem $destPath -Recurse).Count | Should Be 5

			# act
			Backup-FileSet $fs

			# assert
			"$backupPath.manifest" | Should Exist
			"$backupPath.manifest" | Should Contain "`"d11\\d21\\f31.txt`""
			"$backupPath.manifest" | Should Contain "`"d11\\f21.txt`""
		}
	}

	Context 'when I copy a fileset to its destination' {
		It 'should create the destination path if it does not exist' {
			
		}

		It 'should copy all files present in the source to the correct destination path' {

			$sourcePath = (Join-Path $testDrive 'files\test')
			$destPath = (Join-Path $testDrive 'testdest')

			New-TestFiles `
				$sourcePath `
				@('d11\d21\f31.txt','d11\d21\f32.txt','d11\f21.txt','f11.txt')

			$fs = New-FileSet 'test' $destPath -BasePath $testDrive

			$destPath | Should Exist
			@(Get-ChildItem $destPath).Count | Should Be 0

			# act
			Copy-FileSet $fs

			# assert
			$destPath | Should ContainAllFilesFrom $sourcePath
		}

		It 'should overwrite any files that exist in the target' {

		}

		It 'should ignore empty folders present in the source' {}
		It 'should retry a copy a configurable number of times over a configurable time period' {}
	}

	Context "when I restore from a backup (uninstall)" {
# what happens when i backup twice?
# what happens if i install twice?
# what happens if i uninstall twice?

		It 'should fail without modification if the set of files in the manifest aren''t present in the backup folder' {
			$sourcePath = (Join-Path $testDrive 'files\test')
			$destPath = (Join-Path $testDrive 'testdest')
			$backupPath = (Get-TestBackupPath $testDrive $destPath)

			# setup files for scenario
			New-TestFiles `
				$sourcePath `
				@('d11\d21\f31.txt','d11\f21.txt')
			New-TestFiles `
				$destPath `
				@('d11\d21\f31.txt','d11\f21.txt') # all overlap

			# create the fileset to test
			$fs = New-FileSet 'test' $destPath -BasePath $testDrive

			# preconditions
			$destPath | Should Exist
			@(Get-ChildItem $destPath -Recurse).Count | Should Be 4

			# act
			Backup-FileSet $fs
			Remove-Item (Join-Path $backupPath '*') -Recurse -Force

			{ Restore-FileSet $fs } |
				# assert
				Should Throw 'Some files present in backup manifest do not exist in backup folder'

		}

		It 'should fail without modification if the set of files to be restored don''t all exist in the target' {
			$sourcePath = (Join-Path $testDrive 'files\test')
			$destPath = (Join-Path $testDrive 'testdest')
			$backupPath = (Get-TestBackupPath $testDrive $destPath)

			# setup files for scenario
			New-TestFiles `
				$sourcePath `
				@('d11\d21\f31.txt','d11\f21.txt')
			New-TestFiles `
				$destPath `
				@('d11\d21\f31.txt','d11\f21.txt') # all overlap

			# create the fileset to test
			$fs = New-FileSet 'test' $destPath -BasePath $testDrive

			# preconditions
			$destPath | Should Exist
			@(Get-ChildItem $destPath -Recurse).Count | Should Be 4

			# act
			Backup-FileSet $fs
			Remove-Item (Join-Path $destPath '*') -Recurse -Force

			{ Restore-FileSet $fs } |
				# assert
				Should Throw 'The following file(s) were marked as overwritten, but do not exist in the destination folder'
		}

		It 'should fail without modification if the target files have been changed from what is present in the source or backup' {
			$sourcePath = (Join-Path $testDrive 'files\test')
			$destPath = (Join-Path $testDrive 'testdest')
			$backupPath = (Get-TestBackupPath $testDrive $destPath)

			# setup files for scenario
			New-TestFiles `
				$sourcePath `
				@('d11\d21\f31.txt','d11\f21.txt')
			New-TestFiles `
				$destPath `
				@('d11\d21\f31.txt','d11\f21.txt') # all overlap

			# create the fileset to test
			$fs = New-FileSet 'test' $destPath -BasePath $testDrive

			# preconditions
			$destPath | Should Exist
			@(Get-ChildItem $destPath -Recurse).Count | Should Be 4

			# act
			Backup-FileSet $fs
			Add-Content -Path (Join-Path $destPath 'd11\d21\f31.txt') -Value 'modify the file contents'

			{ Restore-FileSet $fs } |
				# assert
				Should Throw 'The following file(s) were marked as overwritten, but match neither the original files nor the source files of this fileset'
		}

		It 'should fail without modification if the target files have been changed from what is present in the source or backup, even if there are multiple filesets' {
			$sourcePath = (Join-Path $testDrive 'files\test')
			$destPath = (Join-Path $testDrive 'testdest')
			$backupPath = (Get-TestBackupPath $testDrive $destPath)
			$sourcePath2 = (Join-Path $testDrive 'files\test2')
			$destPath2 = (Join-Path $testDrive 'testdest2')
			$backupPath2 = (Get-TestBackupPath $testDrive $destPath)

			# setup files for scenario
			New-TestFiles `
				$sourcePath `
				@('d11\d21\f31.txt','d11\f21.txt')
			New-TestFiles `
				$destPath `
				@('d11\d21\f31.txt','d11\f21.txt') # all overlap
			New-TestFiles `
				$sourcePath2 `
				@('f11.txt', 'f12.txt')
			New-TestFiles `
				$destPath2 `
				@('f11.txt')

			mkdir 'TestDrive:\backup' -Force | Out-Null

			# create the fileset to test
			$fs = New-FileSet 'test' $destPath -BasePath $testDrive
			$fs2 = New-FileSet 'test2' $destPath2 -BasePath $testDrive

			$filesets = @($fs, $fs2)

			# preconditions
			$destPath | Should Exist
			$destPath2 | Should Exist
			@(Get-ChildItem $destPath -Recurse).Count | Should Be 4
			@(Get-ChildItem $destPath2 -Recurse).Count | Should Be 1

			# act
			$filesets | Backup-FileSet
			$filesets | Copy-FileSet
			Add-Content -Path (Join-Path $destPath2 'f11.txt') -Value 'modify the file contents'

			{ $filesets | Restore-FileSet } |
				# assert
				Should Throw 'The following file(s) were marked as overwritten, but match neither the original files nor the source files of this fileset'
			
			$destPath | Should ContainAllFilesFrom $sourcePath;
			$destPath2 | Should ContainFilesFrom $sourcePath2, @('f12.txt');
			(Join-Path $destPath2 'f11.txt') | Should Contain 'modify the file contents'
		}

		It 'should be possible to force a restore, skipping verification' {}
		
		It 'should copy the files present in the backup folder to the target path' {
			$sourcePath = (Join-Path $testDrive 'files\test')
			$destPath = (Join-Path $testDrive 'testdest')
			$backupPath = (Get-TestBackupPath $testDrive $destPath)

			# setup files for scenario
			New-TestFiles `
				$sourcePath `
				@('d11\d21\f31.txt','d11\d21\f32.txt','d11\f21.txt','f11.txt')
			New-TestFiles `
				$destPath `
				@('d11\d21\f32.txt','d11\f21.txt','f12.txt') # two overlap, one not

			# create the fileset to test
			$fs = New-FileSet 'test' $destPath -BasePath $testDrive

			# preconditions
			$destPath | Should Exist
			@(Get-ChildItem $destPath -Recurse).Count | Should Be 5

			# act
			Backup-FileSet $fs
			Copy-FileSet $fs
			Restore-FileSet $fs

			# assert
			$destPath | Should ContainFilesFrom $destPath, @('d11\d21\f32.txt','d11\f21.txt','f12.txt')
			@(Get-ChildItem $destPath -Recurse).Count | Should Be 5
		}
		
		It 'should not modify files in the target that are not present in the backup' {}
		It 'should remove files that are present in the source but were not present in the destination at the time of patch application' {}
		It 'should handle the case where the patch did not successfully apply' {
			$sourcePath = (Join-Path $testDrive 'files\test')
			$destPath = (Join-Path $testDrive 'testdest')
			$backupPath = (Get-TestBackupPath $testDrive $destPath)

			# setup files for scenario
			New-TestFiles `
				$sourcePath `
				@('d11\d21\f31.txt','d11\d21\f32.txt','d11\f21.txt','f11.txt')
			New-TestFiles `
				$destPath `
				@('d11\d21\f32.txt','d11\f21.txt','f12.txt') # will have two overlaps, one not


			# create the fileset to test
			$fs = New-FileSet 'test' $destPath -BasePath $testDrive

			# preconditions
			$destPath | Should Exist
			@(Get-ChildItem $destPath -Recurse).Count | Should Be 5

			# act
			Backup-FileSet $fs

			# simulate a partial copy by copying filtered filesets
			@(
				(New-FileSet 'test' $destPath -BasePath $testDrive -SourceFilter 'f11.txt'),
				(New-FileSet 'test' $destPath -BasePath $testDrive -SourceFilter 'f32.txt')
			) | Copy-FileSet

			# ensure the middle state is as expected
			$destPath | Should ContainFilesFrom $sourcePath, @('d11\d21\f32.txt','f11.txt')
			$destPath | Should ContainFilesFrom $destPath, @('d11\f21.txt','f12.txt')
			@(Get-ChildItem $destPath -Recurse).Count | Should Be 6

			Restore-FileSet $fs

			# assert
			$destPath | Should ContainFilesFrom $destPath, @('d11\d21\f32.txt','d11\f21.txt','f12.txt')
			(Join-Path $destPath 'f11.txt') | Should Not Exist
			@(Get-ChildItem $destPath -Recurse).Count | Should Be 5
		}
	}

	Context "when I use an XDT in a file set to transform an XML file in the destination" {

		$testBaseFolder = Join-Path $currentScriptLocation 'transformations'

		Mock 'Invoke-XdtTransform' -ModuleName 'FileSets' -MockWith { }

		It "runs transformation against correct file" {
			

			Copy-Item $testBaseFolder\simple\files 'TestDrive:\files' -Recurse
			Copy-Item $testBaseFolder\simple\target 'TestDrive:\target' -Recurse

			$sut = `
				New-FileSet 'test' `
					-DestinationFolderPath (Join-Path $testDrive 'target\test')`
					-BasePath $testDrive

			Copy-FileSet $sut

			Assert-MockCalled 'Invoke-XdtTransform' -ModuleName 'FileSets' -ParameterFilter {
				$SourceXML -eq (Join-Path $testDrive 'target\test\test.xml')`
					-and $TransformXDT -eq (Join-Path $testDrive 'files\test\test.xml.xdt')`
					-and $DestinationXML -eq (Join-Path $testDrive 'target\test\test.xml')
			}
		}
	}

	Context "when I use an XDT in a file set to transform an XML file and I attempt to restore the original" {
		$testBaseFolder = Join-Path $currentScriptLocation 'transformations'

		It "allows the restore of the original if the file is unchanged after transformation" {

			Copy-Item $testBaseFolder\simple\files 'TestDrive:\files' -Recurse
			Copy-Item $testBaseFolder\simple\target 'TestDrive:\target' -Recurse

			'TestDrive:\target\test\test.xml' | Should Not Contain 'tagName="AddSAPTransactionProfileToObjectDialog"'

			$sut = `
				New-FileSet 'test' `
					-DestinationFolderPath (Join-Path $testDrive 'target\test')`
					-BasePath $testDrive

			Backup-FileSet $sut
			Copy-FileSet $sut

			# transformed
			'TestDrive:\target\test\test.xml' | Should Contain 'tagName="AddSAPTransactionProfileToObjectDialog"'

			Restore-FileSet $sut

			# reverted
			'TestDrive:\target\test\test.xml' | Should Not Contain 'tagName="AddSAPTransactionProfileToObjectDialog"'
		}

		It "fails to restore if the destination file has been changed after transformation" {

			Copy-Item $testBaseFolder\simple\files 'TestDrive:\files' -Recurse
			Copy-Item $testBaseFolder\simple\target 'TestDrive:\target' -Recurse

			'TestDrive:\target\test\test.xml' | Should Not Contain 'tagName="AddSAPTransactionProfileToObjectDialog"'

			$sut = `
				New-FileSet 'test' `
					-DestinationFolderPath (Join-Path $testDrive 'target\test')`
					-BasePath $testDrive

			Backup-FileSet $sut
			Copy-FileSet $sut

			# transformed
			'TestDrive:\target\test\test.xml' | Should Contain 'tagName="AddSAPTransactionProfileToObjectDialog"'
			'modified content' | Add-Content -Path 'TestDrive:\target\test\test.xml'

			{ Restore-FileSet $sut } |
				Should Throw 'The following file(s) were marked as overwritten, but match neither the original files nor the source files of this fileset:'

			# reverted
			'TestDrive:\target\test\test.xml' | Should Contain 'modified content'
		}
	}

	Context 'when I transform a file with the XDT engine' {
		$testBaseFolder = Join-Path $currentScriptLocation 'transformations'

		It "fails if the source XML file doesn't exist" {
			$base = Join-Path $testBaseFolder 'xdt-only'
			$dest = Join-Path (Convert-Path 'TestDrive:\') 'result.xml'

			{ Invoke-XdtTransform -SourceXML $base\test-not-exists.xml -TransformXDT $base\test.xml.xdt -DestinationXML $dest } |
				Should Throw

			@(Get-ChildItem 'TestDrive:\result.xml*').Count | Should Be 0
		}

		It "fails when SlowCheetah returns non-zero" {
			$base = Join-Path $testBaseFolder 'xdt-only'
			$dest = Join-Path (Convert-Path 'TestDrive:\') 'result.xml'

			{ Invoke-XdtTransform -SourceXML $base\test-not-exists.xml -TransformXDT $base\test.xml.xdt -DestinationXML $dest } |
				Should Throw

			@(Get-ChildItem 'TestDrive:\result.xml*').Count | Should Be 0
		}

		It "succeeds when SlowCheetah succeeds" {
			$base = Join-Path $testBaseFolder 'xdt-only'
			$dest = Join-Path (Convert-Path 'TestDrive:\') 'result.xml'

			Invoke-XdtTransform -SourceXML $base\test.xml -TransformXDT $base\test.xml.xdt -DestinationXML $dest 

			@(Get-ChildItem 'TestDrive:\result.xml*').Count | Should Be 1
		}

		It "transforms file as expected" {
			$base = Join-Path $testBaseFolder 'xdt-only'
			$dest = Join-Path (Convert-Path 'TestDrive:\') 'result.xml'

			Invoke-XdtTransform -SourceXML $base\test.xml -TransformXDT $base\test.xml.xdt -DestinationXML $dest 

			'TestDrive:\result.xml' | 
				Should Contain 'add tagName="AddedTag"'
		}

		It "use environment variable as expected when present" {
			$base = Join-Path $testBaseFolder 'xdt-only'
			$dest = Join-Path (Convert-Path 'TestDrive:\') 'result.xml'
			$env:EnvVariable = [guid]::NewGuid().ToString();

			Invoke-XdtTransform -SourceXML $base\test.xml -TransformXDT $base\env-variable.xdt -DestinationXML $dest 

			'TestDrive:\result.xml' | 
				Should Contain "env=`"${env:EnvVariable}`""
		}
	}
}
