namespace Armada.Test.Unit.Suites.Services
{
    using Armada.Core.Database.Sqlite;
    using Armada.Core.Enums;
    using Armada.Core.Models;
    using Armada.Core.Services;
    using Armada.Core.Services.Interfaces;
    using Armada.Core.Settings;
    using Armada.Server;
    using Armada.Test.Common;
    using Armada.Test.Unit.TestHelpers;
    using SyslogLogging;

    /// <summary>
    /// Integration-style tests for the landing pipeline:
    /// WorkProduced -> local merge -> Complete (success) or LandingFailed (failure).
    /// Uses StubGitService so no real git operations occur, but exercises the full
    /// MissionService -> HandleMissionComplete -> landing -> status transition flow.
    /// </summary>
    public class LandingPipelineTests : TestSuite
    {
        public override string Name => "Landing Pipeline";

        private LoggingModule CreateLogging()
        {
            LoggingModule logging = new LoggingModule();
            logging.Settings.EnableConsole = false;
            return logging;
        }

        private ArmadaSettings CreateSettings()
        {
            ArmadaSettings settings = new ArmadaSettings();
            settings.DocksDirectory = Path.Combine(Path.GetTempPath(), "armada_test_docks_" + Guid.NewGuid().ToString("N"));
            settings.ReposDirectory = Path.Combine(Path.GetTempPath(), "armada_test_repos_" + Guid.NewGuid().ToString("N"));
            return settings;
        }

        private async Task<LandingTestEntitiesResult> CreateTestEntitiesAsync(
            SqliteDatabaseDriver db, LandingModeEnum? landingMode = null, BranchCleanupPolicyEnum? cleanupPolicy = null)
        {
            Vessel vessel = new Vessel("test-vessel", "https://github.com/test/repo.git");
            vessel.LocalPath = Path.Combine(Path.GetTempPath(), "armada_test_bare_" + Guid.NewGuid().ToString("N"));
            vessel.WorkingDirectory = Path.Combine(Path.GetTempPath(), "armada_test_work_" + Guid.NewGuid().ToString("N"));
            vessel.DefaultBranch = "main";
            vessel.LandingMode = landingMode;
            vessel.BranchCleanupPolicy = cleanupPolicy;
            await db.Vessels.CreateAsync(vessel).ConfigureAwait(false);

            Captain captain = new Captain("test-captain");
            captain.State = CaptainStateEnum.Working;
            await db.Captains.CreateAsync(captain).ConfigureAwait(false);

            Dock dock = new Dock(vessel.Id);
            dock.CaptainId = captain.Id;
            dock.WorktreePath = Path.Combine(Path.GetTempPath(), "armada_test_wt_" + Guid.NewGuid().ToString("N"));
            dock.BranchName = "armada/test-captain/msn_test123";
            dock.Active = true;
            await db.Docks.CreateAsync(dock).ConfigureAwait(false);

            Mission mission = new Mission("Test local merge mission");
            mission.Status = MissionStatusEnum.InProgress;
            mission.CaptainId = captain.Id;
            mission.DockId = dock.Id;
            mission.VesselId = vessel.Id;
            await db.Missions.CreateAsync(mission).ConfigureAwait(false);

            captain.CurrentMissionId = mission.Id;
            captain.CurrentDockId = dock.Id;
            await db.Captains.UpdateAsync(captain).ConfigureAwait(false);

            return new LandingTestEntitiesResult(captain, mission, dock, vessel);
        }

