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

const DocumentSection = require('./document-section');
const TemplateReference = require('./template-reference');

const CheckboxCard = require('./checkbox-card');
const ConditionCard = require('./condition-card');
const ChemicalCard = require('./chemical-card');
const EquipmentCard = require('./equipment-card');
const InspectCard = require('./inspect-card');
const FileReference = require('./file-reference');
const ReferenceCard = require('./reference-card');
const TextCard = require('./text-card');
const Access = require('../common/access');
const PersonReference = require('../core/person-reference');

const ACCESS_PERMISSION = require('../../constant/common/access-permission.constant');
const CARD_TYPE = require('../../constant/procedure/card-type.constant');
const DOCUMENT_STATUS = require('../../constant/procedure/document-status.constant');
const Helper = require('../../helper/helper');

class Document {
  constructor(opt) {
    this._id = new ObjectId();
    this.seriesId = null;
    this.revision = 0;
    this.template = null;
    this.title = null;
    this.description = null;
    this.sections = [];
    this.status = DOCUMENT_STATUS.DRAFT;
    this.access = [];
    this.acknowledgement = {
      acknowledgeLabel: 'Acknowledged by:',
      prompt: 'I have read and understood this document',
      requiredSignatures: [],
      signatures: [],
      showAcknowledgement: false,
    };
    this.createdBy = null;
    this.createdDate = null;
    this.lastModifiedBy = null;
    this.lastModifiedDate = null;
    this.expirationDate = null;
    this.readyForUseDate = null;
    this.readyForUseBy = null;
    this.label = {};
    this.workflowSteps = [];
    this.family = [];
    this.band = [];
    this.printedDate = null;

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

  fillObject(opt) {
    this._id = new ObjectId(opt._id);
    this.seriesId = new ObjectId(opt.seriesId);
    this.revision = opt.revision || this.revision;
    this.template = new TemplateReference(opt.template);
    this.title = opt.title ? opt.title : null;
    this.description = opt.description ? opt.description : null;
    this.access = opt.access && opt.access.length ? opt.access.map((item) => new Access(item)) : [];
    this.createdBy = opt.createdBy ? new PersonReference(opt.createdBy) : null;
    this.createdDate = opt.createdDate ? new Date(opt.createdDate) : null;
    this.lastModifiedBy = opt.lastModifiedBy ? new PersonReference(opt.lastModifiedBy) : null;
    this.lastModifiedDate = opt.lastModifiedDate ? new Date(opt.lastModifiedDate) : null;
    this.status = opt.status ? opt.status : DOCUMENT_STATUS.DRAFT;
    this.expirationDate = opt.expirationDate ? new Date(opt.expirationDate) : null;
    this.readyForUseDate = opt.readyForUseDate ? new Date(opt.readyForUseDate) : null;
    this.readyForUseBy = opt.readyForUseBy ? new PersonReference(opt.readyForUseBy) : null;
    this.workflowSteps = opt.workflowSteps ? opt.workflowSteps : [DOCUMENT_STATUS.DRAFT, DOCUMENT_STATUS.READY_FOR_USE];
    if (opt.acknowledgement) {
      this.acknowledgement.prompt = opt.acknowledgement.prompt || this.acknowledgement.prompt;
      this.acknowledgement.acknowledgeLabel =
        opt.acknowledgement.acknowledgeLabel || this.acknowledgement.acknowledgeLabel;
      if (Array.isArray(opt.acknowledgement.signatures)) {
        this.acknowledgement.signatures = this.acknowledgement.signatures.concat(
          opt.acknowledgement.signatures.map((signature) => ({
            person: new PersonReference(signature.person),
            date: signature.date ? new Date(signature.date) : new Date(),
          })),
        );
      }
      if (Array.isArray(opt.acknowledgement.requiredSignatures)) {
        this.acknowledgement.requiredSignatures = this.acknowledgement.requiredSignatures.concat(
          opt.acknowledgement.requiredSignatures.map((signature) => ({
            person: new PersonReference(signature.person),
            hasSigned: !!this.acknowledgement.signatures.some((sig) => sig.person.userId === signature.person.userId),
          })),
        );
      }
      this.acknowledgement.showAcknowledgement = opt.acknowledgement.showAcknowledgement || false;
    }
    if (opt.sections) {
      opt.sections.forEach((section) => {
        this.sections.push(new DocumentSection(section));
      });
    }
    if (opt.label) {
      const label = Object.keys(opt.label).reduce((acc, cur) => {
        if (!cur.match('band:') && !cur.match('family:')) {
          acc[cur] = opt.label[cur];
        }
        return acc;
      }, {});
      this.label = { ...label, ...this.getLabel() };
    } else {
      this.label = {};
    }
    this.printedDate = opt.createdDate ? new Date(opt.createdDate) : null;
  }

  setExpirationDate(duration) {
    const expirationDate = new Date();
    expirationDate.setHours(0, 0, 0, 0);
    expirationDate.setDate(expirationDate.getDate() + duration);
    this.expirationDate = expirationDate;
  }

  validate() {
    const validation = tv4.validateMultiple(this, Document.schema());
    const labelIsValid = Helper.validateLabel(this.label);
    if (!labelIsValid) {
      validation.errors.push(new Error('Invalid type (expecting string, boolean or number) for values on label'));
      validation.valid = false;
    }
    return validation;
  }

  static schema() {
    return {
      $schema: 'http://json-schema.org/draft-04/schema#',
      id: 'document.schema.json',
      type: 'object',
      additionalProperties: false,
      properties: {
        _id: { type: ['object', 'null'] },
        seriesId: { type: 'object' },
        revision: { type: 'integer' },
        template: { $ref: 'template-reference.schema.json' },
        title: { type: 'string' },
        description: { type: ['string', 'null'] },
        access: { type: 'array', item: { $ref: 'access.schema.json' } },
        acknowledgement: {
          type: 'object',
          properties: {
            acknowledgeLabel: { type: 'string' },
            prompt: { type: 'string' },
            requiredSignatures: {
              type: 'array',
              items: {
                person: { $ref: 'person-reference.schema.json' },
                hasSigned: { type: 'boolean' },
              },
            },
            signatures: {
              type: 'array',
              items: {
                person: { $ref: 'person-reference.schema.json' },
                date: { type: 'object' },
              },
            },
            showAcknowledgement: { type: 'boolean' },
          },
        },
        sections: { type: 'array', item: { $ref: 'document-section.schema.json' } },
        status: { type: 'string', enum: Object.keys(DOCUMENT_STATUS) },
        createdBy: { $ref: 'person-reference.schema.json' },
        createdDate: { type: 'object' },
        label: { type: 'object', items: { not: ['object'] } },
        family: { type: 'array', item: { type: 'object' } },
        band: { type: 'array', item: { type: 'object' } },
        lastModifiedBy: { $ref: 'person-reference.schema.json' },
        lastModifiedDate: { type: 'object' },
        expirationDate: { type: ['object', 'null'] },
        readyForUseBy: { anyOf: [{ type: 'null' }, { $ref: 'person-reference.schema.json' }] },
        readyForUseDate: { type: ['object', 'null'] },
        workflowSteps: { type: 'array', item: { type: 'string' } },
        printedDate: { type: ['object', 'null'] },
        // TODO: add validation for each card schema
      },
      required: Object.keys(new Document()),
    };
  }

  static cloneWithCommentsAllowed(document, properties) {
    const copy = Document.clone(document, properties);
    copy.title = `${document.title}`;
    copy.sections = copy.sections.map((section) => ({
      ...section,
      allowableCards: section.allowableCards.map((allowableCard) => ({ ...allowableCard, allowComment: true })),
      cards: section.cards.map((card) => ({
        ...card,
        allowComment: true,
      })),
    }));

    return new this(copy);
  }

  static clone(document, properties, newVersion = false, user = null) {
    const access = user ? [Access.fromPerson(user, ACCESS_PERMISSION.OWNER)] : document.access;
    const copy = {
      ...document,
      createdBy: user ? new PersonReference(user) : null,
      createdDate: new Date(),
      expirationDate: null,
      lastModifiedBy: null,
      lastModifiedDate: null,
      readyForUseBy: null,
      readyForUseDate: null,
      status: DOCUMENT_STATUS.DRAFT,
      acknowledgement: {
        ...document.acknowledgement,
        signatures: [],
      },
      ...(newVersion ? {} : { seriesId: new ObjectId(), revision: 0, title: `${document.title}-clone`, access }),
      ...properties,
    };
    delete copy._id;

    copy.sections = copy.sections.map((section) => {
      const cloneSection = {
        ...section,
        id: new ObjectId(),
        cards: section.cards.map((card) => {
          const attachments = newVersion ? card.attachments : [];
          if (card.type === CARD_TYPE.ACKNOWLEDGEMENT.type) {
            return {
              ...card,
              id: new ObjectId(),
              attachments,
              details: {
                ...card.details,
                signatures: [],
              },
            };
          }
          return {
            ...card,
            id: new ObjectId(),
            attachments,
          };
        }),
      };
      return cloneSection;
    });

    return new this(copy);
  }

  static fromTemplate(template, opt) {
    const status = template.createBySystem ? DOCUMENT_STATUS.READY_FOR_USE_BY_SYSTEM : DOCUMENT_STATUS.DRAFT;
    const sections = template.sectionDefinitions.map((sectionDefinition) => ({
      ...sectionDefinition,
      id: new ObjectId(),
      cards: sectionDefinition.cardDefinitions.map((item) => ({
        ...item,
        id: new ObjectId(),
      })),
    }));
    return new this({ ...opt, acknowledgement: template.acknowledgement, sections, template, status });
  }

  calculateCounts() {
    let count = {};
    this.sections.forEach((section) => {
      section.cards.forEach((card) => {
        if (card.details.countLabel) {
          count[card.details.countLabel] = (count[card.details.countLabel] || 0) + card.details.count;
        } else if (card.type === 'CONDITION') {
          count = card.calculateCounts(count);
        }
      });
    });
    const labels = Object.keys(count);
    const counts = labels.map((label) => count[label]);
    return { labels, counts };
  }

  getLabel() {
    let label = { ...this.label };
    this.sections.forEach((section) => {
      section.cards.forEach((card) => {
        if (typeof card.getLabel === 'function') {
          switch (card.type) {
            case 'CHEMICAL':
              this.family = this.family.concat(card.getLabel());
              break;
            case 'CHEMICAL_BAND':
              this.band = this.band.concat(card.getLabel());
              break;
            default:
              label = { ...label, ...card.getLabel() };
              break;
          }
        }
      });
    });
    return label;
  }
}

tv4.addSchema(Access.schema());
tv4.addSchema(PersonReference.schema());
tv4.addSchema(TemplateReference.schema());
tv4.addSchema(DocumentSection.schema());
tv4.addSchema(CheckboxCard.schema());
tv4.addSchema(ChemicalCard.schema());
tv4.addSchema(ConditionCard.schema());
tv4.addSchema(EquipmentCard.schema());
tv4.addSchema(InspectCard.schema());
tv4.addSchema(FileReference.schema());
tv4.addSchema(ReferenceCard.schema());
tv4.addSchema(TextCard.schema());

module.exports = Document;
