'use strict';

const chai = require('chai');

const expect = chai.expect;
const Support = require('../support');
const { DataTypes } = require('@sequelize/core');

const dialect = Support.getTestDialect();

describe(Support.getTestDialectTeaser('QueryInterface'), () => {
  beforeEach(function () {
    this.queryInterface = this.sequelize.queryInterface;
  });

  describe('describeTable', () => {
    Support.allowDeprecationsInSuite(['SEQUELIZE0015']);

    if (Support.sequelize.dialect.supports.schemas) {
      it('reads the metadata of the table with schema in object', async function () {
        const MyTable1 = this.sequelize.define('my_table', {
          username1: DataTypes.STRING,
        });

        const MyTable2 = this.sequelize.define(
          'my_table',
          {
            username2: DataTypes.STRING,
          },
          { schema: 'test_meta' },
        );

        await this.sequelize.createSchema('test_meta');
        await MyTable1.sync({ force: true });
        await MyTable2.sync({ force: true });
        const metadata0 = await this.queryInterface.describeTable({
          tableName: 'my_tables',
          schema: 'test_meta',
        });
        expect(metadata0.username2).not.to.be.undefined;
        const metadata = await this.queryInterface.describeTable('my_tables');
        expect(metadata.username1).not.to.be.undefined;
      });

      it('reads the metadata of the table with schema parameter', async function () {
        const MyTable1 = this.sequelize.define('my_table', {
          username1: DataTypes.STRING,
        });

        const MyTable2 = this.sequelize.define(
          'my_table',
          {
            username2: DataTypes.STRING,
          },
          { schema: 'test_meta' },
        );

        await this.sequelize.createSchema('test_meta');
        await MyTable1.sync({ force: true });
        await MyTable2.sync({ force: true });
        const metadata0 = await this.queryInterface.describeTable('my_tables', 'test_meta');
        expect(metadata0.username2).not.to.be.undefined;
        const metadata = await this.queryInterface.describeTable('my_tables');
        expect(metadata.username1).not.to.be.undefined;
      });
    }

    it('rejects when no data is available', async function () {
      const table = this.sequelize.queryGenerator.extractTableDetails('_some_random_missing_table');
      await expect(this.queryInterface.describeTable(table)).to.be.rejectedWith(
        `No description found for table ${table.tableName}${table.schema ? ` in schema ${table.schema}` : ''}. Check the table name and schema; remember, they _are_ case sensitive.`,
      );
    });

    it('reads the metadata of the table', async function () {
      const Users = this.sequelize.define(
        '_Users',
        {
          username: DataTypes.STRING,
          city: {
            type: DataTypes.STRING,
            defaultValue: null,
            comment: 'Users City',
          },
          isAdmin: DataTypes.BOOLEAN,
          enumVals: DataTypes.ENUM('hello', 'world'),
        },
        { freezeTableName: true },
      );

      await Users.sync({ force: true });
      const metadata = await this.queryInterface.describeTable('_Users');
      const id = metadata.id;
      const username = metadata.username;
      const city = metadata.city;
      const isAdmin = metadata.isAdmin;
      const enumVals = metadata.enumVals;

      expect(id.primaryKey).to.be.true;

      if (['mysql', 'mssql', 'db2'].includes(dialect)) {
        expect(id.autoIncrement).to.be.true;
      }

      let assertVal = 'VARCHAR(255)';
      switch (dialect) {
        case 'postgres':
          assertVal = 'CHARACTER VARYING(255)';
          break;
        case 'mssql':
          assertVal = 'NVARCHAR(255)';
          break;
        case 'sqlite3':
          assertVal = 'TEXT';
          break;
        case 'ibmi':
        case 'db2':
          assertVal = 'VARCHAR';
          break;
      }

      expect(username.type).to.equal(assertVal);
      expect(username.allowNull).to.be.true;

      switch (dialect) {
        case 'sqlite3':
          expect(username.defaultValue).to.be.undefined;
          break;
        default:
          expect(username.defaultValue).to.be.null;
      }

      switch (dialect) {
        case 'sqlite3':
          expect(city.defaultValue).to.be.null;
          break;
      }

      assertVal = 'TINYINT(1)';
      switch (dialect) {
        case 'postgres':
        case 'db2':
          assertVal = 'BOOLEAN';
          break;
        case 'sqlite3':
          assertVal = 'INTEGER';
          break;
        case 'mssql':
          assertVal = 'BIT';
          break;
        case 'ibmi':
          assertVal = 'SMALLINT';
          break;
      }

      expect(isAdmin.type).to.equal(assertVal);
      expect(isAdmin.allowNull).to.be.true;
      switch (dialect) {
        case 'sqlite3':
          expect(isAdmin.defaultValue).to.be.undefined;
          break;
        default:
          expect(isAdmin.defaultValue).to.be.null;
      }

      if (dialect.startsWith('postgres')) {
        expect(enumVals.special).to.be.instanceof(Array);
        expect(enumVals.special).to.have.length(2);
      } else if (dialect === 'mysql') {
        expect(enumVals.type).to.eql("ENUM('hello','world')");
      }

      if (['postgres', 'mysql', 'mssql'].includes(dialect)) {
        expect(city.comment).to.equal('Users City');
        expect(username.comment).to.equal(null);
      }
    });

    it('should correctly determine the primary key columns', async function () {
      const Country = this.sequelize.define(
        '_Country',
        {
          code: { type: DataTypes.STRING, primaryKey: true },
          name: { type: DataTypes.STRING, allowNull: false },
        },
        { freezeTableName: true },
      );
      const Alumni = this.sequelize.define(
        '_Alumni',
        {
          year: { type: DataTypes.INTEGER, primaryKey: true },
          num: { type: DataTypes.INTEGER, primaryKey: true },
          username: { type: DataTypes.STRING, allowNull: false, unique: true },
          dob: { type: DataTypes.DATEONLY, allowNull: false },
          dod: { type: DataTypes.DATEONLY, allowNull: true },
          city: { type: DataTypes.STRING, allowNull: false },
          ctrycod: {
            type: DataTypes.STRING,
            allowNull: false,
            references: { model: Country, key: 'code' },
          },
        },
        { freezeTableName: true },
      );

      await Country.sync({ force: true });
      const metacountry = await this.queryInterface.describeTable('_Country');
      expect(metacountry.code.primaryKey).to.eql(true);
      expect(metacountry.name.primaryKey).to.eql(false);

      await Alumni.sync({ force: true });
      const metalumni = await this.queryInterface.describeTable('_Alumni');
      expect(metalumni.year.primaryKey).to.eql(true);
      expect(metalumni.num.primaryKey).to.eql(true);
      expect(metalumni.username.primaryKey).to.eql(false);
      expect(metalumni.dob.primaryKey).to.eql(false);
      expect(metalumni.dod.primaryKey).to.eql(false);
      expect(metalumni.ctrycod.primaryKey).to.eql(false);
      expect(metalumni.city.primaryKey).to.eql(false);
    });

    it('should correctly return the columns when the table contains a dot in the name', async function () {
      const User = this.sequelize.define(
        'my.user',
        {
          name: DataTypes.STRING,
        },
        { freezeTableName: true },
      );

      await User.sync({ force: true });
      const metadata = await this.queryInterface.describeTable('my.user');

      expect(metadata).to.haveOwnProperty('name');
    });
  });
});