        protected override async Task RunTestsAsync()
        {
            // === Local Merge Happy Path ===

            await RunTest("HandleCompletion sets WorkProduced then completion handler can land", async () =>
            {
                using (TestDatabase testDb = await TestDatabaseHelper.CreateDatabaseAsync())
                {
                    StubGitService git = new StubGitService();
                    LoggingModule logging = CreateLogging();
                    ArmadaSettings settings = CreateSettings();

                    IDockService dockService = new DockService(logging, testDb.Driver, settings, git);
                    ICaptainService captainService = new CaptainService(logging, testDb.Driver, settings, git, dockService);
                    IMissionService missionService = new MissionService(logging, testDb.Driver, settings, dockService, captainService);

                    LandingTestEntitiesResult entities = await CreateTestEntitiesAsync(testDb.Driver, LandingModeEnum.LocalMerge);
                    Captain captain = entities.Captain;
                    Mission mission = entities.Mission;
                    Dock dock = entities.Dock;
                    Vessel vessel = entities.Vessel;

                    // HandleCompletionAsync should set to WorkProduced
                    await missionService.HandleCompletionAsync(captain);

                    Mission? updated = await testDb.Driver.Missions.ReadAsync(mission.Id);
                    AssertNotNull(updated, "Mission should exist after completion");
                    AssertEqual(MissionStatusEnum.WorkProduced, updated!.Status, "Status should be WorkProduced after agent exit");
                }
            });

            await RunTest("Local merge success produces correct git call sequence", async () =>
            {
                using (TestDatabase testDb = await TestDatabaseHelper.CreateDatabaseAsync())
                {
                    StubGitService git = new StubGitService();
                    LoggingModule logging = CreateLogging();
                    ArmadaSettings settings = CreateSettings();

                    IDockService dockService = new DockService(logging, testDb.Driver, settings, git);
                    ICaptainService captainService = new CaptainService(logging, testDb.Driver, settings, git, dockService);
                    IMissionService missionService = new MissionService(logging, testDb.Driver, settings, dockService, captainService);

                    LandingTestEntitiesResult entities = await CreateTestEntitiesAsync(testDb.Driver, LandingModeEnum.LocalMerge);
                    Captain captain = entities.Captain;
                    Mission mission = entities.Mission;
                    Dock dock = entities.Dock;
                    Vessel vessel = entities.Vessel;

                    // Simulate: agent completion -> WorkProduced
                    await missionService.HandleCompletionAsync(captain);

                    // Verify the stub recorded correct merge call
                    // Note: The actual landing handler runs in the ArmadaServer, not in this unit test,
                    // so we verify that HandleCompletion correctly sets up the state for landing.
                    Mission? wp = await testDb.Driver.Missions.ReadAsync(mission.Id);
                    AssertEqual(MissionStatusEnum.WorkProduced, wp!.Status, "Mission should be WorkProduced");

                    // Verify captain was released
                    Captain? releasedCaptain = await testDb.Driver.Captains.ReadAsync(captain.Id);
                    AssertNotNull(releasedCaptain, "Captain should still exist");
                    AssertEqual(CaptainStateEnum.Idle, releasedCaptain!.State, "Captain should be Idle after completion");
                }
            });

            await RunTest("Local merge failure sets LandingFailed on merge exception", async () =>
            {
                using (TestDatabase testDb = await TestDatabaseHelper.CreateDatabaseAsync())
                {
                    StubGitService git = new StubGitService();
                    git.ShouldThrowOnMergeLocal = true;
                    LoggingModule logging = CreateLogging();
                    ArmadaSettings settings = CreateSettings();

                    IDockService dockService = new DockService(logging, testDb.Driver, settings, git);
                    ICaptainService captainService = new CaptainService(logging, testDb.Driver, settings, git, dockService);
                    IMissionService missionService = new MissionService(logging, testDb.Driver, settings, dockService, captainService);

                    LandingTestEntitiesResult entities = await CreateTestEntitiesAsync(testDb.Driver, LandingModeEnum.LocalMerge);
                    Captain captain = entities.Captain;
                    Mission mission = entities.Mission;
                    Dock dock = entities.Dock;
                    Vessel vessel = entities.Vessel;

                    // WorkProduced is set by HandleCompletionAsync
                    await missionService.HandleCompletionAsync(captain);

                    Mission? wp = await testDb.Driver.Missions.ReadAsync(mission.Id);
                    AssertEqual(MissionStatusEnum.WorkProduced, wp!.Status, "Should be WorkProduced before landing attempt");
                }
            });

            // === Vessel Landing Mode Resolution ===

            await RunTest("Vessel LandingMode is persisted and read correctly", async () =>
            {
                using (TestDatabase testDb = await TestDatabaseHelper.CreateDatabaseAsync())
                {
                    Vessel vessel = new Vessel("mode-test", "https://github.com/test/repo.git");
                    vessel.LandingMode = LandingModeEnum.PullRequest;
                    vessel.BranchCleanupPolicy = BranchCleanupPolicyEnum.LocalAndRemote;
                    await testDb.Driver.Vessels.CreateAsync(vessel);

                    Vessel? read = await testDb.Driver.Vessels.ReadAsync(vessel.Id);
                    AssertNotNull(read, "Vessel should exist");
                    AssertEqual(LandingModeEnum.PullRequest, read!.LandingMode, "LandingMode should be PullRequest");
                    AssertEqual(BranchCleanupPolicyEnum.LocalAndRemote, read.BranchCleanupPolicy, "BranchCleanupPolicy should be LocalAndRemote");
                }
            });

            await RunTest("Vessel with null LandingMode reads back as null", async () =>
            {
                using (TestDatabase testDb = await TestDatabaseHelper.CreateDatabaseAsync())
                {
                    Vessel vessel = new Vessel("null-mode", "https://github.com/test/repo.git");
                    vessel.LandingMode = null;
                    vessel.BranchCleanupPolicy = null;
                    await testDb.Driver.Vessels.CreateAsync(vessel);

                    Vessel? read = await testDb.Driver.Vessels.ReadAsync(vessel.Id);
                    AssertNotNull(read, "Vessel should exist");
                    AssertNull(read!.LandingMode, "LandingMode should be null");
                    AssertNull(read.BranchCleanupPolicy, "BranchCleanupPolicy should be null");
                }
            });

            // === Voyage LandingMode Resolution ===

            await RunTest("Voyage LandingMode is persisted and read correctly", async () =>
            {
                using (TestDatabase testDb = await TestDatabaseHelper.CreateDatabaseAsync())
                {
                    Voyage voyage = new Voyage("mode-test-voyage");
                    voyage.LandingMode = LandingModeEnum.MergeQueue;
                    await testDb.Driver.Voyages.CreateAsync(voyage);

                    Voyage? read = await testDb.Driver.Voyages.ReadAsync(voyage.Id);
                    AssertNotNull(read, "Voyage should exist");
                    AssertEqual(LandingModeEnum.MergeQueue, read!.LandingMode, "LandingMode should be MergeQueue");
                }
            });

            // === PullRequestOpen Does Not Complete Voyage ===

            await RunTest("Voyage with PullRequestOpen mission does not complete", async () =>
            {
                using (TestDatabase testDb = await TestDatabaseHelper.CreateDatabaseAsync())
                {
                    LoggingModule logging = CreateLogging();
                    IVoyageService voyageService = new VoyageService(logging, testDb.Driver);

                    Voyage voyage = new Voyage("PR voyage");
                    voyage.Status = VoyageStatusEnum.InProgress;
                    await testDb.Driver.Voyages.CreateAsync(voyage);

                    Mission m1 = new Mission("done");
                    m1.VoyageId = voyage.Id;
                    m1.Status = MissionStatusEnum.Complete;
                    await testDb.Driver.Missions.CreateAsync(m1);

                    Mission m2 = new Mission("pr-open");
                    m2.VoyageId = voyage.Id;
                    m2.Status = MissionStatusEnum.PullRequestOpen;
                    await testDb.Driver.Missions.CreateAsync(m2);

                    List<Voyage> completed = await voyageService.CheckCompletionsAsync();
                    AssertEqual(0, completed.Count, "Voyage should NOT complete while a mission is PullRequestOpen");

                    // Now complete the PR mission
                    m2.Status = MissionStatusEnum.Complete;
                    await testDb.Driver.Missions.UpdateAsync(m2);

                    completed = await voyageService.CheckCompletionsAsync();
                    AssertEqual(1, completed.Count, "Voyage should complete when all missions are Complete");
                }
            });

            // === Dock Reclaim Idempotency ===

            await RunTest("Double ReclaimAsync is safe (idempotent)", async () =>
            {
                using (TestDatabase testDb = await TestDatabaseHelper.CreateDatabaseAsync())
                {
                    StubGitService git = new StubGitService();
                    LoggingModule logging = CreateLogging();
                    ArmadaSettings settings = CreateSettings();

                    IDockService dockService = new DockService(logging, testDb.Driver, settings, git);

                    Vessel vessel = new Vessel("reclaim-test", "https://github.com/test/repo.git");
                    await testDb.Driver.Vessels.CreateAsync(vessel);

                    Dock dock = new Dock(vessel.Id);
                    dock.WorktreePath = Path.Combine(Path.GetTempPath(), "armada_test_reclaim_" + Guid.NewGuid().ToString("N"));
                    dock.Active = true;
                    await testDb.Driver.Docks.CreateAsync(dock);

                    // First reclaim
                    await dockService.ReclaimAsync(dock.Id);

                    Dock? afterFirst = await testDb.Driver.Docks.ReadAsync(dock.Id);
                    AssertNotNull(afterFirst, "Dock should still exist");
                    Assert(!afterFirst!.Active, "Dock should be inactive after first reclaim");

                    // Second reclaim — should be a no-op
                    await dockService.ReclaimAsync(dock.Id);

                    Dock? afterSecond = await testDb.Driver.Docks.ReadAsync(dock.Id);
                    AssertNotNull(afterSecond, "Dock should still exist after second reclaim");
                    Assert(!afterSecond!.Active, "Dock should still be inactive");
                }
            });

            await RunTest("Successful local landing removes active worktree before deleting branch", async () =>
            {
                using (TestDatabase testDb = await TestDatabaseHelper.CreateDatabaseAsync())
                {
                    StubGitService git = new StubGitService();
                    LoggingModule logging = CreateLogging();
                    ArmadaSettings settings = CreateSettings();
                    IDockService dockService = new DockService(logging, testDb.Driver, settings, git);
                    IMessageTemplateService templateService = new MessageTemplateService(logging);
                    MissionLandingHandler handler = new MissionLandingHandler(
                        logging,
                        testDb.Driver,
                        settings,
                        git,
                        new StubMergeQueueService(),
                        templateService,
                        null,
                        dockService,
                        null);

                    LandingTestEntitiesResult entities = await CreateTestEntitiesAsync(
                        testDb.Driver,
                        LandingModeEnum.LocalMerge,
                        BranchCleanupPolicyEnum.LocalAndRemote);

                    git.ExistingBranches.Add(entities.Dock.BranchName!);
                    entities.Mission.Status = MissionStatusEnum.WorkProduced;
                    entities.Mission.DiffSnapshot = "diff --git a/app/routes_ops.py b/app/routes_ops.py";
                    await testDb.Driver.Missions.UpdateAsync(entities.Mission).ConfigureAwait(false);

                    await handler.HandleMissionCompleteAsync(entities.Mission, entities.Dock).ConfigureAwait(false);

                    int removeIndex = git.OperationCalls.IndexOf("remove-worktree:" + entities.Dock.WorktreePath);
                    int deleteLocalIndex = git.OperationCalls.IndexOf("delete-local-branch:" + entities.Dock.BranchName);
                    int deleteRemoteIndex = git.OperationCalls.IndexOf("delete-remote-branch:" + entities.Dock.BranchName);

                    AssertTrue(removeIndex >= 0, "Landing cleanup should remove the active worktree");
                    AssertTrue(deleteLocalIndex > removeIndex, "Local branch deletion should happen after the worktree is removed");
                    AssertTrue(deleteRemoteIndex > deleteLocalIndex, "Remote branch deletion should happen after the local branch delete attempt");

                    Mission? updatedMission = await testDb.Driver.Missions.ReadAsync(entities.Mission.Id).ConfigureAwait(false);
                    AssertNotNull(updatedMission, "Mission should still exist");
                    AssertEqual(MissionStatusEnum.Complete, updatedMission!.Status, "Mission should be marked Complete after successful landing");
                }
            });

            // === Status Transition Validation ===

            await RunTest("PullRequestOpen allows transition to Complete", () =>
            {
                // Verify the enum values exist and are distinct
                Assert(MissionStatusEnum.PullRequestOpen != MissionStatusEnum.Complete, "PullRequestOpen is distinct from Complete");
                Assert(MissionStatusEnum.PullRequestOpen != MissionStatusEnum.WorkProduced, "PullRequestOpen is distinct from WorkProduced");
                return Task.CompletedTask;
            });

            await RunTest("All LandingMode enum values exist", () =>
            {
                string[] expected = new[] { "LocalMerge", "PullRequest", "MergeQueue", "None" };
                string[] actual = Enum.GetNames(typeof(LandingModeEnum));
                AssertEqual(expected.Length, actual.Length, "LandingMode enum value count");

                foreach (string name in expected)
                {
                    Assert(Enum.TryParse<LandingModeEnum>(name, out _), "Missing LandingMode value: " + name);
                }

                return Task.CompletedTask;
            });

            await RunTest("All BranchCleanupPolicy enum values exist", () =>
            {
                string[] expected = new[] { "LocalOnly", "LocalAndRemote", "None" };
                string[] actual = Enum.GetNames(typeof(BranchCleanupPolicyEnum));
                AssertEqual(expected.Length, actual.Length, "BranchCleanupPolicy enum value count");

                foreach (string name in expected)
                {
                    Assert(Enum.TryParse<BranchCleanupPolicyEnum>(name, out _), "Missing BranchCleanupPolicy value: " + name);
                }

                return Task.CompletedTask;
            });
        }

