import {Issue} from '../src/classes/issue';
import {IIssuesProcessorOptions} from '../src/interfaces/issues-processor-options';
import {IssuesProcessorMock} from './classes/issues-processor-mock';
import {DefaultProcessorOptions} from './constants/default-processor-options';
import {generateIssue} from './functions/generate-issue';
import {alwaysFalseStateMock} from './classes/state-mock';
import {IState} from '../src/interfaces/state/state';
import {IIssueEvent} from '../src/interfaces/issue-event';
import {IssuesProcessor} from '../src/classes/issues-processor';

describe('remove-stale-when-updated with stale label events', (): void => {
  const markedStaleOn = '2025-01-01T00:00:00Z';
  const updatedAt = '2025-01-01T00:01:00Z';

  let options: IIssuesProcessorOptions;

  beforeEach((): void => {
    options = {
      ...DefaultProcessorOptions,
      removeStaleWhenUpdated: true
    };
  });

  const buildIssue = (): Issue =>
    generateIssue(
      options,
      1,
      'dummy-title',
      updatedAt,
      markedStaleOn,
      false,
      false,
      ['Stale']
    );

  const buildEvents = (): IIssueEvent[] => [
    {
      event: 'labeled',
      created_at: markedStaleOn,
      label: {name: 'Stale'}
    }
  ];

  test('does not remove stale label when only stale label events occurred', async (): Promise<void> => {
    expect.assertions(1);
    const issue = buildIssue();

    const processor = new IssuesProcessorMock(
      options,
      alwaysFalseStateMock,
      async p => (p === 1 ? [issue] : []),
      async () => [],
      async () => ({creationDate: markedStaleOn, events: buildEvents()}),
      async () => true
    );

    await processor.processIssues();

    expect(processor.removedLabelIssues).toHaveLength(0);
  });

  test('removes stale label when updates are not just stale label events', async (): Promise<void> => {
    expect.assertions(1);
    const issue = buildIssue();

    const processor = new IssuesProcessorMock(
      options,
      alwaysFalseStateMock,
      async p => (p === 1 ? [issue] : []),
      async () => [],
      async () => ({creationDate: markedStaleOn, events: buildEvents()}),
      async () => false
    );

    await processor.processIssues();

    expect(processor.removedLabelIssues).toHaveLength(1);
  });
});

class TestIssuesProcessor extends IssuesProcessor {
  constructor(
    options: IIssuesProcessorOptions,
    state: IState,
    events: IIssueEvent[]
  ) {
    super(options, state);
    const client = {
      rest: {
        issues: {
          listEvents: {
            endpoint: {
              merge: () => ({})
            }
          }
        }
      },
      paginate: {
        iterator: async function* () {
          yield {data: events};
        }
      }
    };
    (this as any).client = client;
  }

  async callhasOnlyStaleLabelingEventsSince(
    issue: Issue,
    sinceDate: string,
    staleLabel: string,
    events: IIssueEvent[]
  ): Promise<boolean> {
    return this.hasOnlyStaleLabelingEventsSince(
      issue,
      sinceDate,
      staleLabel,
      events
    );
  }
}

describe('hasOnlyStaleLabelingEventsSince', (): void => {
  const staleLabel = 'Stale';
  const sinceDate = '2025-01-01T00:00:00Z';
  const originalRepo = process.env.GITHUB_REPOSITORY;

  let options: IIssuesProcessorOptions;

  beforeEach((): void => {
    process.env.GITHUB_REPOSITORY = 'owner/repo';
    options = {
      ...DefaultProcessorOptions,
      staleIssueLabel: staleLabel,
      removeStaleWhenUpdated: true
    };
  });

  afterEach((): void => {
    if (originalRepo === undefined) {
      delete process.env.GITHUB_REPOSITORY;
    } else {
      process.env.GITHUB_REPOSITORY = originalRepo;
    }
  });

  const buildIssue = (): Issue =>
    generateIssue(
      options,
      1,
      'dummy-title',
      '2025-01-01T00:02:00Z',
      sinceDate,
      false,
      false,
      [staleLabel]
    );

  test('returns true when only stale label events exist after the since date', async (): Promise<void> => {
    expect.assertions(1);
    const issue = buildIssue();
    const events: IIssueEvent[] = [
      // Event before the sinceDate should be ignored.
      {
        event: 'labeled',
        created_at: '2024-12-31T23:59:00Z',
        label: {name: staleLabel}
      },
      {
        event: 'labeled',
        created_at: '2025-01-01T00:00:10Z',
        label: {name: staleLabel}
      }
    ];
    const processor = new TestIssuesProcessor(
      options,
      alwaysFalseStateMock,
      events
    );
    const result = await processor.callhasOnlyStaleLabelingEventsSince(
      issue,
      sinceDate,
      staleLabel,
      events
    );

    expect(result).toBe(true);
  });

  test('returns false when a non-stale label event exists after the since date', async (): Promise<void> => {
    expect.assertions(1);
    const issue = buildIssue();
    const events: IIssueEvent[] = [
      {
        event: 'labeled',
        created_at: '2025-01-01T00:00:10Z',
        label: {name: 'other-label'}
      }
    ];
    const processor = new TestIssuesProcessor(
      options,
      alwaysFalseStateMock,
      events
    );
    const result = await processor.callhasOnlyStaleLabelingEventsSince(
      issue,
      sinceDate,
      staleLabel,
      events
    );

    expect(result).toBe(false);
  });

  test('returns false when stale label is removed after the since date', async (): Promise<void> => {
    expect.assertions(1);
    const issue = buildIssue();
    const events: IIssueEvent[] = [
      {
        event: 'unlabeled',
        created_at: '2025-01-01T00:00:10Z',
        label: {name: staleLabel}
      }
    ];
    const processor = new TestIssuesProcessor(
      options,
      alwaysFalseStateMock,
      events
    );
    const result = await processor.callhasOnlyStaleLabelingEventsSince(
      issue,
      sinceDate,
      staleLabel,
      events
    );

    expect(result).toBe(false);
  });

  test('returns false when a non-label event exists after the since date', async (): Promise<void> => {
    expect.assertions(1);
    const issue = buildIssue();
    const events: IIssueEvent[] = [
      {
        event: 'commented',
        created_at: '2025-01-01T00:00:10Z',
        label: {name: staleLabel}
      }
    ];
    const processor = new TestIssuesProcessor(
      options,
      alwaysFalseStateMock,
      events
    );
    const result = await processor.callhasOnlyStaleLabelingEventsSince(
      issue,
      sinceDate,
      staleLabel,
      events
    );

    expect(result).toBe(false);
  });

  test('includes events that occur exactly at the since date boundary', async (): Promise<void> => {
    expect.assertions(1);
    const issue = buildIssue();
    const events: IIssueEvent[] = [
      {
        event: 'labeled',
        created_at: sinceDate,
        label: {name: staleLabel}
      }
    ];
    const processor = new TestIssuesProcessor(
      options,
      alwaysFalseStateMock,
      events
    );
    const result = await processor.callhasOnlyStaleLabelingEventsSince(
      issue,
      sinceDate,
      staleLabel,
      events
    );

    expect(result).toBe(true);
  });
});
