Public/TestCaseManagement/Resolve-TcmTestCaseConflict.ps1

function Resolve-TcmTestCaseConflict {
    <#
        .SYNOPSIS
            Resolves synchronization conflicts for test cases.

        .DESCRIPTION
            Resolves conflicts that occur when both local and remote versions of a test case have changes.
            Provides multiple resolution strategies and supports interactive conflict resolution.

            Conflicts occur when content has changed in both the local YAML file and the Azure DevOps work item
            since the last synchronization. This function helps choose which version to keep or merge changes.

        .PARAMETER Id
            The local identifier of the test case with the conflict (e.g., "TC001").
            Accepts pipeline input by value or property name.

        .PARAMETER Strategy
            The conflict resolution strategy to use:
            - Manual: Interactive resolution allowing user to choose (default)
            - LocalWins: Use the local version, overwrite remote changes
            - RemoteWins: Use the remote version, overwrite local changes
            - LatestWins: Use the version with the most recent modification timestamp

        .PARAMETER TestCasesRoot
            Root directory containing test case YAML files.
            If not specified, uses the current directory or searches parent directories for .tcm-config.yaml.

        .EXAMPLE
            PS C:\> Resolve-TcmTestCaseConflict -Id "TC001" -Strategy LocalWins

            Resolves the conflict for TC001 by keeping the local version.

        .EXAMPLE
            PS C:\> Resolve-TcmTestCaseConflict -Id "TC001" -Strategy RemoteWins

            Resolves the conflict for TC001 by using the Azure DevOps version.

        .EXAMPLE
            PS C:\> Resolve-TcmTestCaseConflict -Id "TC001" -Strategy LatestWins

            Resolves the conflict by choosing the version that was modified most recently.

        .EXAMPLE
            PS C:\> "TC001", "TC002" | Resolve-TcmTestCaseConflict -Strategy Manual

            Interactively resolves conflicts for multiple test cases.

        .INPUTS
            System.String
            Accepts test case IDs from the pipeline.

        .OUTPUTS
            None. Displays resolution results to the console.

        .NOTES
            - Manual strategy will prompt for user input to choose resolution approach.
            - LatestWins compares file modification timestamps vs. work item changed dates.
            - Resolution is atomic per test case to prevent inconsistent states.
            - After resolution, the test case will be marked as synced.

        .LINK
            Sync-TcmTestCase

        .LINK
            Get-TcmTestCaseSyncStatus
    #>


    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [string] $Id,

        [Parameter(Mandatory)]
        [ValidateSet('Manual', 'LocalWins', 'RemoteWins', 'LatestWins')]
        [string] $Strategy,

        [string] $TestCasesRoot
    )

    begin {
        # Get configuration
        $config = Get-TcmTestCaseConfig -TestCasesRoot $TestCasesRoot
    }

    process {
        try {
            Write-Verbose "Resolving conflict for test case '$Id' using strategy: $Strategy"

            # Verify there's actually a conflict
            $syncStatus = Get-TcmTestCaseSyncStatus -Id $Id -Config $config

            if ($syncStatus -ne 'conflict') {
                Write-Warning "Test case '$Id' does not have a conflict (status: $syncStatus)"
                return
            }

            # Find local file by scanning
            $localPath = $null
            $yamlFiles = Get-ChildItem -Path $config.TestCasesRoot -Include "*.yaml" -Recurse -File

            foreach ($file in $yamlFiles) {
                try {
                    $fileData = Get-TcmTestCaseFromFile -FilePath $file.FullName -IncludeMetadata -ErrorAction SilentlyContinue
                    if ($fileData.testCase.id -eq $Id) {
                        $localPath = $file.FullName
                        $localData = $fileData
                        break
                    }
                } catch {
                    # Skip files that can't be parsed
                    continue
                }
            }

            if (-not $localPath) {
                throw "Test case with ID '$Id' not found in any local YAML file"
            }

            # The $Id is the Work Item ID
            $collection = $null
            $project = $null
            if ($config.azureDevOps) {
                $collection = $config.azureDevOps.collectionUri
                $project = $config.azureDevOps.project
            }
            $workItem = Get-WorkItem -WorkItem $Id -CollectionUri $collection -Project $project
            $remoteData = ConvertFrom-TcmWorkItemToTestCase -WorkItem $workItem

            switch ($Strategy) {
                'LocalWins' {
                    if ($PSCmdlet.ShouldProcess("Test case '$Id'", "Resolve conflict - keep local changes")) {
                        Write-Host "Resolving conflict for '$Id': Local version wins" -ForegroundColor Yellow
                        Sync-TcmTestCaseToRemote -Id $Id -TestCasesRoot $config.TestCasesRoot -Force
                        Write-Host "✓ Conflict resolved: Local changes pushed to Azure DevOps" -ForegroundColor Green
                    }
                }

                'RemoteWins' {
                    if ($PSCmdlet.ShouldProcess("Test case '$Id'", "Resolve conflict - keep remote changes")) {
                        Write-Host "Resolving conflict for '$Id': Remote version wins" -ForegroundColor Yellow
                        Sync-TcmTestCaseFromRemote -Id $Id -TestCasesRoot $config.TestCasesRoot -Force
                        Write-Host "✓ Conflict resolved: Remote changes pulled from Azure DevOps" -ForegroundColor Green
                    }
                }

                'LatestWins' {
                    # Compare timestamps to determine which is newer
                    $localTimestamp = [DateTime]::Parse($localData.history.lastModifiedAt)
                    $remoteTimestamp = [DateTime]::Parse($workItem.fields.'System.ChangedDate')

                    if ($localTimestamp -gt $remoteTimestamp) {
                        if ($PSCmdlet.ShouldProcess("Test case '$Id'", "Resolve conflict - local is newer")) {
                            Write-Host "Resolving conflict for '$Id': Local version is newer" -ForegroundColor Yellow
                            Sync-TcmTestCaseToRemote -Id $Id -TestCasesRoot $config.TestCasesRoot -Force
                            Write-Host "✓ Conflict resolved: Newer local changes pushed to Azure DevOps" -ForegroundColor Green
                        }
                    } else {
                        if ($PSCmdlet.ShouldProcess("Test case '$Id'", "Resolve conflict - remote is newer")) {
                            Write-Host "Resolving conflict for '$Id': Remote version is newer" -ForegroundColor Yellow
                            Sync-TcmTestCaseFromRemote -Id $Id -TestCasesRoot $config.TestCasesRoot -Force
                            Write-Host "✓ Conflict resolved: Newer remote changes pulled from Azure DevOps" -ForegroundColor Green
                        }
                    }
                }

                'Manual' {
                    # Display conflict information for manual resolution
                    Write-Host "`nConflict Details for Test Case '$Id':" -ForegroundColor Cyan
                    Write-Host "=" * 60 -ForegroundColor Cyan

                    Write-Host "`nLocal Version:" -ForegroundColor Yellow
                    Write-Host " Title: $($localData.testCase.title)"
                    Write-Host " Last Modified: $($localData.history.lastModifiedAt)"
                    Write-Host " Modified By: $($localData.history.lastModifiedBy)"
                    Write-Host " State: $($localData.testCase.state)"
                    Write-Host " Steps Count: $($localData.testCase.steps.Count)"

                    Write-Host "`nRemote Version:" -ForegroundColor Yellow
                    Write-Host " Title: $($workItem.fields.'System.Title')"
                    Write-Host " Last Modified: $($workItem.fields.'System.ChangedDate')"
                    Write-Host " Modified By: $($workItem.fields.'System.ChangedBy'.displayName)"
                    Write-Host " State: $($remoteData.state)"
                    Write-Host " Steps Count: $($remoteData.steps.Count)"

                    Write-Host "`n" -ForegroundColor Cyan
                    Write-Host "To resolve this conflict, run one of the following commands:" -ForegroundColor White
                    Write-Host " Resolve-TcmTestCaseConflict -Id '$Id' -Strategy LocalWins" -ForegroundColor Gray
                    Write-Host " Resolve-TcmTestCaseConflict -Id '$Id' -Strategy RemoteWins" -ForegroundColor Gray
                    Write-Host " Resolve-TcmTestCaseConflict -Id '$Id' -Strategy LatestWins" -ForegroundColor Gray

                    Write-Host "`nOr manually edit the local file and sync:" -ForegroundColor White
                    Write-Host " $localPath" -ForegroundColor Gray
                    Write-Host " Sync-TcmTestCase -Id '$Id'" -ForegroundColor Gray
                }
            }
        } catch {
            Write-Error "Failed to resolve conflict for test case '$Id': $($_.Exception.Message)"
            throw
        }
    }
}