import { AbstractRepository } from './AbstractRepository';
import { ConnectionManager } from './ConnectionManager';

interface IRepoTest {
  lowercase: string;
  camelCase: string;
  PascalCase: string;
  snake_case: string;
  'kebab-case': string;
  UPPERCASE: string;
}

class RepoTest implements IRepoTest {
  public lowercase: string;
  public camelCase: string;
  public PascalCase: string;
  public snake_case: string;
  public 'kebab-case': string;
  public UPPERCASE: string;

  constructor(data: Partial<IRepoTest> = {}) {
    this.lowercase = data.lowercase ?? 'some lowercase';
    this.camelCase = data.camelCase ?? 'some camelCase';
    this.PascalCase = data.PascalCase ?? 'some PascalCase';
    this.snake_case = data.snake_case ?? 'some snake_case';
    this['kebab-case'] = data['kebab-case'] ?? 'some kebab-case';
    this.UPPERCASE = data.UPPERCASE ?? 'some UPPERCASE';
  }
}

function defaultMapping(): { [key in keyof IRepoTest]: string } {
  return {
    lowercase: 'lowercase',
    camelCase: 'camel_case',
    PascalCase: 'pascal_case',
    snake_case: 'snake_case',
    'kebab-case': 'kebab_case',
    UPPERCASE: 'uppercase',
  };
}

const allProperties = Object.keys(defaultMapping()) as Array<keyof IRepoTest>;

class AbstractRepositoryIntegrationTest extends AbstractRepository<IRepoTest, RepoTest> {}

describe('AbstractRepository', () => {
  let db: ConnectionManager<{ test: AbstractRepositoryIntegrationTest }>;
  beforeAll(async () => {
    db = new ConnectionManager(
      'AbstractRepositoryIntegrationTest',
      process.env.COMPONENT_DB_CONNECTION_STRING!,
      '',
      [],
      (db) => ({
        test: new AbstractRepositoryIntegrationTest(
          db.repositories,
          db.pool,
          'abstract_repository_db_test',
          defaultMapping(),
          RepoTest,
        ),
      }),
    );
    await db.pool.query(`
        CREATE TABLE IF NOT EXISTS abstract_repository_db_test (
            lowercase text,
            camel_case text,
            pascal_case text,
            snake_case text,
            kebab_case text,
            uppercase text
        );
    `);
  });

  afterAll(async () => {
    await db.pool.query('DROP TABLE IF EXISTS abstract_repository_db_test;');
    await db.close();
  });

  describe('properties are always mapped to columns', () => {
    beforeEach(async () => {
      await db.pool.query('DELETE FROM abstract_repository_db_test;');
    });
    it('create', async () => {
      const repo = db.repositories.test;
      const data = new RepoTest();
      const result = await repo.create(data);
      expect(result).toEqual(data);
    });

    it.each(allProperties)('update %s', async (property) => {
      const repo = db.repositories.test;
      const data = await repo.create(new RepoTest());
      const result = await repo.update({ [property]: data[property] }, { [property]: `new ${data[property]}` });
      expect(result).toEqual([{ ...data, [property]: `new ${data[property]}` }]);
    });

    it.each(allProperties)('delete %s', async (property) => {
      const repo = db.repositories.test;
      const data = await repo.create(new RepoTest());
      const result = await repo.delete({ [property]: data[property] });
      expect(result).toEqual(1);
    });

    it.each(allProperties)('findOne %s', async (property) => {
      const repo = db.repositories.test;
      const findData = await repo.create(new RepoTest({ [property]: 'some value' }));
      const _differentData = await repo.create(new RepoTest({ [property]: 'some other value' }));

      const result = await repo.findOne({ [property]: findData[property] });
      expect(result).toEqual(findData);
    });

    it.each(allProperties)('find %s', async (property) => {
      const repo = db.repositories.test;
      const findData = await repo.create(new RepoTest({ [property]: 'some value' }));
      const _differentData = await repo.create(new RepoTest({ [property]: 'some other value' }));

      const result = await repo.find({ [property]: findData[property] });
      expect(result).toEqual([findData]);
    });

    it.each(allProperties)('findAll %s', async (property) => {
      const repo = db.repositories.test;
      const findData = await repo.create(new RepoTest({ [property]: 'some value' }));
      const differentData = await repo.create(new RepoTest({ [property]: 'some other value' }));

      const result = await repo.findAll();
      expect(result).toEqual([findData, differentData]);
    });

    it.each(allProperties)('getPage %s', async (property) => {
      const repo = db.repositories.test;
      const firstData = await repo.create(new RepoTest({ [property]: 'aaaaa' }));
      const secondData = await repo.create(new RepoTest({ [property]: 'bbbb' }));

      const ascResult = await repo.getPage(1, [property], 'asc');
      expect(ascResult).toMatchObject({ items: [firstData, secondData], totalCount: 2 });
      const descResult = await repo.getPage(1, [property], 'desc');
      expect(descResult).toMatchObject({ items: [secondData, firstData], totalCount: 2 });
    });

    it.each(allProperties)('getPageRaw %s', async (property) => {
      const repo = db.repositories.test;
      const firstData = await repo.create(new RepoTest({ [property]: 'aaaaa' }));
      const secondData = await repo.create(new RepoTest({ [property]: 'bbbb' }));

      const ascResult = await repo.getPageRaw(1, [property], 'asc');
      expect(ascResult).toMatchObject({ items: [firstData, secondData], totalCount: 2 });
      const descResult = await repo.getPageRaw(1, [property], 'desc');
      expect(descResult).toMatchObject({ items: [secondData, firstData], totalCount: 2 });
    });

    it.each(allProperties)('update Fails when new value is empty object -  %s', async (property) => {
      const repo = db.repositories.test;
      const data = await repo.create(new RepoTest());
      await expect(repo.update({ [property]: data[property] }, {})).rejects.toThrowErrorMatchingInlineSnapshot(
        '"Failed updating the repository, update values cannot be empty"',
      );
    });
  });
});
