// #region TranslateHistoryModelTests [C:3] [TYPE Module]
// @BRIEF L1 unit tests for TranslateHistoryModel — no DOM render.
// @RELATION BINDS_TO -> [TranslateHistoryModel]
// @TEST_INVARIANT: filter-resets-pagination -> VERIFIED_BY: [test_applyFilters_resets_page]
// @TEST_INVARIANT: detail-panel-gate -> VERIFIED_BY: [test_openDetail_closeDetail]
import { describe, it, expect, vi, beforeEach } from "vitest";
vi.mock("$lib/toasts.svelte.js", () => ({ addToast: vi.fn() }));
vi.mock("$lib/i18n/index.svelte.js", () => ({ getT: () => ({}), _: vi.fn((k) => k) }));
vi.mock("$lib/api/translate.js", () => ({
  fetchAllRuns: vi.fn().mockResolvedValue([]),
  fetchRunDetail: vi.fn(),
  fetchAllMetrics: vi.fn(),
  fetchJobs: vi.fn(),
  downloadSkippedCsv: vi.fn(),
  downloadFailedCsv: vi.fn(),
  cancelRun: vi.fn(),
  retryFailedBatches: vi.fn(),
}));
import { TranslateHistoryModel } from "../TranslateHistoryModel.svelte.ts";
import { addToast } from "$lib/toasts.svelte.js";
import {
  fetchAllRuns, fetchRunDetail, fetchAllMetrics, fetchJobs,
  downloadSkippedCsv, downloadFailedCsv, cancelRun, retryFailedBatches,
} from "$lib/api/translate.js";

// #region TranslateHistoryModel.InvariantTests [C:2] [TYPE Function]
describe("TranslateHistoryModel — L1 invariants", () => {
  let model: TranslateHistoryModel;
  beforeEach(() => { vi.clearAllMocks(); model = new TranslateHistoryModel(); });

  // #region TranslateHistoryModel.InitialState [C:2] [TYPE Test]
  it("starts in idle state", () => {
    expect(model.uxState).toBe("idle");
    expect(model.runs).toEqual([]);
  });
  // #endregion

  // #region TranslateHistoryModel.ApplyFilters [C:2] [TYPE Test]
  it("applyFilters resets currentPage to 1", () => {
    model.currentPage = 3;
    model.applyFilters();
    expect(model.currentPage).toBe(1);
  });
  // #endregion

  // #region TranslateHistoryModel.DetailPanel [C:2] [TYPE Test]
  it("openDetail sets selectedRun and loads detail", async () => {
    vi.mocked(fetchRunDetail).mockResolvedValue({ id: "r1", metrics: {} });
    const run = { id: "r1", status: "completed" };
    await model.openDetail(run);
    expect(model.selectedRun).toEqual(run);
    expect(model.uxState).toBe("detail_open");
  });

  it("openDetail handles API error", async () => {
    vi.mocked(fetchRunDetail).mockRejectedValue(new Error("Detail failed"));
    await model.openDetail({ id: "r1" });
    expect(addToast).toHaveBeenCalled();
  });

  it("closeDetail clears selectedRun and detail", () => {
    model.selectedRun = { id: "r1" } as any;
    model.selectedRunDetail = { metrics: {} } as any;
    model.closeDetail();
    expect(model.selectedRun).toBeNull();
    expect(model.selectedRunDetail).toBeNull();
    expect(model.uxState).toBe("empty");
  });

  it("closeDetail restores populated uxState when runs exist", () => {
    model.selectedRun = { id: "r1" } as any;
    model.selectedRunDetail = { metrics: {} } as any;
    model.runs = [{ id: "r1" }] as any;
    model.closeDetail();
    expect(model.uxState).toBe("populated");
  });
  // #endregion
});
// #endregion

