import { describe, expect, it } from "vitest";
import { createMockDb } from "../../../testing/index";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  CountNotFoundError,
  CountNotInProgressError,
  CountLineNotFoundError,
  CountLineInvalidStatusError,
} from "../lib/errors.generated";
import {
  baseInProgressCount,
  basePlannedCount,
  basePendingCountLine,
  baseApprovedCountLine,
} from "../testing/fixtures";
import { run } from "./recordCountLine";
import type { CommandContext } from "@tailor-platform/erp-kit/module";

describe("recordCountLine", () => {
  const ctx: CommandContext = {
    actorId: "test-actor",
    permissions: ["inventory:recordCountLine"],
  };

  it("returns error when count not found", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(undefined); // count not found

    const result = await run(
      db,
      {
        inventoryCountId: "nonexistent",
        countLineId: "count-line-1",
        countedQuantity: 98,
      },
      ctx,
    );
    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(CountNotFoundError);
    }
  });

  it("returns error when count not in progress", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(basePlannedCount); // count is PLANNED

    const result = await run(
      db,
      {
        inventoryCountId: "count-1",
        countLineId: "count-line-1",
        countedQuantity: 98,
      },
      ctx,
    );
    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(CountNotInProgressError);
    }
  });

  it("returns error when line not pending", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseInProgressCount) // count lookup
      .mockReturnValueOnce(baseApprovedCountLine); // line is APPROVED

    const result = await run(
      db,
      {
        inventoryCountId: "count-2",
        countLineId: "count-line-2",
        countedQuantity: 50,
      },
      ctx,
    );
    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(CountLineInvalidStatusError);
    }
  });

  it("returns error when count line not found", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseInProgressCount) // count lookup
      .mockReturnValueOnce(undefined); // count line not found

    const result = await run(
      db,
      {
        inventoryCountId: "count-2",
        countLineId: "nonexistent",
        countedQuantity: 98,
      },
      ctx,
    );
    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(CountLineNotFoundError);
    }
  });

  it("records counted quantity", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const updatedLine = {
      ...basePendingCountLine,
      countedQuantity: 95,
      variance: -5,
      status: "PENDING",
    };

    spies.select
      .mockReturnValueOnce(baseInProgressCount) // count lookup
      .mockReturnValueOnce(basePendingCountLine); // line lookup
    spies.update.mockReturnValue(updatedLine);

    const result = await run(
      db,
      {
        inventoryCountId: "count-2",
        countLineId: "count-line-1",
        countedQuantity: 95,
      },
      ctx,
    );
    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.countLine.countedQuantity).toBe(95);
    }
    expect(spies.update).toHaveBeenCalled();
  });

  it("calculates variance correctly", async () => {
    const { db, spies } = createMockDb<Transaction>();
    // systemQuantity is 100, countedQuantity is 105, variance should be 5
    const updatedLine = {
      ...basePendingCountLine,
      countedQuantity: 105,
      variance: 5,
      status: "PENDING",
    };

    spies.select.mockReturnValueOnce(baseInProgressCount).mockReturnValueOnce(basePendingCountLine); // systemQuantity = 100
    spies.update.mockReturnValue(updatedLine);

    const result = await run(
      db,
      {
        inventoryCountId: "count-2",
        countLineId: "count-line-1",
        countedQuantity: 105,
      },
      ctx,
    );
    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.countLine.variance).toBe(5);
    }
    expect(spies.set).toHaveBeenCalledWith(
      expect.objectContaining({ variance: 5, countedQuantity: 105 }),
    );
  });

  it("allows recording for RECOUNT status lines", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const recountLine = { ...basePendingCountLine, status: "RECOUNT" };
    const updatedLine = { ...recountLine, countedQuantity: 99, variance: -1, status: "PENDING" };

    spies.select.mockReturnValueOnce(baseInProgressCount).mockReturnValueOnce(recountLine);
    spies.update.mockReturnValue(updatedLine);

    const result = await run(
      db,
      {
        inventoryCountId: "count-2",
        countLineId: "count-line-1",
        countedQuantity: 99,
      },
      ctx,
    );
    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.countLine.status).toBe("PENDING");
    }
  });

  it("allows recording for REJECTED status lines", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const rejectedLine = { ...basePendingCountLine, status: "REJECTED" };
    const updatedLine = { ...rejectedLine, countedQuantity: 101, variance: 1, status: "PENDING" };

    spies.select.mockReturnValueOnce(baseInProgressCount).mockReturnValueOnce(rejectedLine);
    spies.update.mockReturnValue(updatedLine);

    const result = await run(
      db,
      {
        inventoryCountId: "count-2",
        countLineId: "count-line-1",
        countedQuantity: 101,
      },
      ctx,
    );
    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.countLine.status).toBe("PENDING");
    }
  });
});
