// Copyright 2024 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only

import createDebug from 'debug';
import { assert } from 'chai';
import { expect } from 'playwright/test';
import {
  type PrimaryDevice,
  type Proto,
  StorageState,
} from '@signalapp/mock-server';
import * as path from 'node:path';
import type { App } from '../playwright.node.ts';
import { Bootstrap } from '../bootstrap.node.ts';
import {
  getMessageInTimelineByTimestamp,
  getTimelineMessageWithText,
  sendMessageWithAttachments,
  sendTextMessage,
} from '../helpers.node.ts';
import * as durations from '../../util/durations/index.std.ts';
import { strictAssert } from '../../util/assert.std.ts';

const debug = createDebug('mock:test:lightbox');

describe('lightbox', function (this: Mocha.Suite) {
  this.timeout(durations.MINUTE);

  let bootstrap: Bootstrap;
  let app: App;
  let pinned: PrimaryDevice;

  beforeEach(async () => {
    bootstrap = new Bootstrap();
    await bootstrap.init();

    let state = StorageState.getEmpty();

    const { phone, contacts } = bootstrap;
    [pinned] = contacts as [PrimaryDevice];

    state = state.addContact(pinned, {
      identityKey: pinned.publicKey.serialize(),
      profileKey: pinned.profileKey.serialize(),
      whitelisted: true,
    });

    state = state.pin(pinned);
    await phone.setStorageState(state);

    app = await bootstrap.link();
  });

  afterEach(async function (this: Mocha.Context) {
    if (!bootstrap) {
      return;
    }

    await bootstrap.maybeSaveLogs(this.currentTest, app);
    await app.close();
    await bootstrap.teardown();
  });

  it('can page through different messages in the same conversation', async () => {
    const page = await app.getWindow();

    await page.getByTestId(pinned.device.aci).click();

    async function sendAttachmentsBack(
      text: string,
      attachments: Array<Proto.AttachmentPointer.Params>
    ) {
      debug(`replying with ${attachments.length} attachments`);
      const timestamp = bootstrap.getTimestamp();
      await sendTextMessage({
        from: pinned,
        to: bootstrap.desktop,
        desktop: bootstrap.desktop,
        text,
        attachments,
        timestamp,
      });
      debug('wait for message to appear in timeline');
      const Message = getMessageInTimelineByTimestamp(page, timestamp);
      const MessageImageLoaded = Message.locator('.module-image--loaded');

      await Message.waitFor();

      await Promise.all(
        attachments.map(async (_, index) => {
          debug(`waiting for ${index} image to render in timeline`);
          await MessageImageLoaded.nth(index).waitFor({
            state: 'visible',
          });
        })
      );
    }

    const fixturesDir = path.join(__dirname, '..', '..', '..', 'fixtures');
    const imageCat = path.join(fixturesDir, 'cat-screenshot.png');
    const imageSnow = path.join(fixturesDir, 'snow.jpg');
    const imageWaterfall = path.join(
      fixturesDir,
      'koushik-chowdavarapu-105425-unsplash.jpg'
    );

    const {
      attachments: [attachmentCat],
    } = await sendMessageWithAttachments(page, pinned, 'Message1', [imageCat]);
    const {
      attachments: [attachmentSnow, attachmentWaterfall],
    } = await sendMessageWithAttachments(page, pinned, 'Message2', [
      imageSnow,
      imageWaterfall,
    ]);

    strictAssert(attachmentCat, 'attachmentCat exists');
    strictAssert(attachmentSnow, 'attachmentSnow exists');
    strictAssert(attachmentWaterfall, 'attachmentWaterfall exists');

    await sendAttachmentsBack('Message3', [attachmentCat]);
    await sendAttachmentsBack('Message4', [
      attachmentSnow,
      attachmentWaterfall,
    ]);

    debug('Clicking first image');
    const FirstMessage = getTimelineMessageWithText(page, 'Message1');
    const FirstImage = FirstMessage.locator('.module-image').nth(0);

    await FirstImage.click();

    const Lightbox = page.locator('.Lightbox');
    const LightboxContent = Lightbox.locator('.Lightbox__zoomable-container');
    const LighboxPrev = Lightbox.locator('.Lightbox__button--previous');
    const LighboxNext = Lightbox.locator('.Lightbox__button--next');

    await Lightbox.waitFor();

    async function expectLightboxImage(
      attachment: Proto.AttachmentPointer.Params
    ) {
      assert.ok(
        attachment.attachmentIdentifier?.cdnKey != null,
        'Must have cdnKey'
      );
      const { cdnKey } = attachment.attachmentIdentifier;
      strictAssert(cdnKey.length > 0, 'Must have valid cdnKey');
      const Object = LightboxContent.getByTestId(cdnKey);
      debug(`Waiting for attachment with cdnKey ${cdnKey}`);
      await expect(Object).toBeVisible();
    }

    const order = [
      ['sent 1: attachment 1', attachmentCat],
      ['sent 2: attachment 1', attachmentSnow],
      ['sent 2: attachment 2', attachmentWaterfall],
      ['received 1: attachment 1', attachmentCat],
      ['received 2: attachment 1', attachmentSnow],
      ['received 2: attachment 2', attachmentWaterfall],
    ] as const;

    const reverseOrder = order.slice().reverse();

    for (const [index, [label, attachment]] of order.entries()) {
      if (index > 0) {
        // oxlint-disable-next-line no-await-in-loop
        await LighboxNext.click();
      }
      debug(label);
      // oxlint-disable-next-line no-await-in-loop
      await expectLightboxImage(attachment);
    }

    for (const [index, [label, attachment]] of reverseOrder.entries()) {
      if (index > 0) {
        // oxlint-disable-next-line no-await-in-loop
        await LighboxPrev.click();
      }
      debug(label);
      // oxlint-disable-next-line no-await-in-loop
      await expectLightboxImage(attachment);
    }
  });
});
