using namespace System.Management.Automation
using namespace System.Management.Automation.Language

Describe "SemanticVersion api tests" -Tags 'CI' {
    Context "constructing valid versions" {
        It "string argument constructor" {
            $v = [SemanticVersion]::new("1.2.3-alpha")
            $v.Major | Should Be 1
            $v.Minor | Should Be 2
            $v.Patch | Should Be 3
            $v.Label | Should Be "alpha"
            $v.ToString() | Should Be "1.2.3-alpha"

            $v = [SemanticVersion]::new("1.0.0")
            $v.Major | Should Be 1
            $v.Minor | Should Be 0
            $v.Patch | Should Be 0
            $v.Label | Should BeNullOrEmpty
            $v.ToString() | Should Be "1.0.0"

            $v = [SemanticVersion]::new("3.0")
            $v.Major | Should Be 3
            $v.Minor | Should Be 0
            $v.Patch | Should Be 0
            $v.Label | Should BeNullOrEmpty
            $v.ToString() | Should Be "3.0.0"

            $v = [SemanticVersion]::new("2")
            $v.Major | Should Be 2
            $v.Minor | Should Be 0
            $v.Patch | Should Be 0
            $v.Label | Should BeNullOrEmpty
            $v.ToString() | Should Be "2.0.0"
        }

	    # After the above test, we trust the properties and rely on ToString for validation

        It "int args constructor" {
            $v = [SemanticVersion]::new(1, 0, 0)
            $v.ToString() | Should Be "1.0.0"

            $v = [SemanticVersion]::new(3, 2, 0, "beta.1")
            $v.ToString() | Should Be "3.2.0-beta.1"

            $v = [SemanticVersion]::new(3, 1)
            $v.ToString() | Should Be "3.1.0"

            $v = [SemanticVersion]::new(3)
            $v.ToString() | Should Be "3.0.0"
        }

        It "version arg constructor" {
            $v = [SemanticVersion]::new([Version]::new(1, 2))
            $v.ToString() | Should Be '1.2.0'

            $v = [SemanticVersion]::new([Version]::new(1, 2, 3))
            $v.ToString() | Should Be '1.2.3'
        }

        It "semantic version can round trip through version" {
            $v1 = [SemanticVersion]::new(3, 2, 1, "prerelease")
            $v2 = [SemanticVersion]::new([Version]$v1)
            $v2.ToString() | Should Be "3.2.1-prerelease"
        }
    }

    Context "Comparisons" {
        $v1_0_0 = [SemanticVersion]::new(1, 0, 0)
        $v1_1_0 = [SemanticVersion]::new(1, 1, 0)
        $v1_1_1 = [SemanticVersion]::new(1, 1, 1)
        $v2_1_0 = [SemanticVersion]::new(2, 1, 0)
        $v1_0_0_alpha = [SemanticVersion]::new(1, 0, 0, "alpha")
        $v1_0_0_beta = [SemanticVersion]::new(1, 0, 0, "beta")

        $testCases = @(
            @{ lhs = $v1_0_0; rhs = $v1_1_0 }
            @{ lhs = $v1_0_0; rhs = $v1_1_1 }
            @{ lhs = $v1_1_0; rhs = $v1_1_1 }
            @{ lhs = $v1_0_0; rhs = $v2_1_0 }
            @{ lhs = $v1_0_0_alpha; rhs = $v1_0_0_beta }
            @{ lhs = $v1_0_0_alpha; rhs = $v1_0_0 }
            @{ lhs = $v1_0_0_beta; rhs = $v1_0_0 }
            @{ lhs = $v2_1_0; rhs = "3.0"}
            @{ lhs = "1.5"; rhs = $v2_1_0}
        )
        It "<lhs> less than <rhs>" -TestCases $testCases {
            param($lhs, $rhs)
            $lhs -lt $rhs | Should Be $true
            $rhs -lt $lhs | Should Be $false
        }
        It "<lhs> less than or equal <rhs>" -TestCases $testCases {
            param($lhs, $rhs)
            $lhs -le $rhs | Should Be $true
            $rhs -le $lhs | Should Be $false
            $lhs -le $lhs | Should Be $true
            $rhs -le $rhs | Should Be $true
        }
        It "<lhs> greater than <rhs>" -TestCases $testCases {
            param($lhs, $rhs)
            $lhs -gt $rhs | Should Be $false
            $rhs -gt $lhs | Should Be $true
        }
        It "<lhs> greater than or equal <rhs>" -TestCases $testCases {
            param($lhs, $rhs)
            $lhs -ge $rhs | Should Be $false
            $rhs -ge $lhs | Should Be $true
            $lhs -ge $lhs | Should Be $true
            $rhs -ge $rhs | Should Be $true
        }

        $testCases = @(
            @{ operand = $v1_0_0 }
            @{ operand = $v1_0_0_alpha }
        )
        It "Equality <operand>" -TestCases $testCases {
            param($operand)
            $operand -eq $operand | Should Be $true
            $operand -ne $operand | Should Be $false
            $null -eq $operand | Should Be $false
            $operand -eq $null | Should Be $false
            $null -ne $operand | Should Be $true
            $operand -ne $null | Should Be $true
        }

        It "comparisons with null" {
            $v1_0_0 -lt $null | Should Be $false
            $null -lt $v1_0_0 | Should Be $true
            $v1_0_0 -le $null | Should Be $false
            $null -le $v1_0_0 | Should Be $true
            $v1_0_0 -gt $null | Should Be $true
            $null -gt $v1_0_0 | Should Be $false
            $v1_0_0 -ge $null | Should Be $true
            $null -ge $v1_0_0 | Should Be $false
        }
    }

    Context "error handling" {

        # The specific errors aren't too useful here, but noted in comments
        # so when we pick up a version of Pester that will let us check FullyQualifiedErrorId,
        # it's easier to tweak the tests

        $testCases = @(
            @{ expectedResult = $false; version = $null  }
            @{ expectedResult = $false; version = [NullString]::Value }
            @{ expectedResult = $false; version = "" }
            @{ expectedResult = $false; version = "1.0.0-" }
            @{ expectedResult = $false; version = "-" }
            @{ expectedResult = $false; version = "." }
            @{ expectedResult = $false; version = "-alpha" }
	        @{ expectedResult = $false; version = "1..0" }
	        @{ expectedResult = $false; version = "1.0.-alpha" }
	        @{ expectedResult = $false; version = "1.0." }
	        @{ expectedResult = $false; version = ".0.0" }
        )

        It "parts of version missing" -TestCases $testCases {
            param($version, $expectedResult)
            { [SemanticVersion]::new($version) } | Should Throw # PSArgumentException
            { [SemanticVersion]::Parse($version) } | Should Throw # PSArgumentException
            $semVer = $null
            [SemanticVersion]::TryParse($_, [ref]$semVer) | Should Be $expectedResult
            $semVer | Should Be $null
        }

        $testCases = @(
            @{ expectedResult = $false; version = "-1.0.0"  }
            @{ expectedResult = $false; version = "1.-1.0"  }
            @{ expectedResult = $false; version = "1.0.-1"  }
        )

        It "range check of versions: <version>" -TestCases $testCases {
            param($version, $expectedResult)
            { [SemanticVersion]::new($version) } | Should Throw # PSArgumentException
            { [SemanticVersion]::Parse($version) } | Should Throw # PSArgumentException
            $semVer = $null
            [SemanticVersion]::TryParse($_, [ref]$semVer) | Should Be $expectedResult
            $semVer | Should Be $null
        }

        $testCases = @(
            @{ expectedResult = $false; version = "aa.0.0"  }
            @{ expectedResult = $false; version = "1.bb.0"  }
            @{ expectedResult = $false; version = "1.0.cc"  }
        )

        It "format errors: <version>" -TestCases $testCases {
            param($version, $expectedResult)
            { [SemanticVersion]::new($version) } | Should Throw # PSArgumentException
            { [SemanticVersion]::Parse($version) } | Should Throw # PSArgumentException
            $semVer = $null
            [SemanticVersion]::TryParse($_, [ref]$semVer) | Should Be $expectedResult
            $semVer | Should Be $null
        }

        It "Negative version arguments" {
            { [SemanticVersion]::new(-1, 0) } | Should Throw # PSArgumentException
            { [SemanticVersion]::new(1, -1) } | Should Throw # PSArgumentException
            { [SemanticVersion]::new(1, 1, -1) } | Should Throw # PSArgumentException
        }

        It "Incompatible version throws" {
            # Revision isn't supported
            { [SemanticVersion]::new([Version]::new(0, 0, 0, 4)) } | Should Throw # PSArgumentException
            { [SemanticVersion]::new([Version]::new("1.2.3.4")) } | Should Throw # PSArgumentException
        }
    }

    Context "Serialization" {
        $testCases = @(
            @{ expectedResult = "1.0.0"; semver = [SemanticVersion]::new(1, 0, 0) }
            @{ expectedResult = "1.0.1"; semver = [SemanticVersion]::new(1, 0, 1) }
            @{ expectedResult = "1.0.0-alpha"; semver = [SemanticVersion]::new(1, 0, 0, "alpha") }
            @{ expectedResult = "1.0.0-beta"; semver = [SemanticVersion]::new(1, 0, 0, "beta") }
        )
        It "Can round trip: <semver>" -TestCases $testCases {
            param($semver, $expectedResult)

            $ser = [PSSerializer]::Serialize($semver)
            $des = [PSSerializer]::Deserialize($ser)

            $des | Should BeOfType System.Management.Automation.SemanticVersion
            $des.ToString() | Should Be $expectedResult
        }
    }
}