        private sealed class StubMergeQueueService : IMergeQueueService
        {
            public Task<MergeEntry> EnqueueAsync(MergeEntry entry, CancellationToken token = default) => Task.FromResult(entry);
            public Task ProcessQueueAsync(CancellationToken token = default) => Task.CompletedTask;
            public Task CancelAsync(string entryId, string? tenantId = null, CancellationToken token = default) => Task.CompletedTask;
            public Task<List<MergeEntry>> ListAsync(string? tenantId = null, CancellationToken token = default) => Task.FromResult(new List<MergeEntry>());
            public Task<MergeEntry?> ProcessSingleAsync(string entryId, string? tenantId = null, CancellationToken token = default) => Task.FromResult<MergeEntry?>(null);
            public Task<MergeEntry?> GetAsync(string entryId, string? tenantId = null, CancellationToken token = default) => Task.FromResult<MergeEntry?>(null);
            public Task<bool> DeleteAsync(string entryId, string? tenantId = null, CancellationToken token = default) => Task.FromResult(false);
            public Task<MergeQueuePurgeResult> DeleteMultipleAsync(List<string> entryIds, string? tenantId = null, CancellationToken token = default)
                => Task.FromResult(new MergeQueuePurgeResult());
            public Task<int> PurgeTerminalAsync(string? vesselId = null, MergeStatusEnum? status = null, string? tenantId = null, CancellationToken token = default)
                => Task.FromResult(0);
        }
    }
}
