using System.Collections.Generic;
using System.Threading.Tasks;
using ClaudeDo.Ui.Services;
using ClaudeDo.Ui.ViewModels.Conflicts;
using Xunit;

namespace ClaudeDo.Ui.Tests.ViewModels;

public class ConflictResolverViewModelTests
{
    // A file split as: stable "a\n", one conflict, stable "z\n".
    private static ConflictDocumentDto OneConflictFile(string path = "README.md") =>
        new(path, false, new[]
        {
            new MergeSegmentDto(false, "a\n", "", null, ""),
            new MergeSegmentDto(true, "", "ours\n", "base\n", "theirs\n"),
            new MergeSegmentDto(false, "z\n", "", null, ""),
        });

    private sealed class FakeWorker : StubWorkerClient
    {
        public string? WrittenTaskId;
        public string? WrittenPath;
        public string? WrittenContent;
        public bool Continued;
        public bool Aborted;
        public string ContinueStatus = "merged";
        public IReadOnlyList<ConflictDocumentDto> Docs = new[] { OneConflictFile() };
        public bool StartCalled;
        public string? ContinuedPlanningParent;
        public string? AbortedPlanningParent;

        public override Task<MergeResultDto> StartConflictMergeAsync(string taskId, string targetBranch)
        {
            StartCalled = true;
            return Task.FromResult(new MergeResultDto("conflict", new[] { "README.md" }, null));
        }

        public override Task ContinuePlanningMergeAsync(string planningTaskId)
        {
            ContinuedPlanningParent = planningTaskId; return Task.CompletedTask;
        }

        public override Task AbortPlanningMergeAsync(string planningTaskId)
        {
            AbortedPlanningParent = planningTaskId; return Task.CompletedTask;
        }

        public override Task<MergeConflictDocumentsDto> GetMergeConflictDocumentsAsync(string taskId)
            => Task.FromResult(new MergeConflictDocumentsDto(taskId, Docs));

        public override Task WriteConflictResolutionAsync(string taskId, string path, string resolvedContent)
        {
            WrittenTaskId = taskId; WrittenPath = path; WrittenContent = resolvedContent; return Task.CompletedTask;
        }

        public override Task<MergeResultDto> ContinueConflictMergeAsync(string taskId)
        {
            Continued = true;
            return Task.FromResult(new MergeResultDto(ContinueStatus, System.Array.Empty<string>(), null));
        }

        public override Task AbortConflictMergeAsync(string taskId) { Aborted = true; return Task.CompletedTask; }
    }

    [Fact]
    public async Task OpenAsync_LoadsConflicts_AndBlocksContinueUntilResolved()
    {
        var vm = new ConflictResolverViewModel(new FakeWorker(), "task-1");

        var hasConflicts = await vm.OpenAsync("main");

        Assert.True(hasConflicts);
        var file = Assert.Single(vm.Files);
        Assert.Equal("README.md", file.Path);
        Assert.Equal(1, vm.TotalConflicts);
        Assert.NotNull(vm.Current);
        Assert.False(vm.CanContinue);

        vm.Current!.AcceptTheirsCommand.Execute(null);
        Assert.True(vm.CanContinue);
        Assert.Equal(1, vm.ResolvedCount);
    }

    [Fact]
    public async Task Continue_WritesComposedFile_AndClosesOnMerged()
    {
        var worker = new FakeWorker();
        var vm = new ConflictResolverViewModel(worker, "task-1");
        var closed = false;
        vm.CloseRequested = () => closed = true;

        await vm.OpenAsync("main");
        vm.Current!.AcceptOursCommand.Execute(null);   // choose "ours\n" for the single conflict
        await vm.ContinueCommand.ExecuteAsync(null);

        Assert.Equal("README.md", worker.WrittenPath);
        // stable "a\n" + chosen "ours\n" + stable "z\n"
        Assert.Equal("a\nours\nz\n", worker.WrittenContent);
        Assert.True(worker.Continued);
        Assert.True(closed);
    }

