import { AuthDto } from 'src/dtos/auth.dto';
import { SyncEntityType, SyncRequestType } from 'src/enum';
import { SYNC_TYPES_ORDER, SyncService } from 'src/services/sync.service';
import { mediumFactory, newMediumService } from 'test/medium.factory';
import { factory } from 'test/small.factory';
import { getKyselyDB } from 'test/utils';

const setup = async () => {
  const db = await getKyselyDB();

  const { sut, mocks, repos, getRepository } = newMediumService(SyncService, {
    database: db,
    repos: {
      sync: 'real',
      session: 'real',
    },
  });

  const user = mediumFactory.userInsert();
  const session = mediumFactory.sessionInsert({ userId: user.id });
  const auth = factory.auth({
    session,
    user: {
      id: user.id,
      name: user.name,
      email: user.email,
    },
  });

  await getRepository('user').create(user);
  await getRepository('session').create(session);

  const testSync = async (auth: AuthDto, types: SyncRequestType[]) => {
    const stream = mediumFactory.syncStream();
    // Wait for 1ms to ensure all updates are available
    await new Promise((resolve) => setTimeout(resolve, 1));
    await sut.stream(auth, stream, { types });

    return stream.getResponse();
  };

  return {
    sut,
    auth,
    mocks,
    repos,
    getRepository,
    testSync,
  };
};

