import { LibraryResponseDto, LoginResponseDto } from '@app/domain';
import { LibraryController } from '@app/immich';
import { LibraryType } from '@app/infra/entities';
import { errorStub, userDto, uuidStub } from '@test/fixtures';
import request from 'supertest';
import { api } from '../client';
import { testApp } from '../utils';

describe(`${LibraryController.name} (e2e)`, () => {
  let server: any;
  let admin: LoginResponseDto;

  beforeAll(async () => {
    server = (await testApp.create()).getHttpServer();
  });

  afterAll(async () => {
    await testApp.teardown();
  });

  beforeEach(async () => {
    await testApp.reset();
    await api.authApi.adminSignUp(server);
    admin = await api.authApi.adminLogin(server);
  });

  describe('GET /library', () => {
    it('should require authentication', async () => {
      const { status, body } = await request(server).get('/library');
      expect(status).toBe(401);
      expect(body).toEqual(errorStub.unauthorized);
    });

    it('should start with a default upload library', async () => {
      const { status, body } = await request(server)
        .get('/library')
        .set('Authorization', `Bearer ${admin.accessToken}`);
      expect(status).toBe(200);
      expect(body).toHaveLength(1);
      expect(body).toEqual([
        expect.objectContaining({
          ownerId: admin.userId,
          type: LibraryType.UPLOAD,
          name: 'Default Library',
          refreshedAt: null,
          assetCount: 0,
          importPaths: [],
          exclusionPatterns: [],
        }),
      ]);
    });
  });

  describe('POST /library', () => {
    it('should require authentication', async () => {
      const { status, body } = await request(server).post('/library').send({});
      expect(status).toBe(401);
      expect(body).toEqual(errorStub.unauthorized);
    });

    it('should create an external library with defaults', async () => {
      const { status, body } = await request(server)
        .post('/library')
        .set('Authorization', `Bearer ${admin.accessToken}`)
        .send({ type: LibraryType.EXTERNAL });

      expect(status).toBe(201);
      expect(body).toEqual(
        expect.objectContaining({
          ownerId: admin.userId,
          type: LibraryType.EXTERNAL,
          name: 'New External Library',
          refreshedAt: null,
          assetCount: 0,
          importPaths: [],
          exclusionPatterns: [],
        }),
      );
    });

    it('should create an external library with options', async () => {
      const { status, body } = await request(server)
        .post('/library')
        .set('Authorization', `Bearer ${admin.accessToken}`)
        .send({
          type: LibraryType.EXTERNAL,
          name: 'My Awesome Library',
          importPaths: ['/path/to/import'],
          exclusionPatterns: ['**/Raw/**'],
        });

      expect(status).toBe(201);
      expect(body).toEqual(
        expect.objectContaining({
          name: 'My Awesome Library',
          importPaths: ['/path/to/import'],
        }),
      );
    });

    it('should create an upload library with defaults', async () => {
      const { status, body } = await request(server)
        .post('/library')
        .set('Authorization', `Bearer ${admin.accessToken}`)
        .send({ type: LibraryType.UPLOAD });

      expect(status).toBe(201);
      expect(body).toEqual(
        expect.objectContaining({
          ownerId: admin.userId,
          type: LibraryType.UPLOAD,
          name: 'New Upload Library',
          refreshedAt: null,
          assetCount: 0,
          importPaths: [],
          exclusionPatterns: [],
        }),
      );
    });

    it('should create an upload library with options', async () => {
      const { status, body } = await request(server)
        .post('/library')
        .set('Authorization', `Bearer ${admin.accessToken}`)
        .send({ type: LibraryType.UPLOAD, name: 'My Awesome Library' });

      expect(status).toBe(201);
      expect(body).toEqual(
        expect.objectContaining({
          name: 'My Awesome Library',
        }),
      );
    });

    it('should not allow upload libraries to have import paths', async () => {
      const { status, body } = await request(server)
        .post('/library')
        .set('Authorization', `Bearer ${admin.accessToken}`)
        .send({ type: LibraryType.UPLOAD, importPaths: ['/path/to/import'] });

      expect(status).toBe(400);
      expect(body).toEqual(errorStub.badRequest('Upload libraries cannot have import paths'));
    });

    it('should not allow upload libraries to have exclusion patterns', async () => {
      const { status, body } = await request(server)
        .post('/library')
        .set('Authorization', `Bearer ${admin.accessToken}`)
        .send({ type: LibraryType.UPLOAD, exclusionPatterns: ['**/Raw/**'] });

      expect(status).toBe(400);
      expect(body).toEqual(errorStub.badRequest('Upload libraries cannot have exclusion patterns'));
    });

    it('should allow a non-admin to create a library', async () => {
      await api.userApi.create(server, admin.accessToken, userDto.user1);
      const user1 = await api.authApi.login(server, userDto.user1);

      const { status, body } = await request(server)
        .post('/library')
        .set('Authorization', `Bearer ${user1.accessToken}`)
        .send({ type: LibraryType.EXTERNAL });

      expect(status).toBe(201);
      expect(body).toEqual(
        expect.objectContaining({
          ownerId: user1.userId,
          type: LibraryType.EXTERNAL,
          name: 'New External Library',
          refreshedAt: null,
          assetCount: 0,
          importPaths: [],
          exclusionPatterns: [],
        }),
      );
    });
  });

  describe('PUT /library/:id', () => {
    it('should require authentication', async () => {
      const { status, body } = await request(server).put(`/library/${uuidStub.notFound}`).send({});
      expect(status).toBe(401);
      expect(body).toEqual(errorStub.unauthorized);
    });

    describe('external library', () => {
      let library: LibraryResponseDto;

      beforeEach(async () => {
        // Create an external library with default settings
        library = await api.libraryApi.create(server, admin.accessToken, { type: LibraryType.EXTERNAL });
      });

      it('should change the library name', async () => {
        const { status, body } = await request(server)
          .put(`/library/${library.id}`)
          .set('Authorization', `Bearer ${admin.accessToken}`)
          .send({ name: 'New Library Name' });

        expect(status).toBe(200);
        expect(body).toEqual(
          expect.objectContaining({
            name: 'New Library Name',
          }),
        );
      });

      it('should not set an empty name', async () => {
        const { status, body } = await request(server)
          .put(`/library/${library.id}`)
          .set('Authorization', `Bearer ${admin.accessToken}`)
          .send({ name: '' });

        expect(status).toBe(400);
        expect(body).toEqual(errorStub.badRequest(['name should not be empty']));
      });

      it('should change the import paths', async () => {
        const { status, body } = await request(server)
          .put(`/library/${library.id}`)
          .set('Authorization', `Bearer ${admin.accessToken}`)
          .send({ importPaths: ['/path/to/import'] });

        expect(status).toBe(200);
        expect(body).toEqual(
          expect.objectContaining({
            importPaths: ['/path/to/import'],
          }),
        );
      });

      it('should not allow an empty import path', async () => {
        const { status, body } = await request(server)
          .put(`/library/${library.id}`)
          .set('Authorization', `Bearer ${admin.accessToken}`)
          .send({ importPaths: [''] });

        expect(status).toBe(400);
        expect(body).toEqual(errorStub.badRequest(['each value in importPaths should not be empty']));
      });

      it('should change the exclusion pattern', async () => {
        const { status, body } = await request(server)
          .put(`/library/${library.id}`)
          .set('Authorization', `Bearer ${admin.accessToken}`)
          .send({ exclusionPatterns: ['**/Raw/**'] });

        expect(status).toBe(200);
        expect(body).toEqual(
          expect.objectContaining({
            exclusionPatterns: ['**/Raw/**'],
          }),
        );
      });

      it('should not allow an empty exclusion pattern', async () => {
        const { status, body } = await request(server)
          .put(`/library/${library.id}`)
          .set('Authorization', `Bearer ${admin.accessToken}`)
          .send({ exclusionPatterns: [''] });

        expect(status).toBe(400);
        expect(body).toEqual(errorStub.badRequest(['each value in exclusionPatterns should not be empty']));
      });
    });
  });

  describe('GET /library/:id', () => {
    it('should require authentication', async () => {
      const { status, body } = await request(server).get(`/library/${uuidStub.notFound}`);

      expect(status).toBe(401);
      expect(body).toEqual(errorStub.unauthorized);
    });

    it('should get library by id', async () => {
      const library = await api.libraryApi.create(server, admin.accessToken, { type: LibraryType.EXTERNAL });

      const { status, body } = await request(server)
        .get(`/library/${library.id}`)
        .set('Authorization', `Bearer ${admin.accessToken}`);

      expect(status).toBe(200);
      expect(body).toEqual(
        expect.objectContaining({
          ownerId: admin.userId,
          type: LibraryType.EXTERNAL,
          name: 'New External Library',
          refreshedAt: null,
          assetCount: 0,
          importPaths: [],
          exclusionPatterns: [],
        }),
      );
    });

    it("should not allow getting another user's library", async () => {
      await Promise.all([
        api.userApi.create(server, admin.accessToken, userDto.user1),
        api.userApi.create(server, admin.accessToken, userDto.user2),
      ]);

      const [user1, user2] = await Promise.all([
        api.authApi.login(server, userDto.user1),
        api.authApi.login(server, userDto.user2),
      ]);

      const library = await api.libraryApi.create(server, user1.accessToken, { type: LibraryType.EXTERNAL });

      const { status, body } = await request(server)
        .get(`/library/${library.id}`)
        .set('Authorization', `Bearer ${user2.accessToken}`);

      expect(status).toBe(400);
      expect(body).toEqual(errorStub.badRequest('Not found or no library.read access'));
    });
  });

  describe('DELETE /library/:id', () => {
    it('should require authentication', async () => {
      const { status, body } = await request(server).delete(`/library/${uuidStub.notFound}`);

      expect(status).toBe(401);
      expect(body).toEqual(errorStub.unauthorized);
    });

    it('should not delete the last upload library', async () => {
      const [defaultLibrary] = await api.libraryApi.getAll(server, admin.accessToken);
      expect(defaultLibrary).toBeDefined();

      const { status, body } = await request(server)
        .delete(`/library/${defaultLibrary.id}`)
        .set('Authorization', `Bearer ${admin.accessToken}`);

      expect(status).toBe(400);
      expect(body).toEqual(errorStub.noDeleteUploadLibrary);
    });

    it('should delete an empty library', async () => {
      const library = await api.libraryApi.create(server, admin.accessToken, { type: LibraryType.EXTERNAL });

      const { status, body } = await request(server)
        .delete(`/library/${library.id}`)
        .set('Authorization', `Bearer ${admin.accessToken}`);

      expect(status).toBe(200);
      expect(body).toEqual({});

      const libraries = await api.libraryApi.getAll(server, admin.accessToken);
      expect(libraries).toHaveLength(1);
      expect(libraries).not.toEqual(
        expect.arrayContaining([
          expect.objectContaining({
            id: library.id,
          }),
        ]),
      );
    });
  });

  describe('GET /library/:id/statistics', () => {
    it('should require authentication', async () => {
      const { status, body } = await request(server).get(`/library/${uuidStub.notFound}/statistics`);

      expect(status).toBe(401);
      expect(body).toEqual(errorStub.unauthorized);
    });
  });

  describe('POST /library/:id/scan', () => {
    it('should require authentication', async () => {
      const { status, body } = await request(server).post(`/library/${uuidStub.notFound}/scan`).send({});

      expect(status).toBe(401);
      expect(body).toEqual(errorStub.unauthorized);
    });
  });

  describe('POST /library/:id/removeOffline', () => {
    it('should require authentication', async () => {
      const { status, body } = await request(server).post(`/library/${uuidStub.notFound}/removeOffline`).send({});

      expect(status).toBe(401);
      expect(body).toEqual(errorStub.unauthorized);
    });
  });
});