    [Fact]
    public async Task Continue_StaysOpenAndReportsError_WhenStillConflicted()
    {
        var worker = new FakeWorker { ContinueStatus = "conflict" };
        var vm = new ConflictResolverViewModel(worker, "task-1");
        var closed = false;
        vm.CloseRequested = () => closed = true;

        await vm.OpenAsync("main");
        vm.Current!.AcceptBothCommand.Execute(null);
        await vm.ContinueCommand.ExecuteAsync(null);

        Assert.False(closed);
        Assert.NotNull(vm.Error);
    }

    [Fact]
    public async Task Navigation_MovesBetweenConflicts_AcrossTheFlattenedList()
    {
        var worker = new FakeWorker
        {
            Docs = new[]
            {
                new ConflictDocumentDto("a.cs", false, new[]
                {
                    new MergeSegmentDto(true, "", "o1\n", null, "t1\n"),
                    new MergeSegmentDto(false, "mid\n", "", null, ""),
                    new MergeSegmentDto(true, "", "o2\n", null, "t2\n"),
                }),
            },
        };
        var vm = new ConflictResolverViewModel(worker, "task-1");

        await vm.OpenAsync("main");

        Assert.Equal(2, vm.TotalConflicts);
        Assert.Equal("o1\n", vm.Current!.Ours);
        Assert.False(vm.PreviousCommand.CanExecute(null));
        Assert.True(vm.NextCommand.CanExecute(null));

        vm.NextCommand.Execute(null);
        Assert.Equal("o2\n", vm.Current!.Ours);
        Assert.False(vm.NextCommand.CanExecute(null));
        Assert.True(vm.PreviousCommand.CanExecute(null));
    }

    [Fact]
    public async Task BinaryConflict_BlocksContinue()
    {
        var worker = new FakeWorker
        {
            Docs = new[] { new ConflictDocumentDto("logo.png", true, System.Array.Empty<MergeSegmentDto>()) },
        };
        var vm = new ConflictResolverViewModel(worker, "task-1");

        var hasConflicts = await vm.OpenAsync("main");

        Assert.True(hasConflicts);          // file is shown
        Assert.True(vm.HasBinaryFiles);
        Assert.Equal(0, vm.TotalConflicts); // nothing to resolve in-app
        Assert.False(vm.CanContinue);       // binary blocks continue
    }

    [Fact]
    public async Task Abort_CallsWorkerAndCloses()
    {
        var worker = new FakeWorker();
        var vm = new ConflictResolverViewModel(worker, "task-1");
        var closed = false;
        vm.CloseRequested = () => closed = true;

        await vm.AbortCommand.ExecuteAsync(null);

        Assert.True(worker.Aborted);
        Assert.True(closed);
    }

    [Fact]
    public async Task OpenForPlanning_LoadsConflicts_WithoutStartingAMerge()
    {
        var worker = new FakeWorker();
        var vm = new ConflictResolverViewModel(worker, "parent-1");

        var hasConflicts = await vm.OpenForPlanningAsync("parent-1", "subtask-7");

        Assert.True(hasConflicts);
        Assert.False(worker.StartCalled);   // the orchestrator already started the merge
        Assert.Equal(1, vm.TotalConflicts);
    }

    [Fact]
    public async Task PlanningContinue_HandsBackToOrchestrator_AndCloses()
    {
        var worker = new FakeWorker();
        var vm = new ConflictResolverViewModel(worker, "parent-1");
        var closed = false;
        vm.CloseRequested = () => closed = true;

        await vm.OpenForPlanningAsync("parent-1", "subtask-7");
        vm.Current!.AcceptOursCommand.Execute(null);
        await vm.ContinueCommand.ExecuteAsync(null);

        Assert.Equal("subtask-7", worker.WrittenTaskId); // resolution written against the subtask's working tree
        Assert.Equal("parent-1", worker.ContinuedPlanningParent);
        Assert.False(worker.Continued);     // single-task continue must NOT be used in planning mode
        Assert.True(closed);
    }

