import moment from 'moment';
import { Model } from 'objection';
import uniqid from 'uniqid';
import SubscriptionPeriod from '@/services/Subscription/SubscriptionPeriod';
import BaseModel from 'models/Model';
import TenantMetadata from './TenantMetadata';
import PlanSubscription from './Subscriptions/PlanSubscription';

export default class Tenant extends BaseModel {
  upgradeJobId: string;
  buildJobId: string;
  initializedAt!: Date | null;
  seededAt!: Date | null;

  /**
   * Table name.
   */
  static get tableName() {
    return 'tenants';
  }

  /**
   * Timestamps columns.
   * @returns {string[]}
   */
  get timestamps() {
    return ['createdAt', 'updatedAt'];
  }

  /**
   * Virtual attributes.
   * @returns {string[]}
   */
  static get virtualAttributes() {
    return ['isReady', 'isBuildRunning', 'isUpgradeRunning'];
  }

  /**
   * Tenant is ready.
   * @returns {boolean}
   */
  get isReady() {
    return !!(this.initializedAt && this.seededAt);
  }

  /**
   * Detarimes the tenant whether is build currently running.
   * @returns {boolean}
   */
  get isBuildRunning() {
    return !!this.buildJobId;
  }

  /**
   * Detarmines the tenant whether is upgrade currently running.
   * @returns {boolean}
   */
  get isUpgradeRunning() {
    return !!this.upgradeJobId;
  }

  /**
   * Query modifiers.
   */
  static modifiers() {
    return {
      subscriptions(builder) {
        builder.withGraphFetched('subscriptions');
      },
    };
  }

  /**
   * Relations mappings.
   */
  static get relationMappings() {
    const PlanSubscription = require('./Subscriptions/PlanSubscription');
    const TenantMetadata = require('./TenantMetadata');

    return {
      subscriptions: {
        relation: Model.HasManyRelation,
        modelClass: PlanSubscription.default,
        join: {
          from: 'tenants.id',
          to: 'subscription_plan_subscriptions.tenantId',
        },
      },
      metadata: {
        relation: Model.HasOneRelation,
        modelClass: TenantMetadata.default,
        join: {
          from: 'tenants.id',
          to: 'tenants_metadata.tenantId',
        },
      },
    };
  }

  /**
   * Creates a new tenant with random organization id.
   */
  static createWithUniqueOrgId(uniqId) {
    const organizationId = uniqid() || uniqId;
    return this.query().insert({ organizationId });
  }

  /**
   * Mark as seeded.
   * @param {number} tenantId
   */
  static markAsSeeded(tenantId) {
    const seededAt = moment().toMySqlDateTime();
    return this.query().update({ seededAt }).where({ id: tenantId });
  }

  /**
   * Mark the the given organization as initialized.
   * @param {string} organizationId
   */
  static markAsInitialized(tenantId) {
    const initializedAt = moment().toMySqlDateTime();
    return this.query().update({ initializedAt }).where({ id: tenantId });
  }

  /**
   * Marks the given tenant as built.
   */
  static markAsBuilt(tenantId) {
    const builtAt = moment().toMySqlDateTime();
    return this.query().update({ builtAt }).where({ id: tenantId });
  }

  /**
   * Marks the given tenant as built.
   */
  static markAsBuilding(tenantId, buildJobId) {
    return this.query().update({ buildJobId }).where({ id: tenantId });
  }

  /**
   * Marks the given tenant as built.
   */
  static markAsBuildCompleted(tenantId) {
    return this.query().update({ buildJobId: null }).where({ id: tenantId });
  }

  /**
   * Marks the given tenant as upgrading.
   * @param {number} tenantId
   * @param {string} upgradeJobId
   * @returns
   */
  static markAsUpgrading(tenantId, upgradeJobId) {
    return this.query().update({ upgradeJobId }).where({ id: tenantId });
  }

  /**
   * Markes the given tenant as upgraded.
   * @param {number} tenantId
   * @returns
   */
  static markAsUpgraded(tenantId) {
    return this.query().update({ upgradeJobId: null }).where({ id: tenantId });
  }

  /**
   * Saves the metadata of the given tenant.
   */
  static async saveMetadata(tenantId, metadata) {
    const foundMetadata = await TenantMetadata.query().findOne({ tenantId });
    const updateOrInsert = foundMetadata ? 'patch' : 'insert';

    return TenantMetadata.query()
      [updateOrInsert]({
        tenantId,
        ...metadata,
      })
      .where({ tenantId });
  }

  /**
   * Saves the metadata of the tenant.
   */
  saveMetadata(metadata) {
    return Tenant.saveMetadata(this.id, metadata);
  }

  /**
   *
   * @param {*} planId
   * @param {*} invoiceInterval
   * @param {*} invoicePeriod
   * @param {*} subscriptionSlug
   * @returns
   */
  public newSubscription(
    planId,
    invoiceInterval,
    invoicePeriod,
    subscriptionSlug,
    payload?,
  ) {
    return Tenant.newSubscription(
      this.id,
      planId,
      invoiceInterval,
      invoicePeriod,
      subscriptionSlug,
      payload
    );
  }

  /**
   * Records a new subscription for the associated tenant.
   */
  static newSubscription(
    tenantId: number,
    planId: number,
    invoiceInterval: 'month' | 'year',
    invoicePeriod: number,
    subscriptionSlug: string,
    payload?: { lemonSqueezyId: string }
  ) {
    const period = new SubscriptionPeriod(invoiceInterval, invoicePeriod);

    return PlanSubscription.query().insert({
      tenantId,
      slug: subscriptionSlug,
      planId,
      startsAt: period.getStartDate(),
      endsAt: period.getEndDate(),
      lemonSubscriptionId: payload?.lemonSqueezyId || null,
    });
  }
}
