import { Test, TestingModule } from '@nestjs/testing';
import { ScheduleNotFoundError } from '@temporalio/client';
import { TemporalService } from './temporal.service';

/**
 * Mock the @temporalio/client module so we can intercept schedule / workflow ops
 * without needing a running Temporal Server.
 */
const mockHandle = {
  describe: jest.fn(),
  update: jest.fn(),
  pause: jest.fn(),
  unpause: jest.fn(),
  delete: jest.fn(),
};
const mockClient = {
  schedule: {
    getHandle: jest.fn(() => mockHandle),
    create: jest.fn(),
  },
  workflow: {
    start: jest.fn(),
    getHandle: jest.fn(() => ({ cancel: jest.fn() })),
  },
};

jest.mock('@temporalio/client', () => {
  class FakeScheduleNotFoundError extends Error {
    constructor(msg = 'not found') {
      super(msg);
      this.name = 'ScheduleNotFoundError';
    }
  }
  return {
    Client: jest.fn(() => mockClient),
    Connection: { connect: jest.fn(async () => ({ close: jest.fn() })) },
    ScheduleNotFoundError: FakeScheduleNotFoundError,
  };
});

describe('TemporalService — cron schedule methods', () => {
  let service: TemporalService;

  beforeEach(async () => {
    jest.clearAllMocks();
    const module: TestingModule = await Test.createTestingModule({
      providers: [TemporalService],
    }).compile();
    service = module.get<TemporalService>(TemporalService);
    await service.onModuleInit();
  });

  describe('upsertCronSchedule', () => {
    it('creates a new schedule when none exists', async () => {
      mockHandle.describe.mockRejectedValueOnce(
        new ScheduleNotFoundError('nf', 'wf-x-cron'),
      );

      await service.upsertCronSchedule({
        workflowId: 42,
        versionId: 7,
        dsl: { nodes: [], edges: [] },
        cron: '0 * * * *',
        timezone: 'Asia/Shanghai',
      });

      expect(mockClient.schedule.create).toHaveBeenCalledTimes(1);
      const arg = mockClient.schedule.create.mock.calls[0][0];
      expect(arg.scheduleId).toBe('wf-42-cron');
      expect(arg.spec.cronExpressions).toEqual(['0 * * * *']);
      expect(arg.spec.timezoneName).toBe('Asia/Shanghai');
      expect(arg.action.workflowType).toBe('cronTriggerWorkflow');
      expect(arg.action.args[0]).toMatchObject({
        workflowId: 42,
        versionId: 7,
        scheduleId: 'wf-42-cron',
      });
    });

    it('updates an existing schedule', async () => {
      mockHandle.describe.mockResolvedValueOnce({});

      await service.upsertCronSchedule({
        workflowId: 42,
        versionId: 8,
        dsl: { nodes: [], edges: [] },
        cron: '*/5 * * * *',
      });

      expect(mockClient.schedule.create).not.toHaveBeenCalled();
      expect(mockHandle.update).toHaveBeenCalledTimes(1);
    });

    it('omits timezoneName when timezone is not provided', async () => {
      mockHandle.describe.mockRejectedValueOnce(
        new ScheduleNotFoundError('nf', 'wf-x-cron'),
      );
      await service.upsertCronSchedule({
        workflowId: 1,
        versionId: 1,
        dsl: {},
        cron: '0 0 * * *',
      });
      const spec = mockClient.schedule.create.mock.calls[0][0].spec;
      expect(spec.timezoneName).toBeUndefined();
    });
  });

  describe('pauseCronSchedule', () => {
    it('calls handle.pause for existing schedule', async () => {
      mockHandle.pause.mockResolvedValueOnce(undefined);
      await service.pauseCronSchedule(42);
      expect(mockHandle.pause).toHaveBeenCalled();
    });

    it('is a no-op when schedule does not exist', async () => {
      mockHandle.pause.mockRejectedValueOnce(
        new ScheduleNotFoundError('nf', 'wf-x-cron'),
      );
      await expect(service.pauseCronSchedule(42)).resolves.toBeUndefined();
    });

    it('rethrows non-NotFound errors', async () => {
      mockHandle.pause.mockRejectedValueOnce(new Error('boom'));
      await expect(service.pauseCronSchedule(42)).rejects.toThrow('boom');
    });
  });

  describe('unpauseCronSchedule', () => {
    it('calls handle.unpause', async () => {
      mockHandle.unpause.mockResolvedValueOnce(undefined);
      await service.unpauseCronSchedule(42);
      expect(mockHandle.unpause).toHaveBeenCalled();
    });

    it('swallows ScheduleNotFoundError', async () => {
      mockHandle.unpause.mockRejectedValueOnce(
        new ScheduleNotFoundError('nf', 'wf-x-cron'),
      );
      await expect(service.unpauseCronSchedule(42)).resolves.toBeUndefined();
    });
  });

  describe('deleteCronSchedule', () => {
    it('calls handle.delete', async () => {
      mockHandle.delete.mockResolvedValueOnce(undefined);
      await service.deleteCronSchedule(42);
      expect(mockHandle.delete).toHaveBeenCalled();
    });

    it('swallows ScheduleNotFoundError', async () => {
      mockHandle.delete.mockRejectedValueOnce(
        new ScheduleNotFoundError('nf', 'wf-x-cron'),
      );
      await expect(service.deleteCronSchedule(42)).resolves.toBeUndefined();
    });
  });
});
