const ObjectId = require('bson').ObjectID;
const clone = require('clone');
const tv4 = require('tv4');

const Audit = require('./audit');
const Checkout = require('./checkout');
const FamilyReference = require('./family-reference');
const InventoryReference = require('./inventory-reference');
const Transfer = require('./transfer');
const Location = require('../core/location');

const Helper = require('../../helper/helper');
const UNITS_CONVERSION = require('../../constant/chemical/units-conversion.constant');
const UNITS = require('../../constant/chemical/units.constant');

class Container {
  constructor(opt) {
    this._id = new ObjectId();
    this.substance = {
      _id: null,
    };
    this.family = null;
    this.inventory = null;
    this.location = null;
    this.size = null;
    this.amount = null;
    this.units = null;
    this.physicalState = null;
    this.type = null;
    this.openedDate = null;
    this.receivedDate = null;
    this.expirationDate = null;
    this.lastTestedDate = null;
    this.isPrivate = false;
    this.barcode = null;
    this.customBarcode = null;
    this.audits = [];
    this.tags = [];
    this.comments = null;
    this.checkout = null;
    this.normalizedSize = {
      gram: null,
      liter: null,
    };
    this.normalizedAmount = {
      gram: null,
      liter: null,
    };
    this.transfer = null;
    this.solvent = null;
    this.concentration = null;
    this.concentrationUnits = null;
    this.exemption = null;
    this.exemptionNotes = null;
    this.active = true;
    this.createdByUserId = null;

    if (opt) {
      this.fillObject(clone(opt));
    }
  }

  fillObject(opt) {
    this._id = new ObjectId(opt._id);
    // eslint-disable-next-line no-underscore-dangle
    this._createdDate = this._id.getTimestamp();
    this.substance = {
      _id: opt.substance && opt.substance._id ? new ObjectId(opt.substance._id) : null,
      name: opt.substance && opt.substance.name ? opt.substance.name : null,
      vendor: opt.substance && opt.substance.vendor ? opt.substance.vendor : null,
    };
    this.family = new FamilyReference(opt.family);
    this.inventory = new InventoryReference(opt.inventory);
    this.location = opt.location ? new Location(opt.location) : null;
    this.size = parseFloat(opt.size || 0, 10);
    this.amount = parseFloat(!isNaN(opt.amount) && opt.amount !== null ? opt.amount : opt.size, 10); // eslint-disable-line
    this.units = opt.units || null;
    this.physicalState = opt.physicalState || null;
    this.type = opt.type || null;
    this.openedDate = opt.openedDate ? new Date(opt.openedDate) : null;
    this.receivedDate = opt.receivedDate ? new Date(opt.receivedDate) : null;
    this.expirationDate = opt.expirationDate ? new Date(opt.expirationDate) : null;
    this.lastTestedDate = opt.lastTestedDate ? new Date(opt.lastTestedDate) : null;
    this.isPrivate = opt.isPrivate || false;
    this.barcode = opt.barcode || null;
    this.customBarcode = opt.customBarcode || null;
    this.comments = opt.comments || null;
    this.checkout = opt.checkout ? new Checkout(opt.checkout) : null;
    this.transfer = opt.transfer ? new Transfer(opt.transfer) : null;
    this.solvent = opt.solvent || null;
    this.concentration = opt.concentration ? parseFloat(opt.concentration) || null : null;
    this.concentrationUnits = opt.concentrationUnits || null;
    this.exemption = opt.exemption || null;
    this.exemptionNotes = opt.exemptionNotes || null;
    this.active = typeof opt.active === 'boolean' ? opt.active : this.active;
    this.createdByUserId = opt.createdByUserId;

    if (opt.audits) {
      this.audits = opt.audits.map((obj) => new Audit(obj));
    }

    if (opt.tags) {
      opt.tags.forEach((tag) => this.tags.push(tag));
    }

    const { normalizedSize, normalizedAmount } = this.normalizeSizeAndAmount(
      this.size,
      this.amount,
      this.units,
      this.family.density && this.family.density.value ? this.family.density.value : 1,
    );
    this.normalizedSize = normalizedSize;
    this.normalizedAmount = normalizedAmount;
  }