// #region TranslateHistoryModel.DataLoading [C:2] [TYPE Function]
describe("TranslateHistoryModel — Data Loading", () => {
  let model: TranslateHistoryModel;
  beforeEach(() => { vi.clearAllMocks(); model = new TranslateHistoryModel(); });

  // #region TranslateHistoryModel.LoadRuns [C:2] [TYPE Test]
  it("loadRuns sets loading state and populates runs", async () => {
    vi.mocked(fetchAllRuns).mockResolvedValue({ items: [{ id: "r1" }, { id: "r2" }], total: 2 });
    const promise = model.loadRuns();
    expect(model.isLoading).toBe(true);
    expect(model.uxState).toBe("loading");
    await promise;
    expect(model.runs).toHaveLength(2);
    expect(model.total).toBe(2);
    expect(model.uxState).toBe("populated");
    expect(model.isLoading).toBe(false);
  });

  it("loadRuns sets empty state when no results", async () => {
    vi.mocked(fetchAllRuns).mockResolvedValue({ items: [], total: 0 });
    await model.loadRuns();
    expect(model.uxState).toBe("empty");
  });

  it("loadRuns handles API error", async () => {
    vi.mocked(fetchAllRuns).mockRejectedValue(new Error("Load failed"));
    await model.loadRuns();
    expect(model.uxState).toBe("empty");
    expect(addToast).toHaveBeenCalled();
    expect(model.isLoading).toBe(false);
  });
  // #endregion

  // #region TranslateHistoryModel.LoadMetrics [C:2] [TYPE Test]
  it("loadMetrics populates metrics array", async () => {
    vi.mocked(fetchAllMetrics).mockResolvedValue([{ total_runs: 10, successful_runs: 8, failed_runs: 2, total_records: 100 }]);
    await model.loadMetrics();
    expect(model.metrics).toHaveLength(1);
  });

  it("loadMetrics silently ignores error", async () => {
    vi.mocked(fetchAllMetrics).mockRejectedValue(new Error("Metrics down"));
    await model.loadMetrics();
    expect(model.metrics).toEqual([]);
  });
  // #endregion

  // #region TranslateHistoryModel.LoadJobs [C:2] [TYPE Test]
  it("loadJobs accepts array result", async () => {
    vi.mocked(fetchJobs).mockResolvedValue([{ id: "j1", name: "Job 1" }]);
    await model.loadJobs();
    expect(model.jobs).toHaveLength(1);
  });

  it("loadJobs accepts items format result", async () => {
    vi.mocked(fetchJobs).mockResolvedValue({ items: [{ id: "j1", name: "Job 1" }] });
    await model.loadJobs();
    expect(model.jobs).toHaveLength(1);
  });

  it("loadJobs accepts results format result", async () => {
    vi.mocked(fetchJobs).mockResolvedValue({ results: [{ id: "j1", name: "Job 1" }] });
    await model.loadJobs();
    expect(model.jobs).toHaveLength(1);
  });

  it("loadJobs silently ignores error", async () => {
    vi.mocked(fetchJobs).mockRejectedValue(new Error("Jobs down"));
    await model.loadJobs();
    expect(model.jobs).toEqual([]);
  });

  it("loadJobs handles response without items or results field", async () => {
    vi.mocked(fetchJobs).mockResolvedValue({ other_field: "no items" });
    await model.loadJobs();
    expect(model.jobs).toEqual([]);
  });
  // #endregion

  // #region TranslateHistoryModel.NonErrorExceptions [C:2] [TYPE Test]
  it("loadRuns handles non-Error rejections", async () => {
    vi.mocked(fetchAllRuns).mockRejectedValue("string error");
    await model.loadRuns();
    expect(model.uxState).toBe("empty");
    expect(model.isLoading).toBe(false);
  });

  it("loadMetrics handles non-Error rejections", async () => {
    vi.mocked(fetchAllMetrics).mockRejectedValue("metrics error");
    await model.loadMetrics();
    expect(model.metrics).toEqual([]);
  });

  it("loadJobs handles non-Error rejections", async () => {
    vi.mocked(fetchJobs).mockRejectedValue("jobs error");
    await model.loadJobs();
    expect(model.jobs).toEqual([]);
  });
  // #endregion
});
// #endregion

