// frontend/src/lib/models/__tests__/MappingsModel.test.ts
// #region MappingsModelTests [C:3] [TYPE Module] [SEMANTICS test,model,mapping]
// @BRIEF L1 unit tests for MappingsModel @INVARIANT guarantees — no DOM render.
// @RELATION BINDS_TO -> [MappingsModel]
// @TEST_INVARIANT: selectSourceEnv-resets-databases -> VERIFIED_BY: [test_selectSourceEnv_resets_databases, test_selectSourceEnv_resets_targetEnvId]
// @TEST_INVARIANT: selectTargetEnv-resets-databases -> VERIFIED_BY: [test_selectTargetEnv_resets_databases]
// @TEST_INVARIANT: fetchDatabases-loads-all-resources -> VERIFIED_BY: [test_fetchDatabases_loads_all_resources]
// @TEST_INVARIANT: saveMapping-adds-to-mappings -> VERIFIED_BY: [test_saveMapping_adds_to_mappings, test_saveMapping_replaces_existing_mapping]
import { describe, it, expect, vi, beforeEach } from "vitest";

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

// Mock environment context store
vi.mock("$lib/stores/environmentContext.svelte.js", () => ({
  environmentContextStore: {
    subscribe: vi.fn(),
    get value() {
      return { selectedEnvId: "env-1" };
    },
  },
}));