    [Fact]
    public async Task PlanningAbort_AbortsThePlanningMerge()
    {
        var worker = new FakeWorker();
        var vm = new ConflictResolverViewModel(worker, "parent-1");

        await vm.OpenForPlanningAsync("parent-1", "subtask-7");
        await vm.AbortCommand.ExecuteAsync(null);

        Assert.Equal("parent-1", worker.AbortedPlanningParent);
        Assert.False(worker.Aborted);       // single-task abort must NOT be used in planning mode
    }

    // ── Task 1 new tests ─────────────────────────────────────────────────────

    [Fact]
    public void OursTheirsResult_Reconstruction()
    {
        // stable "a\n" + conflict(ours "o\n", base null, theirs "t\n") + stable "z\n"
        var segments = new[]
        {
            MergeFileSegment.Stable("a\n"),
            MergeFileSegment.FromConflict(new MergeConflictBlock("o\n", null, "t\n")),
            MergeFileSegment.Stable("z\n"),
        };
        var file = new MergeFile("f.cs", false, segments);

        Assert.Equal("a\no\nz\n", file.OursText);
        Assert.Equal("a\nt\nz\n", file.TheirsText);
        Assert.Equal("a\nz\n", file.ResultText);   // unresolved conflicts start empty

        // After resolving: ResultText reflects the resolution
        file.Conflicts[0].Resolution = "r\n";
        Assert.Equal("a\nr\nz\n", file.ResultText);
    }

    [Fact]
    public async Task ActiveFile_DefaultsToFirstFile_AfterOpen()
    {
        var vm = new ConflictResolverViewModel(new FakeWorker(), "task-1");

        await vm.OpenAsync("main");

        Assert.NotNull(vm.ActiveFile);
        Assert.Equal("README.md", vm.ActiveFile!.Path);
    }

    [Fact]
    public async Task SelectFile_PreservesResolution_AndUpdatesPassThroughs()
    {
        // Two-file setup
        var worker = new FakeWorker
        {
            Docs = new[]
            {
                new ConflictDocumentDto("a.cs", false, new[]
                {
                    new MergeSegmentDto(false, "a\n", "", null, ""),
                    new MergeSegmentDto(true,  "",    "ours-a\n", null, "theirs-a\n"),
                    new MergeSegmentDto(false, "z\n", "", null, ""),
                }),
                new ConflictDocumentDto("b.cs", false, new[]
                {
                    new MergeSegmentDto(true, "", "ours-b\n", null, "theirs-b\n"),
                }),
            },
        };
        var vm = new ConflictResolverViewModel(worker, "task-1");
        await vm.OpenAsync("main");

        // Resolve the block in file A
        var fileA = vm.Files[0];
        fileA.Conflicts[0].Resolution = "resolved-a\n";

        // Switch to file B
        vm.SelectFileCommand.Execute(vm.Files[1]);
        Assert.Equal("b.cs", vm.ActiveFile!.Path);
        Assert.Equal("", vm.ActiveResultText);   // unresolved conflicts start empty

        // Switch back to file A
        vm.SelectFileCommand.Execute(vm.Files[0]);
        Assert.Equal("a.cs", vm.ActiveFile!.Path);
        // Resolution survived the round-trip
        Assert.Equal("a\nresolved-a\nz\n", vm.ActiveResultText);
        Assert.Equal("a\nours-a\nz\n",     vm.ActiveOursText);
    }

    [Fact]
    public async Task PositionText_ReadsActiveFileConflicts()
    {
        var vm = new ConflictResolverViewModel(new FakeWorker(), "task-1");
        await vm.OpenAsync("main");

        // 1 conflict, 0 resolved
        Assert.Equal("1 conflict · 0 resolved", vm.PositionText);

        vm.Current!.AcceptOursCommand.Execute(null);

        // 1 conflict, 1 resolved
        Assert.Equal("1 conflict · 1 resolved", vm.PositionText);
    }
}
