import type { CommandContext } from "@tailor-platform/erp-kit/core";
import { createKyselyMock } from "@tailor-platform/sdk/vitest";
import { describe, expect, it } from "vitest";
import type { Namespace } from "../generated/kysely-tailordb";
import {
  WorkOrderNotFoundError,
  WorkOrderNotReportableError,
  InvalidReportedQuantityError,
  EmptyProgressTransactionError,
  ScrapHandoffRequiredError,
} from "../lib/errors.generated";
import { baseInProgressWorkOrder, basePendingWorkOrder } from "../testing/fixtures";
import { run } from "./reportWorkOrderProgress";

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

  it("records partial completed quantity and time on an in-progress work order", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const updatedWorkOrder = {
      ...baseInProgressWorkOrder,
      completedQuantity: baseInProgressWorkOrder.completedQuantity + 10,
      actualRunTime: baseInProgressWorkOrder.actualRunTime + 30,
    };

    mock.enqueueResults([baseInProgressWorkOrder], [updatedWorkOrder], []);

    const result = await mock.withTx((trx) =>
      run(trx, { id: baseInProgressWorkOrder.id, completedQuantity: 10, actualRunTime: 30 }, ctx),
    );

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.workOrder.completedQuantity).toBe(
        baseInProgressWorkOrder.completedQuantity + 10,
      );
      expect(result.value.workOrder.actualRunTime).toBe(baseInProgressWorkOrder.actualRunTime + 30);
    }
    expect(mock.updates.length).toBeGreaterThan(0);
    expect(mock.inserts.length).toBeGreaterThan(0);
  });

  it("returns error when the work order does not exist", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([]);

    const result = await mock.withTx((trx) =>
      run(trx, { id: "nonexistent", completedQuantity: 5 }, ctx),
    );

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(WorkOrderNotFoundError);
    }
  });

  it("returns error when the work order is not in progress", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([basePendingWorkOrder]);

    const result = await mock.withTx((trx) =>
      run(trx, { id: basePendingWorkOrder.id, completedQuantity: 5 }, ctx),
    );

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(WorkOrderNotReportableError);
    }
  });

  it("returns error when completed or scrap quantity is negative", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();

    const result = await mock.withTx((trx) =>
      run(trx, { id: baseInProgressWorkOrder.id, completedQuantity: -5 }, ctx),
    );

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(InvalidReportedQuantityError);
    }
  });

  it("returns error when no positive quantity or time is provided", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();

    const result = await mock.withTx((trx) =>
      run(trx, { id: baseInProgressWorkOrder.id, completedQuantity: 0, scrapQuantity: 0 }, ctx),
    );

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(EmptyProgressTransactionError);
    }
  });

  it("emits manufacturing scrap handoff when positive scrap is reported", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const updatedWorkOrder = {
      ...baseInProgressWorkOrder,
      scrapQuantity: baseInProgressWorkOrder.scrapQuantity + 3,
      completedQuantity: baseInProgressWorkOrder.completedQuantity + 10,
    };

    mock.enqueueResults([baseInProgressWorkOrder], [updatedWorkOrder], []);

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          id: baseInProgressWorkOrder.id,
          completedQuantity: 10,
          scrapQuantity: 3,
          scrapHandoffData: { reason: "defective material" },
        },
        ctx,
      ),
    );

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.workOrder.scrapQuantity).toBe(baseInProgressWorkOrder.scrapQuantity + 3);
    }
  });

  it("returns error when positive scrap is reported without scrap handoff data", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();

    mock.enqueueResults([baseInProgressWorkOrder]);

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          id: baseInProgressWorkOrder.id,
          completedQuantity: 10,
          scrapQuantity: 3,
        },
        ctx,
      ),
    );

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(ScrapHandoffRequiredError);
    }
  });

  it("accumulates completed quantity, setup time, and run time on the work order", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const updatedWorkOrder = {
      ...baseInProgressWorkOrder,
      completedQuantity: baseInProgressWorkOrder.completedQuantity + 20,
      actualSetupTime: baseInProgressWorkOrder.actualSetupTime + 5,
      actualRunTime: baseInProgressWorkOrder.actualRunTime + 45,
    };

    mock.enqueueResults([baseInProgressWorkOrder], [updatedWorkOrder], []);

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          id: baseInProgressWorkOrder.id,
          completedQuantity: 20,
          actualSetupTime: 5,
          actualRunTime: 45,
        },
        ctx,
      ),
    );

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.workOrder.completedQuantity).toBe(
        baseInProgressWorkOrder.completedQuantity + 20,
      );
      expect(result.value.workOrder.actualSetupTime).toBe(
        baseInProgressWorkOrder.actualSetupTime + 5,
      );
      expect(result.value.workOrder.actualRunTime).toBe(baseInProgressWorkOrder.actualRunTime + 45);
    }
    expect(mock.updates).toHaveLength(1);
    expect(mock.selects).toHaveLength(1);
  });
});
