// frontend/src/lib/models/__tests__/MigrationSettingsModel.test.ts
// #region MigrationSettingsModelTests [C:3] [TYPE Module] [SEMANTICS test,model,migration-settings]
// @BRIEF L1 unit tests for MigrationSettingsModel @INVARIANT guarantees — no DOM render.
// @RELATION BINDS_TO -> [MigrationSettingsModel]
// @TEST_INVARIANT: loadSettings-sets-cron -> VERIFIED_BY: [test_loadSettings_sets_cron_from_api]
// @TEST_INVARIANT: loadSettings-handles-error -> VERIFIED_BY: [test_loadSettings_handles_error]
// @TEST_INVARIANT: saveSettings-sends-put -> VERIFIED_BY: [test_saveSettings_sends_put_request]
// @TEST_INVARIANT: triggerSyncNow-sends-post -> VERIFIED_BY: [test_triggerSyncNow_sends_post_request]
// @TEST_INVARIANT: loading-flag-lifecycle -> VERIFIED_BY: [test_saveSettings_loading_flag_lifecycle, test_triggerSyncNow_syncing_flag_lifecycle]
import { describe, it, expect, vi, beforeEach } from "vitest";

// Mock the API module before importing
vi.mock("$lib/api.js", () => ({
  api: {
    requestApi: vi.fn(),
    postApi: vi.fn(),
  },
}));

import { MigrationSettingsModel } from "../MigrationSettingsModel.svelte.ts";
import { api } from "$lib/api.js";

describe("MigrationSettingsModel — L1 invariants (no render)", () => {
  let model: MigrationSettingsModel;

  beforeEach(() => {
    vi.clearAllMocks();
    model = new MigrationSettingsModel();
  });

  // ═══════════════════════════════════════════════════════════════
  // @INVARIANT: loadSettings fetches GET /migration/settings
  // ═══════════════════════════════════════════════════════════════
  describe("loadSettings", () => {
    it("sets cron from API response", async () => {
      // Arrange
      vi.mocked(api.requestApi).mockResolvedValueOnce({ cron: "0 4 * * *" });

      // Act
      await model.loadSettings();

      // Assert
      expect(api.requestApi).toHaveBeenCalledWith("/migration/settings");
      expect(model.cron).toBe("0 4 * * *");
    });

    it("handles error without throwing", async () => {
      // Arrange
      vi.mocked(api.requestApi).mockRejectedValueOnce(new Error("Network error"));

      // Act
      await model.loadSettings();

      // Assert: cron stays at default, error is set
      expect(model.cron).toBe("0 2 * * *");
      expect(model.error).toBe("Network error");
    });
  });

  // ═══════════════════════════════════════════════════════════════
  // @INVARIANT: saveSettings sends PUT /migration/settings
  // ═══════════════════════════════════════════════════════════════
  describe("saveSettings", () => {
    it("sends PUT request with correct data", async () => {
      // Arrange
      vi.mocked(api.requestApi).mockResolvedValueOnce({ ok: true });

      // Act
      await model.saveSettings({ cron: "0 6 * * *" });

      // Assert
      expect(api.requestApi).toHaveBeenCalledWith(
        "/migration/settings",
        "PUT",
        { cron: "0 6 * * *" },
      );
      expect(model.success).toBe("Settings saved successfully");
    });

    it("handles error and sets error message", async () => {
      // Arrange
      vi.mocked(api.requestApi).mockRejectedValueOnce(new Error("Save failed"));

      // Act
      await model.saveSettings({ cron: "bad-cron" });

      // Assert
      expect(model.error).toBe("Save failed");
      expect(model.success).toBe("");
    });
  });

  // ═══════════════════════════════════════════════════════════════
  // @INVARIANT: triggerSyncNow sends POST /migration/sync-now
  // ═══════════════════════════════════════════════════════════════
  describe("triggerSyncNow", () => {
    it("sends POST request and reloads settings", async () => {
      // Arrange
      vi.mocked(api.postApi).mockResolvedValueOnce({
        synced_count: 2,
        failed_count: 0,
      });
      vi.mocked(api.requestApi).mockResolvedValueOnce({ cron: "0 2 * * *" });

      // Act
      await model.triggerSyncNow();

      // Assert
      expect(api.postApi).toHaveBeenCalledWith("/migration/sync-now", {});
      expect(model.refreshKey).toBe(1);
      expect(model.cron).toBe("0 2 * * *");
      expect(model.success).toContain("Synced 2 environment(s)");
    });

    it("handles sync failure with error message", async () => {
      // Arrange
      vi.mocked(api.postApi).mockRejectedValueOnce(new Error("Sync timeout"));

      // Act
      await model.triggerSyncNow();

      // Assert
      expect(model.error).toBe("Sync timeout");
      expect(model.refreshKey).toBe(0); // not incremented
    });

    it("includes failed count in success message", async () => {
      // Arrange
      vi.mocked(api.postApi).mockResolvedValueOnce({
        synced_count: 3,
        failed_count: 1,
      });
      vi.mocked(api.requestApi).mockResolvedValueOnce({ cron: "0 2 * * *" });

      // Act
      await model.triggerSyncNow();

      // Assert
      expect(model.success).toContain("Synced 3 environment(s)");
      expect(model.success).toContain("1 failed");
    });
  });

  // ═══════════════════════════════════════════════════════════════
  // @INVARIANT: loading flag lifecycle (saving/syncing)
  // ═══════════════════════════════════════════════════════════════
  describe("loading flag lifecycle", () => {
    it("sets saving true during save and false after", async () => {
      // Arrange
      vi.mocked(api.requestApi).mockImplementation(
        () => new Promise((resolve) => setTimeout(resolve, 50)),
      );

      // Act
      const promise = model.saveSettings({ cron: "0 2 * * *" });
      expect(model.saving).toBe(true);
      await promise;
      expect(model.saving).toBe(false);
    });

    it("sets syncing true during sync and false after", async () => {
      // Arrange
      let resolveSync: ((value: unknown) => void) | undefined;
      vi.mocked(api.postApi).mockImplementation(
        () =>
          new Promise((resolve) => {
            resolveSync = resolve;
          }),
      );
      vi.mocked(api.requestApi).mockResolvedValue({ cron: "0 2 * * *" });

      // Act
      const promise = model.triggerSyncNow();
      expect(model.syncing).toBe(true);
      resolveSync!({ synced_count: 1, failed_count: 0 });
      await promise;
      expect(model.syncing).toBe(false);
    });

    it("sets syncing false on sync failure", async () => {
      // Arrange
      vi.mocked(api.postApi).mockRejectedValueOnce(new Error("Fail"));

      // Act
      await model.triggerSyncNow();

      // Assert
      expect(model.syncing).toBe(false);
    });

    it("sets saving false on save failure", async () => {
      // Arrange
      vi.mocked(api.requestApi).mockRejectedValueOnce(new Error("Fail"));

      // Act
      await model.saveSettings({ cron: "bad" });

      // Assert
      expect(model.saving).toBe(false);
    });
  });
});
// #endregion MigrationSettingsModelTests