// #region TranslateHistoryModel.RunActions [C:2] [TYPE Function]
describe("TranslateHistoryModel — Run Actions", () => {
  let model: TranslateHistoryModel;
  beforeEach(() => { vi.clearAllMocks(); model = new TranslateHistoryModel(); });

  // #region TranslateHistoryModel.CancelRun [C:2] [TYPE Test]
  it("handleCancelRun calls cancel API and refreshes runs", async () => {
    vi.mocked(cancelRun).mockResolvedValue({});
    vi.mocked(fetchAllRuns).mockResolvedValue({ items: [], total: 0 });
    await model.handleCancelRun("r1");
    expect(cancelRun).toHaveBeenCalledWith("r1");
    expect(model.cancellingRunId).toBeNull();
  });

  it("handleCancelRun handles error", async () => {
    vi.mocked(cancelRun).mockRejectedValue(new Error("Cancel failed"));
    await model.handleCancelRun("r1");
    expect(addToast).toHaveBeenCalled();
    expect(model.cancellingRunId).toBeNull();
  });

  it("handleCancelRun refreshes selectedRunDetail if matches", async () => {
    vi.mocked(cancelRun).mockResolvedValue({});
    vi.mocked(fetchRunDetail).mockResolvedValue({ id: "r1", status: "cancelled" });
    vi.mocked(fetchAllRuns).mockResolvedValue({ items: [], total: 0 });
    model.selectedRunDetail = { id: "r1" } as any;
    await model.handleCancelRun("r1");
    expect(fetchRunDetail).toHaveBeenCalled();
  });
  // #endregion

  // #region TranslateHistoryModel.RetryRun [C:2] [TYPE Test]
  it("handleRetryRun calls retry API and refreshes runs", async () => {
    vi.mocked(retryFailedBatches).mockResolvedValue({});
    vi.mocked(fetchAllRuns).mockResolvedValue({ items: [], total: 0 });
    await model.handleRetryRun("r1");
    expect(retryFailedBatches).toHaveBeenCalledWith("r1");
    expect(model.retryingRunId).toBeNull();
  });

  it("handleRetryRun handles error", async () => {
    vi.mocked(retryFailedBatches).mockRejectedValue(new Error("Retry failed"));
    await model.handleRetryRun("r1");
    expect(addToast).toHaveBeenCalled();
    expect(model.retryingRunId).toBeNull();
  });
  // #endregion

  // #region TranslateHistoryModel.Downloads [C:2] [TYPE Test]
  it("handleDownloadSkipped calls API", async () => {
    vi.mocked(downloadSkippedCsv).mockResolvedValue(undefined);
    await model.handleDownloadSkipped("r1");
    expect(downloadSkippedCsv).toHaveBeenCalledWith("r1");
  });

  it("handleDownloadSkipped handles error", async () => {
    vi.mocked(downloadSkippedCsv).mockRejectedValue(new Error("Download failed"));
    await model.handleDownloadSkipped("r1");
    expect(addToast).toHaveBeenCalled();
  });

  it("handleDownloadSkipped handles non-Error rejection", async () => {
    vi.mocked(downloadSkippedCsv).mockRejectedValue("string error");
    await model.handleDownloadSkipped("r1");
    expect(addToast).toHaveBeenCalled();
  });

  it("handleDownloadFailed calls API", async () => {
    vi.mocked(downloadFailedCsv).mockResolvedValue(undefined);
    await model.handleDownloadFailed("r1");
    expect(downloadFailedCsv).toHaveBeenCalledWith("r1");
  });

  it("handleDownloadFailed handles error", async () => {
    vi.mocked(downloadFailedCsv).mockRejectedValue(new Error("Download failed"));
    await model.handleDownloadFailed("r1");
    expect(addToast).toHaveBeenCalled();
  });

  it("handleDownloadFailed handles non-Error rejection", async () => {
    vi.mocked(downloadFailedCsv).mockRejectedValue("string error");
    await model.handleDownloadFailed("r1");
    expect(addToast).toHaveBeenCalled();
  });

  it("handleCancelRun handles non-Error rejection", async () => {
    vi.mocked(cancelRun).mockRejectedValue("cancel string error");
    await model.handleCancelRun("r1");
    expect(addToast).toHaveBeenCalled();
    expect(model.cancellingRunId).toBeNull();
  });

  it("handleRetryRun handles non-Error rejection", async () => {
    vi.mocked(retryFailedBatches).mockRejectedValue("retry string error");
    await model.handleRetryRun("r1");
    expect(addToast).toHaveBeenCalled();
    expect(model.retryingRunId).toBeNull();
  });

  it("openDetail handles non-Error rejection", async () => {
    vi.mocked(fetchRunDetail).mockRejectedValue("string error");
    await model.openDetail({ id: "r1" });
    expect(addToast).toHaveBeenCalled();
  });
  // #endregion
});
// #endregion

