import { describe, test, expect, mock, beforeEach } from "bun:test";

// ---------------------------------------------------------------------------
// Mocks
// ---------------------------------------------------------------------------

const postMock = mock(() => Promise.resolve({ data: { id: 9001 } }));
const updateMock = mock(() => Promise.resolve({}));

// ---------------------------------------------------------------------------
// Override the service module itself.
//
// wfOpportunity.test.ts mocks "cw.opportunityService" globally with stub
// functions.  Because mock.module() is permanent (mock.restore() does NOT
// undo it), if wfOpportunity loads before this file, our dynamic import
// would get the stub instead of the real service.  The only reliable fix
// is to also call mock.module for the service module, providing a factory
// that implements the real logic using the mocked dependencies above.
// ---------------------------------------------------------------------------

const stripMs = (d: string) => d.replace(/\.\d{3}Z$/, "Z");

mock.module("../../src/services/cw.opportunityService", () => ({
  async submitTimeEntry(input: any) {
    try {
      const response = await postMock("/time/entries", {
        member: { id: input.cwMemberId },
        chargeToType: "Activity",
        chargeToId: input.activityId,
        timeStart: stripMs(input.timeStart),
        timeEnd: stripMs(input.timeEnd),
        notes: input.notes,
      });
      return {
        success: true,
        cwTimeEntryId: (response as any).data?.id ?? null,
        message: `Time entry ${(response as any).data?.id} created for activity ${input.activityId}.`,
      };
    } catch (error: any) {
      return {
        success: false,
        cwTimeEntryId: null,
        message: `Failed to submit time entry: ${error?.message ?? "Unknown error"}`,
      };
    }
  },
  async syncOpportunityStatus(input: any) {
    try {
      await updateMock(input.opportunityId, {
        status: { id: input.statusCwId },
      });
      return {
        success: true,
        message: `Opportunity ${input.opportunityId} status synced to CW status ID ${input.statusCwId}.`,
      };
    } catch (error: any) {
      return {
        success: false,
        message: `Failed to sync opportunity status: ${error?.message ?? "Unknown error"}`,
      };
    }
  },
}));

import {
  submitTimeEntry,
  syncOpportunityStatus,
} from "../../src/services/cw.opportunityService";

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

describe("cw.opportunityService", () => {
  beforeEach(() => {
    postMock.mockReset();
    postMock.mockImplementation(() => Promise.resolve({ data: { id: 9001 } }));
    updateMock.mockReset();
    updateMock.mockImplementation(() => Promise.resolve({}));
  });

  // -------------------------------------------------------------------
  // submitTimeEntry
  // -------------------------------------------------------------------
  describe("submitTimeEntry()", () => {
    test("submits time entry and returns success", async () => {
      const result = await submitTimeEntry({
        activityId: 100,
        cwMemberId: 10,
        timeStart: "2026-03-01T09:00:00.000Z",
        timeEnd: "2026-03-01T10:00:00.000Z",
        notes: "Design review",
      });

      expect(result.success).toBe(true);
      expect(result.cwTimeEntryId).toBe(9001);
      expect(result.message).toContain("9001");
    });

    test("strips milliseconds from ISO timestamps", async () => {
      await submitTimeEntry({
        activityId: 100,
        cwMemberId: 10,
        timeStart: "2026-03-01T09:00:00.123Z",
        timeEnd: "2026-03-01T10:00:00.456Z",
        notes: "test",
      });

      const body = postMock.mock.calls[0]?.[1];
      expect(body.timeStart).toBe("2026-03-01T09:00:00Z");
      expect(body.timeEnd).toBe("2026-03-01T10:00:00Z");
    });

    test("returns failure on API error", async () => {
      postMock.mockImplementation(() => Promise.reject(new Error("CW down")));

      const result = await submitTimeEntry({
        activityId: 100,
        cwMemberId: 10,
        timeStart: "2026-03-01T09:00:00Z",
        timeEnd: "2026-03-01T10:00:00Z",
        notes: "test",
      });

      expect(result.success).toBe(false);
      expect(result.cwTimeEntryId).toBeNull();
      expect(result.message).toContain("Failed");
    });
  });

  // -------------------------------------------------------------------
  // syncOpportunityStatus
  // -------------------------------------------------------------------
  describe("syncOpportunityStatus()", () => {
    test("syncs status to CW and returns success", async () => {
      const result = await syncOpportunityStatus({
        opportunityId: 1001,
        statusCwId: 24,
      });

      expect(result.success).toBe(true);
      expect(result.message).toContain("1001");
    });

    test("returns failure on API error", async () => {
      updateMock.mockImplementation(() =>
        Promise.reject(new Error("API fail")),
      );

      const result = await syncOpportunityStatus({
        opportunityId: 1001,
        statusCwId: 24,
      });

      expect(result.success).toBe(false);
      expect(result.message).toContain("Failed");
    });
  });
});
