using System.Collections.Generic;
using Dalamud.Bindings.ImGui;
using Dalamud.Plugin.SelfTest;
using HellionChat.Code;
using HellionChat.Ui;

namespace HellionChat.SelfTests;

// Drives the FirstRunWizard state machine through every step and
// commits a no-op pending state (Variant 1), then re-runs picking
// Roleplay on Step 2 and skipping Step 3 (Variant 2). Verifies
// that the staged-commit path does not throw under any combination
// of Pending* values and that CommitPending leaves Config in a
// readable shape. Variant 2's Roleplay commit would normally
// mutate the six PrivacyFilter / Retention fields ApplyRoleplay
// touches, so the step snapshots them before Variant 2 runs and
// CleanUp() restores them — the self-test stays idempotent across
// repeated /xlperf runs and does not overwrite an active privacy
// profile.
internal sealed class WizardStateSmokeStep : ISelfTestStep
{
    private readonly Plugin plugin;

    // Snapshot slots for the six Configuration fields ApplyRoleplay
    // writes in Variant 2. Populated right before Variant 2 mutates
    // Config, consumed by CleanUp(). Reference-typed snapshots
    // (HashSet, Dictionary) capture the existing slot by reference,
    // which is safe because ApplyRoleplay reassigns the slot with
    // a fresh instance instead of mutating in place.
    private bool? snapshotPrivacyFilterEnabled;
    private HashSet<ChatType>? snapshotPrivacyPersistChannels;
    private bool? snapshotPrivacyPersistUnknownChannels;
    private bool? snapshotRetentionEnabled;
    private int? snapshotRetentionDefaultDays;
    private Dictionary<ChatType, int>? snapshotRetentionPerChannelDays;

    public WizardStateSmokeStep(Plugin plugin)
    {
        this.plugin = plugin;
    }

    public string Name => "Hellion Chat - FirstRunWizard state smoke";

    public SelfTestStepResult RunStep()
    {
        var wizard = this.plugin.FirstRunWizard;
        if (wizard is null)
        {
            ImGui.Text("Plugin.FirstRunWizard is null");
            return SelfTestStepResult.Fail;
        }

        try
        {
            // Variant 1: no-op CommitPending. Walks the state machine and
            // verifies the empty-pending write-back path does not throw.
            wizard.TestOnly_AdvanceTo(1);
            wizard.TestOnly_AdvanceTo(2);
            wizard.TestOnly_AdvanceTo(3);
            wizard.TestOnly_AdvanceTo(4);
            wizard.CommitPending();

            // Variant 2: skip Step 3 explicitly. Picks Roleplay on Step 2,
            // jumps straight to Step 4 (no Step-3 entry → no seed for
            // LoadPreviousSession / FilterIncludePreviousSessions), commits,
            // and asserts the two coupled history toggles remained on their
            // pre-test value. Pins the null-semantics from Spec Z.176 so a
            // regression in CommitPending that started writing seeded
            // recommendations unconditionally would surface here.
            // CommitPending → ApplyRoleplay overwrites six privacy /
            // retention fields, so snapshot them first and let CleanUp
            // restore them after the assert. Keeps /xlperf idempotent.
            this.snapshotPrivacyFilterEnabled = Plugin.Config.PrivacyFilterEnabled;
            this.snapshotPrivacyPersistChannels = Plugin.Config.PrivacyPersistChannels;
            this.snapshotPrivacyPersistUnknownChannels = Plugin
                .Config
                .PrivacyPersistUnknownChannels;
            this.snapshotRetentionEnabled = Plugin.Config.RetentionEnabled;
            this.snapshotRetentionDefaultDays = Plugin.Config.RetentionDefaultDays;
            this.snapshotRetentionPerChannelDays = Plugin.Config.RetentionPerChannelDays;

            var loadPrevBefore = Plugin.Config.LoadPreviousSession;
            var filterPrevBefore = Plugin.Config.FilterIncludePreviousSessions;
            wizard.TestOnly_AdvanceTo(2);
            wizard.TestOnly_SetPendingProfile(FirstRunWizard.PrivacyProfile.Roleplay);
            wizard.TestOnly_AdvanceTo(4);
            wizard.CommitPending();
            if (Plugin.Config.LoadPreviousSession != loadPrevBefore)
            {
                ImGui.Text("Skip-Step-3 path overwrote LoadPreviousSession");
                return SelfTestStepResult.Fail;
            }
            if (Plugin.Config.FilterIncludePreviousSessions != filterPrevBefore)
            {
                ImGui.Text("Skip-Step-3 path overwrote FilterIncludePreviousSessions");
                return SelfTestStepResult.Fail;
            }
        }
        catch (Exception ex)
        {
            ImGui.Text($"Wizard state smoke threw: {ex.GetType().Name}: {ex.Message}");
            return SelfTestStepResult.Fail;
        }

        return SelfTestStepResult.Pass;
    }

    public void CleanUp()
    {
        // Restore the six Variant-2 snapshots so back-to-back /xlperf
        // runs don't drift the active privacy profile. If Variant 2
        // never ran (Variant 1 threw early), the slots stay null and
        // restore is a no-op. After restore the slots are nulled so a
        // future RunStep starts fresh.
        if (this.snapshotPrivacyFilterEnabled is { } privacyFilter)
            Plugin.Config.PrivacyFilterEnabled = privacyFilter;
        if (this.snapshotPrivacyPersistChannels is { } persistChannels)
            Plugin.Config.PrivacyPersistChannels = persistChannels;
        if (this.snapshotPrivacyPersistUnknownChannels is { } persistUnknown)
            Plugin.Config.PrivacyPersistUnknownChannels = persistUnknown;
        if (this.snapshotRetentionEnabled is { } retentionEnabled)
            Plugin.Config.RetentionEnabled = retentionEnabled;
        if (this.snapshotRetentionDefaultDays is { } retentionDays)
            Plugin.Config.RetentionDefaultDays = retentionDays;
        if (this.snapshotRetentionPerChannelDays is { } retentionPolicy)
            Plugin.Config.RetentionPerChannelDays = retentionPolicy;

        this.snapshotPrivacyFilterEnabled = null;
        this.snapshotPrivacyPersistChannels = null;
        this.snapshotPrivacyPersistUnknownChannels = null;
        this.snapshotRetentionEnabled = null;
        this.snapshotRetentionDefaultDays = null;
        this.snapshotRetentionPerChannelDays = null;
    }
}
