import { describe, expect, it } from "vitest";

import {
  initialPollState,
  isTerminal,
  narrowJob,
  reducePoll,
  styleLearnResult,
  type JobView,
} from "./job";

describe("narrowJob", () => {
  it("narrows a well-formed loose dict into JobView", () => {
    const job = narrowJob({
      id: "j1",
      kind: "style_learn",
      status: "running",
      progress: 42,
      result: null,
      error: null,
    });
    expect(job).toEqual({
      id: "j1",
      kind: "style_learn",
      status: "running",
      progress: 42,
      result: null,
      error: null,
    });
  });

  it("falls back safely for missing/wrong-typed fields", () => {
    expect(narrowJob({})).toEqual({
      id: "",
      kind: "",
      status: "queued",
      progress: 0,
      result: null,
      error: null,
    });
    // unknown status → queued; out-of-range progress clamped.
    expect(narrowJob({ status: "weird", progress: 250 }).status).toBe("queued");
    expect(narrowJob({ progress: 250 }).progress).toBe(100);
    expect(narrowJob({ progress: -5 }).progress).toBe(0);
  });

  it("keeps result only when an object (not array)", () => {
    expect(narrowJob({ result: { version: 2 } }).result).toEqual({ version: 2 });
    expect(narrowJob({ result: [1, 2] }).result).toBeNull();
  });

  it("narrows non-object input", () => {
    expect(narrowJob(null).status).toBe("queued");
    expect(narrowJob("nope").id).toBe("");
  });
});

describe("isTerminal", () => {
  it("done and failed are terminal", () => {
    expect(isTerminal("done")).toBe(true);
    expect(isTerminal("failed")).toBe(true);
    expect(isTerminal("queued")).toBe(false);
    expect(isTerminal("running")).toBe(false);
  });
});

describe("reducePoll", () => {
  const job = (over: Partial<JobView>): JobView => ({
    id: "j1",
    kind: "style_learn",
    status: "running",
    progress: 0,
    result: null,
    error: null,
    ...over,
  });

  it("stays polling for queued/running and tracks progress", () => {
    const out = reducePoll(initialPollState, {
      type: "tick",
      job: job({ status: "running", progress: 30 }),
    });
    expect(out.status).toBe("polling");
    expect(out.progress).toBe(30);
  });

  it("transitions to done with progress 100", () => {
    const out = reducePoll(initialPollState, {
      type: "tick",
      job: job({ status: "done", result: { version: 2, dims_count: 16 } }),
    });
    expect(out.status).toBe("done");
    expect(out.progress).toBe(100);
    expect(out.job?.result).toEqual({ version: 2, dims_count: 16 });
  });

  it("transitions to error on failed, using job.error", () => {
    const out = reducePoll(initialPollState, {
      type: "tick",
      job: job({ status: "failed", error: "网关爆炸", progress: 20 }),
    });
    expect(out.status).toBe("error");
    expect(out.error).toBe("网关爆炸");
    expect(out.progress).toBe(20);
  });

  it("defaults error text when failed without error string", () => {
    const out = reducePoll(initialPollState, {
      type: "tick",
      job: job({ status: "failed", error: null }),
    });
    expect(out.error).toBe("任务失败");
  });

  it("handles a fail action (poll request failure)", () => {
    const out = reducePoll(initialPollState, {
      type: "fail",
      error: "网络断了",
    });
    expect(out.status).toBe("error");
    expect(out.error).toBe("网络断了");
  });
});

describe("styleLearnResult", () => {
  it("extracts version + dims_count from done result", () => {
    const out = styleLearnResult({
      id: "j",
      kind: "style_learn",
      status: "done",
      progress: 100,
      result: { version: 3, dims_count: 16 },
      error: null,
    });
    expect(out).toEqual({ version: 3, dimsCount: 16 });
  });

  it("returns nulls for missing result fields", () => {
    expect(
      styleLearnResult({
        id: "j",
        kind: "style_learn",
        status: "done",
        progress: 100,
        result: null,
        error: null,
      }),
    ).toEqual({ version: null, dimsCount: null });
  });
});