// #region TranslateHistoryModel.Utilities [C:2] [TYPE Function]
describe("TranslateHistoryModel — Utilities", () => {
  let model: TranslateHistoryModel;
  beforeEach(() => { vi.clearAllMocks(); model = new TranslateHistoryModel(); });

  // #region TranslateHistoryModel.GetStatusClass [C:2] [TYPE Test]
  it("getStatusClass returns correct classes for PENDING", () => {
    expect(model.getStatusClass("PENDING")).toContain("warning");
  });

  it("getStatusClass returns correct classes for COMPLETED", () => {
    expect(model.getStatusClass("COMPLETED")).toContain("success");
  });

  it("getStatusClass returns correct classes for FAILED", () => {
    expect(model.getStatusClass("FAILED")).toContain("destructive");
  });

  it("getStatusClass returns muted for unknown status", () => {
    expect(model.getStatusClass("UNKNOWN")).toContain("muted");
  });
  // #endregion

  // #region TranslateHistoryModel.GetJobName [C:2] [TYPE Test]
  it("getJobName returns job name when found", () => {
    model.jobs = [{ id: "j1", name: "My Job" }] as any;
    expect(model.getJobName("j1")).toBe("My Job");
  });

  it("getJobName returns truncated id when not found", () => {
    model.jobs = [];
    expect(model.getJobName("abcdef123456")).toBe("abcdef12...");
  });

  it("getJobName returns default for undefined", () => {
    const name = model.getJobName(undefined);
    expect(name).toContain("...");
  });
  // #endregion

  // #region TranslateHistoryModel.TotalMetric [C:2] [TYPE Test]
  it("totalMetric returns null when no metrics", () => {
    expect(model.totalMetric).toBeNull();
  });

  it("totalMetric aggregates metrics", () => {
    model.metrics = [
      { total_runs: 10, successful_runs: 8, failed_runs: 2, total_records: 100 },
      { total_runs: 5, successful_runs: 4, failed_runs: 1, total_records: 50 },
    ] as any;
    expect(model.totalMetric.total_runs).toBe(15);
    expect(model.totalMetric.successful_runs).toBe(12);
    expect(model.totalMetric.failed_runs).toBe(3);
    expect(model.totalMetric.total_records).toBe(150);
  });
  // #endregion

  // #region TranslateHistoryModel.StatusClassEdge [C:2] [TYPE Test]
  it("getStatusClass returns default for undefined status", () => {
    expect(model.getStatusClass(undefined)).toContain("muted");
  });

  it("getStatusClass returns default for empty string status", () => {
    expect(model.getStatusClass("")).toContain("muted");
  });
  // #endregion

  // #region TranslateHistoryModel.JobNameEdge [C:2] [TYPE Test]
  it("getJobName returns String(undefined) when job has no name", () => {
    model.jobs = [{ id: "j1" }] as any;
    // job is found but name is undefined => String(undefined) => "undefined"
    expect(model.getJobName("j1")).toBe("undefined");
  });

  it("getJobName returns empty for undefined job id", () => {
    expect(model.getJobName(undefined)).toBe("...");
  });
  // #endregion

  // #region TranslateHistoryModel.RunDetailNonMatch [C:2] [TYPE Test]
  it("handleCancelRun does not refresh detail when detail id differs", async () => {
    vi.mocked(cancelRun).mockResolvedValue({});
    vi.mocked(fetchAllRuns).mockResolvedValue({ items: [], total: 0 });
    model.selectedRunDetail = { id: "other-run" } as any;
    await model.handleCancelRun("r1");
    // fetchRunDetail should NOT be called because selectedRunDetail.id !== runId
    expect(fetchRunDetail).not.toHaveBeenCalled();
  });

  it("handleRetryRun does not refresh detail when detail id differs", async () => {
    vi.mocked(retryFailedBatches).mockResolvedValue({});
    vi.mocked(fetchAllRuns).mockResolvedValue({ items: [], total: 0 });
    model.selectedRunDetail = { id: "other-run" } as any;
    await model.handleRetryRun("r1");
    expect(fetchRunDetail).not.toHaveBeenCalled();
  });
  // #endregion
});
// #endregion