describe(SyncService.name, () => {
  it('should have all the types in the ordering variable', () => {
    for (const key in SyncRequestType) {
      expect(SYNC_TYPES_ORDER).includes(key);
    }

    expect(SYNC_TYPES_ORDER.length).toBe(Object.keys(SyncRequestType).length);
  });

  describe.concurrent(SyncEntityType.UserV1, () => {
    it('should detect and sync the first user', async () => {
      const { auth, sut, getRepository, testSync } = await setup();

      const userRepo = getRepository('user');
      const user = await userRepo.get(auth.user.id, { withDeleted: false });
      if (!user) {
        expect.fail('First user should exist');
      }

      const initialSyncResponse = await testSync(auth, [SyncRequestType.UsersV1]);
      expect(initialSyncResponse).toHaveLength(1);
      expect(initialSyncResponse).toEqual([
        {
          ack: expect.any(String),
          data: {
            deletedAt: user.deletedAt,
            email: user.email,
            id: user.id,
            name: user.name,
          },
          type: 'UserV1',
        },
      ]);

      const acks = [initialSyncResponse[0].ack];
      await sut.setAcks(auth, { acks });
      const ackSyncResponse = await testSync(auth, [SyncRequestType.UsersV1]);

      expect(ackSyncResponse).toHaveLength(0);
    });

    it('should detect and sync a soft deleted user', async () => {
      const { auth, sut, getRepository, testSync } = await setup();

      const deletedAt = new Date().toISOString();
      const deletedUser = mediumFactory.userInsert({ deletedAt });
      const deleted = await getRepository('user').create(deletedUser);

      const response = await testSync(auth, [SyncRequestType.UsersV1]);

      expect(response).toHaveLength(2);
      expect(response).toEqual(
        expect.arrayContaining([
          {
            ack: expect.any(String),
            data: {
              deletedAt: null,
              email: auth.user.email,
              id: auth.user.id,
              name: auth.user.name,
            },
            type: 'UserV1',
          },
          {
            ack: expect.any(String),
            data: {
              deletedAt,
              email: deleted.email,
              id: deleted.id,
              name: deleted.name,
            },
            type: 'UserV1',
          },
        ]),
      );

      const acks = [response[1].ack];
      await sut.setAcks(auth, { acks });
      const ackSyncResponse = await testSync(auth, [SyncRequestType.UsersV1]);

      expect(ackSyncResponse).toHaveLength(0);
    });

    it('should detect and sync a deleted user', async () => {
      const { auth, sut, getRepository, testSync } = await setup();

      const userRepo = getRepository('user');
      const user = mediumFactory.userInsert();
      await userRepo.create(user);
      await userRepo.delete({ id: user.id }, true);

      const response = await testSync(auth, [SyncRequestType.UsersV1]);

      expect(response).toHaveLength(2);
      expect(response).toEqual(
        expect.arrayContaining([
          {
            ack: expect.any(String),
            data: {
              userId: user.id,
            },
            type: 'UserDeleteV1',
          },
          {
            ack: expect.any(String),
            data: {
              deletedAt: null,
              email: auth.user.email,
              id: auth.user.id,
              name: auth.user.name,
            },
            type: 'UserV1',
          },
        ]),
      );

      const acks = response.map(({ ack }) => ack);
      await sut.setAcks(auth, { acks });
      const ackSyncResponse = await testSync(auth, [SyncRequestType.UsersV1]);

      expect(ackSyncResponse).toHaveLength(0);
    });

    it('should sync a user and then an update to that same user', async () => {
      const { auth, sut, getRepository, testSync } = await setup();

      const initialSyncResponse = await testSync(auth, [SyncRequestType.UsersV1]);

      expect(initialSyncResponse).toHaveLength(1);
      expect(initialSyncResponse).toEqual(
        expect.arrayContaining([
          {
            ack: expect.any(String),
            data: {
              deletedAt: null,
              email: auth.user.email,
              id: auth.user.id,
              name: auth.user.name,
            },
            type: 'UserV1',
          },
        ]),
      );

      const acks = [initialSyncResponse[0].ack];
      await sut.setAcks(auth, { acks });

      const userRepo = getRepository('user');
      const updated = await userRepo.update(auth.user.id, { name: 'new name' });
      const updatedSyncResponse = await testSync(auth, [SyncRequestType.UsersV1]);

      expect(updatedSyncResponse).toHaveLength(1);
      expect(updatedSyncResponse).toEqual(
        expect.arrayContaining([
          {
            ack: expect.any(String),
            data: {
              deletedAt: null,
              email: auth.user.email,
              id: auth.user.id,
              name: updated.name,
            },
            type: 'UserV1',
          },
        ]),
      );
    });
  });

  describe.concurrent(SyncEntityType.PartnerV1, () => {
    it('should detect and sync the first partner', async () => {
      const { auth, sut, getRepository, testSync } = await setup();

      const user1 = auth.user;
      const userRepo = getRepository('user');
      const partnerRepo = getRepository('partner');

      const user2 = mediumFactory.userInsert();
      await userRepo.create(user2);

      const partner = await partnerRepo.create({ sharedById: user2.id, sharedWithId: user1.id });

      const initialSyncResponse = await testSync(auth, [SyncRequestType.PartnersV1]);

      expect(initialSyncResponse).toHaveLength(1);
      expect(initialSyncResponse).toEqual(
        expect.arrayContaining([
          {
            ack: expect.any(String),
            data: {
              inTimeline: partner.inTimeline,
              sharedById: partner.sharedById,
              sharedWithId: partner.sharedWithId,
            },
            type: 'PartnerV1',
          },
        ]),
      );

      const acks = [initialSyncResponse[0].ack];
      await sut.setAcks(auth, { acks });

      const ackSyncResponse = await testSync(auth, [SyncRequestType.PartnersV1]);

      expect(ackSyncResponse).toHaveLength(0);
    });

    it('should detect and sync a deleted partner', async () => {
      const { auth, sut, getRepository, testSync } = await setup();

      const userRepo = getRepository('user');
      const user1 = auth.user;
      const user2 = mediumFactory.userInsert();
      await userRepo.create(user2);

      const partnerRepo = getRepository('partner');
      const partner = await partnerRepo.create({ sharedById: user2.id, sharedWithId: user1.id });
      await partnerRepo.remove(partner);

      const response = await testSync(auth, [SyncRequestType.PartnersV1]);

      expect(response).toHaveLength(1);
      expect(response).toEqual(
        expect.arrayContaining([
          {
            ack: expect.any(String),
            data: {
              sharedById: partner.sharedById,
              sharedWithId: partner.sharedWithId,
            },
            type: 'PartnerDeleteV1',
          },
        ]),
      );

      const acks = response.map(({ ack }) => ack);
      await sut.setAcks(auth, { acks });

      const ackSyncResponse = await testSync(auth, [SyncRequestType.PartnersV1]);

      expect(ackSyncResponse).toHaveLength(0);
    });

    it('should detect and sync a partner share both to and from another user', async () => {
      const { auth, sut, getRepository, testSync } = await setup();

      const userRepo = getRepository('user');
      const user1 = auth.user;
      const user2 = await userRepo.create(mediumFactory.userInsert());

      const partnerRepo = getRepository('partner');
      const partner1 = await partnerRepo.create({ sharedById: user2.id, sharedWithId: user1.id });
      const partner2 = await partnerRepo.create({ sharedById: user1.id, sharedWithId: user2.id });

      const response = await testSync(auth, [SyncRequestType.PartnersV1]);

      expect(response).toHaveLength(2);
      expect(response).toEqual(
        expect.arrayContaining([
          {
            ack: expect.any(String),
            data: {
              inTimeline: partner1.inTimeline,
              sharedById: partner1.sharedById,
              sharedWithId: partner1.sharedWithId,
            },
            type: 'PartnerV1',
          },
          {
            ack: expect.any(String),
            data: {
              inTimeline: partner2.inTimeline,
              sharedById: partner2.sharedById,
              sharedWithId: partner2.sharedWithId,
            },
            type: 'PartnerV1',
          },
        ]),
      );

      await sut.setAcks(auth, { acks: [response[1].ack] });

      const ackSyncResponse = await testSync(auth, [SyncRequestType.PartnersV1]);

      expect(ackSyncResponse).toHaveLength(0);
    });

    it('should sync a partner and then an update to that same partner', async () => {
      const { auth, sut, getRepository, testSync } = await setup();

      const userRepo = getRepository('user');
      const user1 = auth.user;
      const user2 = await userRepo.create(mediumFactory.userInsert());

      const partnerRepo = getRepository('partner');
      const partner = await partnerRepo.create({ sharedById: user2.id, sharedWithId: user1.id });

      const initialSyncResponse = await testSync(auth, [SyncRequestType.PartnersV1]);

      expect(initialSyncResponse).toHaveLength(1);
      expect(initialSyncResponse).toEqual(
        expect.arrayContaining([
          {
            ack: expect.any(String),
            data: {
              inTimeline: partner.inTimeline,
              sharedById: partner.sharedById,
              sharedWithId: partner.sharedWithId,
            },
            type: 'PartnerV1',
          },
        ]),
      );

      const acks = [initialSyncResponse[0].ack];
      await sut.setAcks(auth, { acks });

      const updated = await partnerRepo.update(
        { sharedById: partner.sharedById, sharedWithId: partner.sharedWithId },
        { inTimeline: true },
      );

      const updatedSyncResponse = await testSync(auth, [SyncRequestType.PartnersV1]);

      expect(updatedSyncResponse).toHaveLength(1);
      expect(updatedSyncResponse).toEqual(
        expect.arrayContaining([
          {
            ack: expect.any(String),
            data: {
              inTimeline: updated.inTimeline,
              sharedById: updated.sharedById,
              sharedWithId: updated.sharedWithId,
            },
            type: 'PartnerV1',
          },
        ]),
      );
    });

    it('should not sync a partner or partner delete for an unrelated user', async () => {
      const { auth, getRepository, testSync } = await setup();

      const userRepo = getRepository('user');
      const user2 = await userRepo.create(mediumFactory.userInsert());
      const user3 = await userRepo.create(mediumFactory.userInsert());

      const partnerRepo = getRepository('partner');
      const partner = await partnerRepo.create({ sharedById: user2.id, sharedWithId: user3.id });

      expect(await testSync(auth, [SyncRequestType.PartnersV1])).toHaveLength(0);

      await partnerRepo.remove(partner);

      expect(await testSync(auth, [SyncRequestType.PartnersV1])).toHaveLength(0);
    });

    it('should not sync a partner delete after a user is deleted', async () => {
      const { auth, getRepository, testSync } = await setup();

      const userRepo = getRepository('user');
      const user2 = await userRepo.create(mediumFactory.userInsert());

      const partnerRepo = getRepository('partner');
      await partnerRepo.create({ sharedById: user2.id, sharedWithId: auth.user.id });
      await userRepo.delete({ id: user2.id }, true);

      expect(await testSync(auth, [SyncRequestType.PartnersV1])).toHaveLength(0);
    });
  });

  describe.concurrent(SyncEntityType.AssetV1, () => {
    it('should detect and sync the first asset', async () => {
      const { auth, sut, getRepository, testSync } = await setup();

      const checksum = '1115vHcVkZzNp3Q9G+FEA0nu6zUbGb4Tj4UOXkN0wRA=';
      const thumbhash = '2225vHcVkZzNp3Q9G+FEA0nu6zUbGb4Tj4UOXkN0wRA=';
      const date = new Date().toISOString();

      const assetRepo = getRepository('asset');
      const asset = mediumFactory.assetInsert({
        ownerId: auth.user.id,
        checksum: Buffer.from(checksum, 'base64'),
        thumbhash: Buffer.from(thumbhash, 'base64'),
        fileCreatedAt: date,
        fileModifiedAt: date,
        localDateTime: date,
        deletedAt: null,
      });
      await assetRepo.create(asset);

      const initialSyncResponse = await testSync(auth, [SyncRequestType.AssetsV1]);

      expect(initialSyncResponse).toHaveLength(1);
      expect(initialSyncResponse).toEqual(
        expect.arrayContaining([
          {
            ack: expect.any(String),
            data: {
              id: asset.id,
              ownerId: asset.ownerId,
              thumbhash,
              checksum,
              deletedAt: asset.deletedAt,
              fileCreatedAt: asset.fileCreatedAt,
              fileModifiedAt: asset.fileModifiedAt,
              isFavorite: asset.isFavorite,
              localDateTime: asset.localDateTime,
              type: asset.type,
              visibility: asset.visibility,
            },
            type: 'AssetV1',
          },
        ]),
      );

      const acks = [initialSyncResponse[0].ack];
      await sut.setAcks(auth, { acks });

      const ackSyncResponse = await testSync(auth, [SyncRequestType.AssetsV1]);

      expect(ackSyncResponse).toHaveLength(0);
    });

    it('should detect and sync a deleted asset', async () => {
      const { auth, sut, getRepository, testSync } = await setup();

      const assetRepo = getRepository('asset');
      const asset = mediumFactory.assetInsert({ ownerId: auth.user.id });
      await assetRepo.create(asset);
      await assetRepo.remove(asset);

      const response = await testSync(auth, [SyncRequestType.AssetsV1]);

      expect(response).toHaveLength(1);
      expect(response).toEqual(
        expect.arrayContaining([
          {
            ack: expect.any(String),
            data: {
              assetId: asset.id,
            },
            type: 'AssetDeleteV1',
          },
        ]),
      );

      const acks = response.map(({ ack }) => ack);
      await sut.setAcks(auth, { acks });

      const ackSyncResponse = await testSync(auth, [SyncRequestType.AssetsV1]);

      expect(ackSyncResponse).toHaveLength(0);
    });

    it('should not sync an asset or asset delete for an unrelated user', async () => {
      const { auth, getRepository, testSync } = await setup();

      const userRepo = getRepository('user');
      const user2 = mediumFactory.userInsert();
      await userRepo.create(user2);

      const sessionRepo = getRepository('session');
      const session = mediumFactory.sessionInsert({ userId: user2.id });
      await sessionRepo.create(session);

      const assetRepo = getRepository('asset');
      const asset = mediumFactory.assetInsert({ ownerId: user2.id });
      await assetRepo.create(asset);

      const auth2 = factory.auth({ session, user: user2 });

      expect(await testSync(auth2, [SyncRequestType.AssetsV1])).toHaveLength(1);
      expect(await testSync(auth, [SyncRequestType.AssetsV1])).toHaveLength(0);

      await assetRepo.remove(asset);
      expect(await testSync(auth2, [SyncRequestType.AssetsV1])).toHaveLength(1);
      expect(await testSync(auth, [SyncRequestType.AssetsV1])).toHaveLength(0);
    });
  });

  describe.concurrent(SyncRequestType.PartnerAssetsV1, () => {
    it('should detect and sync the first partner asset', async () => {
      const { auth, sut, getRepository, testSync } = await setup();

      const checksum = '1115vHcVkZzNp3Q9G+FEA0nu6zUbGb4Tj4UOXkN0wRA=';
      const thumbhash = '2225vHcVkZzNp3Q9G+FEA0nu6zUbGb4Tj4UOXkN0wRA=';
      const date = new Date().toISOString();

      const userRepo = getRepository('user');
      const user2 = mediumFactory.userInsert();
      await userRepo.create(user2);

      const assetRepo = getRepository('asset');
      const asset = mediumFactory.assetInsert({
        ownerId: user2.id,
        checksum: Buffer.from(checksum, 'base64'),
        thumbhash: Buffer.from(thumbhash, 'base64'),
        fileCreatedAt: date,
        fileModifiedAt: date,
        localDateTime: date,
        deletedAt: null,
      });
      await assetRepo.create(asset);

      const partnerRepo = getRepository('partner');
      await partnerRepo.create({ sharedById: user2.id, sharedWithId: auth.user.id });

      const initialSyncResponse = await testSync(auth, [SyncRequestType.PartnerAssetsV1]);

      expect(initialSyncResponse).toHaveLength(1);
      expect(initialSyncResponse).toEqual(
        expect.arrayContaining([
          {
            ack: expect.any(String),
            data: {
              id: asset.id,
              ownerId: asset.ownerId,
              thumbhash,
              checksum,
              deletedAt: null,
              fileCreatedAt: date,
              fileModifiedAt: date,
              isFavorite: false,
              localDateTime: date,
              type: asset.type,
              visibility: asset.visibility,
            },
            type: SyncEntityType.PartnerAssetV1,
          },
        ]),
      );

      const acks = [initialSyncResponse[0].ack];
      await sut.setAcks(auth, { acks });

      const ackSyncResponse = await testSync(auth, [SyncRequestType.PartnerAssetsV1]);

      expect(ackSyncResponse).toHaveLength(0);
    });

    it('should detect and sync a deleted partner asset', async () => {
      const { auth, sut, getRepository, testSync } = await setup();

      const userRepo = getRepository('user');
      const user2 = mediumFactory.userInsert();
      await userRepo.create(user2);
      const asset = mediumFactory.assetInsert({ ownerId: user2.id });

      const assetRepo = getRepository('asset');
      await assetRepo.create(asset);

      const partnerRepo = getRepository('partner');
      await partnerRepo.create({ sharedById: user2.id, sharedWithId: auth.user.id });
      await assetRepo.remove(asset);

      const response = await testSync(auth, [SyncRequestType.PartnerAssetsV1]);

      expect(response).toHaveLength(1);
      expect(response).toEqual(
        expect.arrayContaining([
          {
            ack: expect.any(String),
            data: {
              assetId: asset.id,
            },
            type: SyncEntityType.PartnerAssetDeleteV1,
          },
        ]),
      );

      const acks = response.map(({ ack }) => ack);
      await sut.setAcks(auth, { acks });

      const ackSyncResponse = await testSync(auth, [SyncRequestType.PartnerAssetsV1]);

      expect(ackSyncResponse).toHaveLength(0);
    });

    it('should not sync a deleted partner asset due to a user delete', async () => {
      const { auth, getRepository, testSync } = await setup();

      const userRepo = getRepository('user');
      const user2 = mediumFactory.userInsert();
      await userRepo.create(user2);

      const partnerRepo = getRepository('partner');
      await partnerRepo.create({ sharedById: user2.id, sharedWithId: auth.user.id });

      const assetRepo = getRepository('asset');
      await assetRepo.create(mediumFactory.assetInsert({ ownerId: user2.id }));

      await userRepo.delete({ id: user2.id }, true);

      const response = await testSync(auth, [SyncRequestType.PartnerAssetsV1]);
      expect(response).toHaveLength(0);
    });

    it('should not sync a deleted partner asset due to a partner delete (unshare)', async () => {
      const { auth, getRepository, testSync } = await setup();

      const userRepo = getRepository('user');
      const user2 = mediumFactory.userInsert();
      await userRepo.create(user2);

      const assetRepo = getRepository('asset');
      await assetRepo.create(mediumFactory.assetInsert({ ownerId: user2.id }));

      const partnerRepo = getRepository('partner');
      const partner = { sharedById: user2.id, sharedWithId: auth.user.id };
      await partnerRepo.create(partner);

      await expect(testSync(auth, [SyncRequestType.PartnerAssetsV1])).resolves.toHaveLength(1);

      await partnerRepo.remove(partner);

      await expect(testSync(auth, [SyncRequestType.PartnerAssetsV1])).resolves.toHaveLength(0);
    });

    it('should not sync an asset or asset delete for own user', async () => {
      const { auth, getRepository, testSync } = await setup();

      const userRepo = getRepository('user');
      const user2 = mediumFactory.userInsert();
      await userRepo.create(user2);

      const assetRepo = getRepository('asset');
      const asset = mediumFactory.assetInsert({ ownerId: auth.user.id });
      await assetRepo.create(asset);

      const partnerRepo = getRepository('partner');
      await partnerRepo.create({ sharedById: user2.id, sharedWithId: auth.user.id });

      await expect(testSync(auth, [SyncRequestType.AssetsV1])).resolves.toHaveLength(1);
      await expect(testSync(auth, [SyncRequestType.PartnerAssetsV1])).resolves.toHaveLength(0);

      await assetRepo.remove(asset);

      await expect(testSync(auth, [SyncRequestType.AssetsV1])).resolves.toHaveLength(1);
      await expect(testSync(auth, [SyncRequestType.PartnerAssetsV1])).resolves.toHaveLength(0);
    });

    it('should not sync an asset or asset delete for unrelated user', async () => {
      const { auth, getRepository, testSync } = await setup();

      const userRepo = getRepository('user');
      const user2 = mediumFactory.userInsert();
      await userRepo.create(user2);

      const sessionRepo = getRepository('session');
      const session = mediumFactory.sessionInsert({ userId: user2.id });
      await sessionRepo.create(session);

      const auth2 = factory.auth({ session, user: user2 });

      const assetRepo = getRepository('asset');
      const asset = mediumFactory.assetInsert({ ownerId: user2.id });
      await assetRepo.create(asset);

      await expect(testSync(auth2, [SyncRequestType.AssetsV1])).resolves.toHaveLength(1);
      await expect(testSync(auth, [SyncRequestType.PartnerAssetsV1])).resolves.toHaveLength(0);

      await assetRepo.remove(asset);

      await expect(testSync(auth2, [SyncRequestType.AssetsV1])).resolves.toHaveLength(1);
      await expect(testSync(auth, [SyncRequestType.PartnerAssetsV1])).resolves.toHaveLength(0);
    });
  });

  describe.concurrent(SyncRequestType.AssetExifsV1, () => {
    it('should detect and sync the first asset exif', async () => {
      const { auth, sut, getRepository, testSync } = await setup();

      const assetRepo = getRepository('asset');
      const asset = mediumFactory.assetInsert({ ownerId: auth.user.id });
      await assetRepo.create(asset);
      await assetRepo.upsertExif({ assetId: asset.id, make: 'Canon' });

      const initialSyncResponse = await testSync(auth, [SyncRequestType.AssetExifsV1]);

      expect(initialSyncResponse).toHaveLength(1);
      expect(initialSyncResponse).toEqual(
        expect.arrayContaining([
          {
            ack: expect.any(String),
            data: {
              assetId: asset.id,
              city: null,
              country: null,
              dateTimeOriginal: null,
              description: '',
              exifImageHeight: null,
              exifImageWidth: null,
              exposureTime: null,
              fNumber: null,
              fileSizeInByte: null,
              focalLength: null,
              fps: null,
              iso: null,
              latitude: null,
              lensModel: null,
              longitude: null,
              make: 'Canon',
              model: null,
              modifyDate: null,
              orientation: null,
              profileDescription: null,
              projectionType: null,
              rating: null,
              state: null,
              timeZone: null,
            },
            type: SyncEntityType.AssetExifV1,
          },
        ]),
      );

      const acks = [initialSyncResponse[0].ack];
      await sut.setAcks(auth, { acks });

      const ackSyncResponse = await testSync(auth, [SyncRequestType.AssetExifsV1]);

      expect(ackSyncResponse).toHaveLength(0);
    });

    it('should only sync asset exif for own user', async () => {
      const { auth, getRepository, testSync } = await setup();

      const userRepo = getRepository('user');
      const user2 = mediumFactory.userInsert();
      await userRepo.create(user2);

      const partnerRepo = getRepository('partner');
      await partnerRepo.create({ sharedById: user2.id, sharedWithId: auth.user.id });

      const assetRepo = getRepository('asset');
      const asset = mediumFactory.assetInsert({ ownerId: user2.id });
      await assetRepo.create(asset);
      await assetRepo.upsertExif({ assetId: asset.id, make: 'Canon' });

      const sessionRepo = getRepository('session');
      const session = mediumFactory.sessionInsert({ userId: user2.id });
      await sessionRepo.create(session);

      const auth2 = factory.auth({ session, user: user2 });
      await expect(testSync(auth2, [SyncRequestType.AssetExifsV1])).resolves.toHaveLength(1);
      await expect(testSync(auth, [SyncRequestType.AssetExifsV1])).resolves.toHaveLength(0);
    });
  });

  describe.concurrent(SyncRequestType.PartnerAssetExifsV1, () => {
    it('should detect and sync the first partner asset exif', async () => {
      const { auth, sut, getRepository, testSync } = await setup();

      const userRepo = getRepository('user');
      const user2 = mediumFactory.userInsert();
      await userRepo.create(user2);

      const partnerRepo = getRepository('partner');
      await partnerRepo.create({ sharedById: user2.id, sharedWithId: auth.user.id });

      const assetRepo = getRepository('asset');
      const asset = mediumFactory.assetInsert({ ownerId: user2.id });
      await assetRepo.create(asset);
      await assetRepo.upsertExif({ assetId: asset.id, make: 'Canon' });

      const initialSyncResponse = await testSync(auth, [SyncRequestType.PartnerAssetExifsV1]);

      expect(initialSyncResponse).toHaveLength(1);
      expect(initialSyncResponse).toEqual(
        expect.arrayContaining([
          {
            ack: expect.any(String),
            data: {
              assetId: asset.id,
              city: null,
              country: null,
              dateTimeOriginal: null,
              description: '',
              exifImageHeight: null,
              exifImageWidth: null,
              exposureTime: null,
              fNumber: null,
              fileSizeInByte: null,
              focalLength: null,
              fps: null,
              iso: null,
              latitude: null,
              lensModel: null,
              longitude: null,
              make: 'Canon',
              model: null,
              modifyDate: null,
              orientation: null,
              profileDescription: null,
              projectionType: null,
              rating: null,
              state: null,
              timeZone: null,
            },
            type: SyncEntityType.PartnerAssetExifV1,
          },
        ]),
      );

      const acks = [initialSyncResponse[0].ack];
      await sut.setAcks(auth, { acks });

      const ackSyncResponse = await testSync(auth, [SyncRequestType.PartnerAssetExifsV1]);

      expect(ackSyncResponse).toHaveLength(0);
    });

    it('should not sync partner asset exif for own user', async () => {
      const { auth, getRepository, testSync } = await setup();

      const userRepo = getRepository('user');
      const user2 = mediumFactory.userInsert();
      await userRepo.create(user2);

      const partnerRepo = getRepository('partner');
      await partnerRepo.create({ sharedById: user2.id, sharedWithId: auth.user.id });

      const assetRepo = getRepository('asset');
      const asset = mediumFactory.assetInsert({ ownerId: auth.user.id });
      await assetRepo.create(asset);
      await assetRepo.upsertExif({ assetId: asset.id, make: 'Canon' });

      await expect(testSync(auth, [SyncRequestType.AssetExifsV1])).resolves.toHaveLength(1);
      await expect(testSync(auth, [SyncRequestType.PartnerAssetExifsV1])).resolves.toHaveLength(0);
    });

    it('should not sync partner asset exif for unrelated user', async () => {
      const { auth, getRepository, testSync } = await setup();

      const userRepo = getRepository('user');

      const user2 = mediumFactory.userInsert();
      const user3 = mediumFactory.userInsert();
      await Promise.all([userRepo.create(user2), userRepo.create(user3)]);

      const partnerRepo = getRepository('partner');
      await partnerRepo.create({ sharedById: user2.id, sharedWithId: auth.user.id });

      const assetRepo = getRepository('asset');
      const asset = mediumFactory.assetInsert({ ownerId: user3.id });
      await assetRepo.create(asset);
      await assetRepo.upsertExif({ assetId: asset.id, make: 'Canon' });

      const sessionRepo = getRepository('session');
      const session = mediumFactory.sessionInsert({ userId: user3.id });
      await sessionRepo.create(session);

      const authUser3 = factory.auth({ session, user: user3 });
      await expect(testSync(authUser3, [SyncRequestType.AssetExifsV1])).resolves.toHaveLength(1);
      await expect(testSync(auth, [SyncRequestType.PartnerAssetExifsV1])).resolves.toHaveLength(0);
    });
  });
});
