const Transaction = require('../../execution/transaction');
const debug = require('debug')('knex:tx');

class Transaction_MSSQL extends Transaction {
  begin(/** @type {import('tedious').Connection} */ conn) {
    debug('transaction::begin id=%s', this.txid);

    return new Promise((resolve, reject) => {
      conn.beginTransaction(
        (err) => {
          if (err) {
            debug(
              'transaction::begin error id=%s message=%s',
              this.txid,
              err.message
            );
            return reject(err);
          }
          resolve();
        },
        this.outerTx ? this.txid : undefined,
        nameToIsolationLevelEnum(this.isolationLevel)
      );
    }).then(this._resolver, this._rejecter);
  }

  savepoint(conn) {
    debug('transaction::savepoint id=%s', this.txid);

    return new Promise((resolve, reject) => {
      conn.saveTransaction(
        (err) => {
          if (err) {
            debug(
              'transaction::savepoint id=%s message=%s',
              this.txid,
              err.message
            );
            return reject(err);
          }

          this.trxClient.emit('query', {
            __knexUid: this.trxClient.__knexUid,
            __knexTxId: this.trxClient.__knexTxId,
            autogenerated: true,
            sql: this.outerTx
              ? `SAVE TRANSACTION [${this.txid}]`
              : `SAVE TRANSACTION`,
          });
          resolve();
        },
        this.outerTx ? this.txid : undefined
      );
    });
  }

  commit(conn, value) {
    debug('transaction::commit id=%s', this.txid);

    return new Promise((resolve, reject) => {
      conn.commitTransaction(
        (err) => {
          if (err) {
            debug(
              'transaction::commit error id=%s message=%s',
              this.txid,
              err.message
            );
            return reject(err);
          }

          this._completed = true;
          resolve(value);
        },
        this.outerTx ? this.txid : undefined
      );
    }).then(() => this._resolver(value), this._rejecter);
  }

  release(conn, value) {
    return this._resolver(value);
  }

  rollback(conn, error) {
    this._completed = true;
    debug('transaction::rollback id=%s', this.txid);

    return new Promise((_resolve, reject) => {
      if (!conn.inTransaction) {
        return reject(
          error || new Error('Transaction rejected with non-error: undefined')
        );
      }

      if (conn.state.name !== 'LoggedIn') {
        return reject(
          new Error(
            "Can't rollback transaction. There is a request in progress"
          )
        );
      }

      conn.rollbackTransaction(
        (err) => {
          if (err) {
            debug(
              'transaction::rollback error id=%s message=%s',
              this.txid,
              err.message
            );
          }

          reject(
            err ||
              error ||
              new Error('Transaction rejected with non-error: undefined')
          );
        },
        this.outerTx ? this.txid : undefined
      );
    }).catch((err) => {
      if (!error && this.doNotRejectOnRollback) {
        this._resolver();
        return;
      }
      if (error) {
        try {
          err.originalError = error;
        } catch (_err) {
          // This is to handle https://github.com/knex/knex/issues/4128
        }
      }
      this._rejecter(err);
    });
  }

  rollbackTo(conn, error) {
    return this.rollback(conn, error).then(
      () =>
        void this.trxClient.emit('query', {
          __knexUid: this.trxClient.__knexUid,
          __knexTxId: this.trxClient.__knexTxId,
          autogenerated: true,
          sql: `ROLLBACK TRANSACTION`,
        })
    );
  }
}

module.exports = Transaction_MSSQL;

function nameToIsolationLevelEnum(level) {
  if (!level) return;
  level = level.toUpperCase().replace(' ', '_');
  const knownEnum = isolationEnum[level];
  if (!knownEnum) {
    throw new Error(
      `Unknown Isolation level, was expecting one of: ${JSON.stringify(
        humanReadableKeys
      )}`
    );
  }
  return knownEnum;
}

// Based on: https://github.com/tediousjs/node-mssql/blob/master/lib/isolationlevel.js
const isolationEnum = {
  READ_UNCOMMITTED: 0x01,
  READ_COMMITTED: 0x02,
  REPEATABLE_READ: 0x03,
  SERIALIZABLE: 0x04,
  SNAPSHOT: 0x05,
};
const humanReadableKeys = Object.keys(isolationEnum).map((key) =>
  key.toLowerCase().replace('_', ' ')
);