vi.mock('$lib/i18n/index.svelte.js', () => ({
  t: {
    migration: {
      mapping_saved: "Mapping saved",
    },
  },
}));

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

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

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

  // ═══════════════════════════════════════════════════════════════
  // @INVARIANT: Changing source environment resets databases, mappings, suggestions
  // ═══════════════════════════════════════════════════════════════
  describe("selectSourceEnv", () => {
    it("resets sourceDatabases, targetDatabases, mappings, and suggestions", () => {
      // Arrange: populate all database-related state
      model.sourceDatabases = [{ uuid: "db-1", database_name: "test" }];
      model.targetDatabases = [{ uuid: "db-2", database_name: "test" }];
      model.mappings = [{ id: 1 }];
      model.suggestions = [{ source_db_uuid: "db-1", target_db_uuid: "db-2" }];
      model.sourceEnvId = "env-1";

      // Act: select a different source environment
      model.selectSourceEnv("env-2");

      // Assert: all database-related state is reset
      expect(model.sourceDatabases).toEqual([]);
      expect(model.targetDatabases).toEqual([]);
      expect(model.mappings).toEqual([]);
      expect(model.suggestions).toEqual([]);
    });

    // @INVARIANT: Changing source environment resets targetEnvId
    it("resets targetEnvId", () => {
      // Arrange
      model.sourceEnvId = "env-1";
      model.targetEnvId = "env-2";

      // Act
      model.selectSourceEnv("env-3");

      // Assert
      expect(model.targetEnvId).toBe("");
    });

    it("resets error and success messages", () => {
      // Arrange
      model.error = "Something went wrong";
      model.success = "Mapping saved";
      model.sourceEnvId = "env-1";

      // Act
      model.selectSourceEnv("env-2");

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

    it("does NOT reset if same env is selected again", () => {
      // Arrange
      model.sourceEnvId = "env-1";
      model.targetEnvId = "env-2";
      model.sourceDatabases = [{ uuid: "db-1", database_name: "test" }];
      model.mappings = [{ id: 1 }];

      // Act
      model.selectSourceEnv("env-1");

      // Assert: state unchanged
      expect(model.sourceEnvId).toBe("env-1");
      expect(model.targetEnvId).toBe("env-2");
      expect(model.sourceDatabases).toHaveLength(1);
      expect(model.mappings).toHaveLength(1);
    });
  });

  // ═══════════════════════════════════════════════════════════════
  // @INVARIANT: Changing target environment resets databases, mappings, suggestions
  // ═══════════════════════════════════════════════════════════════
  describe("selectTargetEnv", () => {
    it("resets targetDatabases, mappings, and suggestions", () => {
      // Arrange
      model.targetDatabases = [{ uuid: "tgt-1", database_name: "test" }];
      model.mappings = [{ id: "m-1" }];
      model.suggestions = [{ source_db_uuid: "src-1", target_db_uuid: "tgt-1" }];
      model.targetEnvId = "env-2";

      // Act
      model.selectTargetEnv("env-3");

      // Assert
      expect(model.targetDatabases).toEqual([]);
      expect(model.mappings).toEqual([]);
      expect(model.suggestions).toEqual([]);
    });

    it("does NOT reset sourceDatabases or sourceEnvId", () => {
      // Arrange
      model.sourceEnvId = "env-1";
      model.sourceDatabases = [{ uuid: "src-1", database_name: "test" }];

      // Act
      model.selectTargetEnv("env-3");

      // Assert: source-side state is preserved
      expect(model.sourceEnvId).toBe("env-1");
      expect(model.sourceDatabases).toHaveLength(1);
    });

    it("resets error and success messages", () => {
      // Arrange
      model.error = "Fail";
      model.success = "Done";

      // Act
      model.selectTargetEnv("env-3");

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

    it("does NOT reset if same env is selected again", () => {
      // Arrange
      model.targetEnvId = "env-2";
      model.targetDatabases = [{ uuid: "tgt-1", database_name: "test" }];
      model.mappings = [{ id: "m-1" }];

      // Act
      model.selectTargetEnv("env-2");

      // Assert: state unchanged
      expect(model.targetEnvId).toBe("env-2");
      expect(model.targetDatabases).toHaveLength(1);
      expect(model.mappings).toHaveLength(1);
    });
  });

  // ═══════════════════════════════════════════════════════════════
  // @INVARIANT: fetchDatabases loads all resources
  // ═══════════════════════════════════════════════════════════════
  describe("fetchDatabases", () => {
    const srcDbs: Array<{ uuid: string; database_name: string }> = [{ uuid: "src-1", database_name: "SalesDB" }];
    const tgtDbs: Array<{ uuid: string; database_name: string }> = [{ uuid: "tgt-1", database_name: "SalesDW" }];
    const mappings: Array<{ id: string; source_db_uuid: string; target_db_uuid: string }> = [
      { id: "m-1", source_db_uuid: "src-1", target_db_uuid: "tgt-1" },
    ];
    const suggestions: Array<{ source_db_uuid: string; target_db_uuid: string; confidence: number }> = [
      { source_db_uuid: "src-1", target_db_uuid: "tgt-2", confidence: 0.85 },
    ];

    it("returns early if sourceEnvId is empty", async () => {
      model.sourceEnvId = "";
      model.targetEnvId = "env-2";
      await model.fetchDatabases();
      expect(api.requestApi).not.toHaveBeenCalled();
      expect(model.fetchingDbs).toBe(false);
    });

    it("returns early if targetEnvId is empty", async () => {
      model.sourceEnvId = "env-1";
      model.targetEnvId = "";
      await model.fetchDatabases();
      expect(api.requestApi).not.toHaveBeenCalled();
    });

    it("loads source and target databases, mappings, and suggestions", async () => {
      // Arrange
      model.sourceEnvId = "env-1";
      model.targetEnvId = "env-2";
      vi.mocked(api.requestApi)
        .mockResolvedValueOnce(srcDbs)   // source databases
        .mockResolvedValueOnce(tgtDbs)   // target databases
        .mockResolvedValueOnce(mappings); // existing mappings
      vi.mocked(api.postApi).mockResolvedValueOnce(suggestions); // AI suggestions

      // Act
      await model.fetchDatabases();

      // Assert: correct API endpoints called
      expect(api.requestApi).toHaveBeenCalledWith("/environments/env-1/databases");
      expect(api.requestApi).toHaveBeenCalledWith("/environments/env-2/databases");
      expect(api.requestApi).toHaveBeenCalledWith(
        "/mappings?source_env_id=env-1&target_env_id=env-2",
      );
      expect(api.postApi).toHaveBeenCalledWith("/mappings/suggest", {
        source_env_id: "env-1",
        target_env_id: "env-2",
      });

      // Assert: state populated correctly
      expect(model.sourceDatabases).toEqual(srcDbs);
      expect(model.targetDatabases).toEqual(tgtDbs);
      expect(model.mappings).toEqual(mappings);
      expect(model.suggestions).toEqual(suggestions);
    });

    it("sets fetchingDbs true during fetch and false after", async () => {
      // Arrange
      model.sourceEnvId = "env-1";
      model.targetEnvId = "env-2";
      vi.mocked(api.requestApi).mockResolvedValue([]);
      vi.mocked(api.postApi).mockResolvedValue([]);

      // Act
      const promise = model.fetchDatabases();
      expect(model.fetchingDbs).toBe(true);
      await promise;
      expect(model.fetchingDbs).toBe(false);
    });

    it("sets error on API failure and clears fetchingDbs", async () => {
      // Arrange
      model.sourceEnvId = "env-1";
      model.targetEnvId = "env-2";
      vi.mocked(api.requestApi).mockRejectedValue(new Error("Network failure"));

      // Act
      await model.fetchDatabases();

      // Assert
      expect(model.error).toBe("Network failure");
      expect(model.fetchingDbs).toBe(false);
    });
  });

  // ═══════════════════════════════════════════════════════════════
  // @INVARIANT: saveMapping adds to mappings array
  // ═══════════════════════════════════════════════════════════════
  describe("saveMapping", () => {
    const srcDb = { uuid: "src-1", database_name: "SalesDB" };
    const tgtDb = { uuid: "tgt-1", database_name: "SalesDW" };

    it("returns early if source database is not found", async () => {
      model.sourceDatabases = [];
      model.targetDatabases = [tgtDb];
      await model.saveMapping("src-1", "tgt-1");
      expect(api.postApi).not.toHaveBeenCalled();
    });

    it("returns early if target database is not found", async () => {
      model.sourceDatabases = [srcDb];
      model.targetDatabases = [];
      await model.saveMapping("src-1", "tgt-1");
      expect(api.postApi).not.toHaveBeenCalled();
    });

    it("saves mapping and adds to mappings array", async () => {
      // Arrange
      model.sourceEnvId = "env-1";
      model.targetEnvId = "env-2";
      model.sourceDatabases = [srcDb];
      model.targetDatabases = [tgtDb];
      const savedMapping = {
        id: "m-1",
        source_db_uuid: "src-1",
        target_db_uuid: "tgt-1",
        source_db_name: "SalesDB",
        target_db_name: "SalesDW",
      };
      vi.mocked(api.postApi).mockResolvedValueOnce(savedMapping);

      // Act
      await model.saveMapping("src-1", "tgt-1");

      // Assert: correct API call
      expect(api.postApi).toHaveBeenCalledWith("/mappings", {
        source_env_id: "env-1",
        target_env_id: "env-2",
        source_db_uuid: "src-1",
        target_db_uuid: "tgt-1",
        source_db_name: "SalesDB",
        target_db_name: "SalesDW",
      });

      // Assert: mapping added
      expect(model.mappings).toHaveLength(1);
      expect(model.mappings[0]).toEqual(savedMapping);
    });

    // @INVARIANT: saveMapping replaces existing mapping for same source
    it("replaces existing mapping for the same source database UUID", async () => {
      // Arrange: existing mapping for src-1 → tgt-1
      model.sourceEnvId = "env-1";
      model.targetEnvId = "env-2";
      model.sourceDatabases = [srcDb, { uuid: "src-2", database_name: "AnalyticsDB" }];
      model.targetDatabases = [tgtDb, { uuid: "tgt-2", database_name: "AnalyticsDW" }];
      model.mappings = [
        { id: "old-m", source_db_uuid: "src-1", target_db_uuid: "tgt-1" },
      ];

      // New mapping for src-1 → tgt-2
      const newMapping = {
        id: "new-m",
        source_db_uuid: "src-1",
        target_db_uuid: "tgt-2",
        source_db_name: "SalesDB",
        target_db_name: "AnalyticsDW",
      };
      vi.mocked(api.postApi).mockResolvedValueOnce(newMapping);

      // Act
      await model.saveMapping("src-1", "tgt-2");

      // Assert: old mapping replaced by new
      expect(model.mappings).toHaveLength(1);
      expect(model.mappings[0]).toEqual(newMapping);
      expect(model.mappings[0].target_db_uuid).toBe("tgt-2");
    });

    it("sets success message on successful save", async () => {
      // Arrange
      model.sourceDatabases = [srcDb];
      model.targetDatabases = [tgtDb];
      vi.mocked(api.postApi).mockResolvedValueOnce({
        id: "m-1",
        source_db_uuid: "src-1",
        target_db_uuid: "tgt-1",
      } satisfies Record<string, unknown>);

      // Act
      await model.saveMapping("src-1", "tgt-1");

      // Assert
      expect(model.success).toBe("Mapping saved successfully");
      expect(model.error).toBe("");
    });

    it("sets error message on API failure", async () => {
      // Arrange
      model.sourceDatabases = [srcDb];
      model.targetDatabases = [tgtDb];
      vi.mocked(api.postApi).mockRejectedValueOnce(new Error("Save failed"));

      // Act
      await model.saveMapping("src-1", "tgt-1");

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

  // ═══════════════════════════════════════════════════════════════
  // @INVARIANT: loadEnvironments pre-fills source from context
  // ═══════════════════════════════════════════════════════════════
  describe("loadEnvironments", () => {
    it("sets loading true then false after fetch", async () => {
      vi.mocked(api.getEnvironmentsList).mockResolvedValueOnce([]);
      const promise = model.loadEnvironments();
      expect(model.loading).toBe(true);
      await promise;
      expect(model.loading).toBe(false);
    });

    it("pre-fills sourceEnvId from active context", async () => {
      vi.mocked(api.getEnvironmentsList).mockResolvedValueOnce([
        { id: "env-1", name: "Dev" },
        { id: "env-2", name: "Prod" },
      ]);
      // get() returns { selectedEnvId: "env-1" } from mock
      await model.loadEnvironments();
      expect(model.sourceEnvId).toBe("env-1");
    });

    it("sets error on API failure", async () => {
      vi.mocked(api.getEnvironmentsList).mockRejectedValueOnce(new Error("Failed to load"));
      await model.loadEnvironments();
      expect(model.error).toBe("Failed to load");
      expect(model.loading).toBe(false);
    });
  });
});
// #endregion MappingsModelTests