  validate() {
    const barcodePattern = Helper.getBarcodePattern(this.inventory);
    const validation = tv4.validateMultiple(this, Container.schema(barcodePattern));
    if (this.receivedDate && !Helper.validateDate(this.receivedDate)) {
      validation.valid = false;
      validation.errors.push(new Error('Received Date must be between the year 999 and 3000'));
    }
    if (this.openedDate && !Helper.validateDate(this.openedDate)) {
      validation.valid = false;
      validation.errors.push(new Error('Opened Date must be between the year 999 and 3000'));
    }
    if (this.expirationDate && !Helper.validateDate(this.expirationDate)) {
      validation.valid = false;
      validation.errors.push(new Error('Expiration Date must be between the year 999 and 3000'));
    }
    if (this.lastTestedDate && !Helper.validateDate(this.lastTestedDate)) {
      validation.valid = false;
      validation.errors.push(new Error('Last Tested Date Date must be between the year 999 and 3000'));
    }
    return validation;
  }

  // eslint-disable-next-line class-methods-use-this
  normalizeSizeAndAmount(size, amount, units, density = 1) {
    const normalizedSize = {
      gram: null,
      liter: null,
    };

    const normalizedAmount = {
      gram: null,
      liter: null,
    };

    const conversion = UNITS_CONVERSION[units];
    if (conversion) {
      switch (conversion.type) {
        case 'mass':
          normalizedSize.gram = size * conversion.conversionFactor;
          normalizedSize.liter = (size * conversion.conversionFactor * 0.001) / density; // eslint-disable-line
          normalizedAmount.gram = amount * conversion.conversionFactor;
          normalizedAmount.liter = (amount * conversion.conversionFactor * 0.001) / density; // eslint-disable-line
          break;

        case 'volume':
          normalizedSize.gram = size * conversion.conversionFactor * density * 1000 || null;
          normalizedSize.liter = size * conversion.conversionFactor;
          normalizedAmount.gram = amount * conversion.conversionFactor * density * 1000 || null;
          normalizedAmount.liter = amount * conversion.conversionFactor;
          break;

        default:
          break;
      }
    }

    return { normalizedSize, normalizedAmount };
  }

  static schema(pattern) {
    return {
      $schema: 'http://json-schema.org/draft-04/schema#',
      id: 'container.schema.json',
      type: 'object',
      properties: {
        _id: { type: 'object' },
        substance: { type: 'object' },
        family: { $ref: 'family-reference.schema.json' },
        inventory: { $ref: 'inventory-reference.schema.json' },
        location: { $ref: 'location.schema.json' },
        size: { type: 'number' },
        amount: { type: 'number' },
        normalizedSize: {
          type: 'object',
          properties: {
            gram: { type: ['number', 'null'] },
            liter: { type: ['number', 'null'] },
          },
        },
        normalizedAmount: {
          type: 'object',
          properties: {
            gram: { type: ['number', 'null'] },
            liter: { type: ['number', 'null'] },
          },
        },
        units: { enum: UNITS.map((unit) => unit.value) },
        physicalState: { type: ['string', 'null'] },
        type: { type: 'string' },
        openedDate: { type: ['object', 'null'] },
        receivedDate: { type: ['object', 'null'] },
        expirationDate: { type: ['object', 'null'] },
        lastTestedDate: { type: ['object', 'null'] },
        isPrivate: { type: 'boolean' },
        barcode: { type: ['string', 'null'], pattern: `${pattern}` },
        customBarcode: { type: ['string', 'null'] },
        audits: { type: 'array', item: { type: 'object' } },
        tags: { type: 'array', item: { type: 'string' } },
        comments: { type: ['string', 'null'] },
        solvent: { type: ['string', 'null'] },
        concentration: { type: ['number', 'null'] },
        concentrationUnits: { type: ['string', 'null'] },
        exemption: { type: ['string', 'null'] },
        exemptionNotes: { type: ['string', 'null'] },
        active: { type: 'boolean' },
        createdByUserId: { type: ['string'] },
        checkout: { anyOf: [{ $ref: 'checkout.schema.json' }, { type: ['null'] }] },
        transfer: { anyOf: [{ $ref: 'transfer.schema.json' }, { type: ['null'] }] },
      },

      required: Object.keys(new Container()),
    };
  }
}

tv4.addSchema(FamilyReference.schema());
tv4.addSchema(InventoryReference.schema());
tv4.addSchema(Location.schema());
tv4.addSchema(Checkout.schema());

module.exports = Container;
