import { Container, Service, Inject } from 'typedi';
import TenantsManagerService from '@/services/Tenancy/TenantsManager';
import tenantModelsLoader from '@/loaders/tenantModels';
import tenantRepositoriesLoader from '@/loaders/tenantRepositories';
import tenantCacheLoader from '@/loaders/tenantCache';
import SmsClientLoader from '@/loaders/smsClient';

@Service()
export default class HasTenancyService {
  @Inject()
  tenantsManager: TenantsManagerService;

  /**
   * Retrieve the given tenant container.
   * @param  {number} tenantId
   * @return {Container}
   */
  tenantContainer(tenantId: number) {
    return Container.of(`tenant-${tenantId}`);
  }

  /**
   * Singleton tenant service.
   * @param {number} tenantId - Tenant id.
   * @param {string} key - Service key.
   * @param {Function} callback
   */
  singletonService(tenantId: number, key: string, callback: Function) {
    const container = this.tenantContainer(tenantId);
    const Logger = Container.get('logger');
    const hasServiceInstnace = container.has(key);

    if (!hasServiceInstnace) {
      const serviceInstance = callback();

      container.set(key, serviceInstance);
      Logger.info(`[tenant_DI] ${key} injected to tenant container.`, {
        tenantId,
        key,
      });

      return serviceInstance;
    } else {
      return container.get(key);
    }
  }

  /**
   * Retrieve knex instance of the given tenant id.
   * @param {number} tenantId
   */
  knex(tenantId: number) {
    return this.singletonService(tenantId, 'tenantManager', () => {
      return this.tenantsManager.getKnexInstance(tenantId);
    });
  }

  /**
   * Retrieve models of the givne tenant id.
   * @param {number} tenantId - The tenant id.
   */
  models(tenantId: number) {
    const knexInstance = this.knex(tenantId);

    return this.singletonService(tenantId, 'models', () => {
      return tenantModelsLoader(knexInstance);
    });
  }

  /**
   * Retrieve repositories of the given tenant id.
   * @param {number} tenantId - Tenant id.
   */
  repositories(tenantId: number) {
    return this.singletonService(tenantId, 'repositories', () => {
      const cache = this.cache(tenantId);
      const knex = this.knex(tenantId);
      const i18n = this.i18n(tenantId);

      const repositories = tenantRepositoriesLoader(knex, cache, i18n);

      Object.values(repositories).forEach((repository) => {
        repository.setTenantId(tenantId);
      });
      return repositories;
    });
  }

  /**
   * Sets i18n locals function.
   * @param {number} tenantId
   * @param locals
   */
  setI18nLocals(tenantId: number, locals: any) {
    return this.singletonService(tenantId, 'i18n', () => {
      return locals;
    });
  }

  /**
   * Retrieve i18n locales methods.
   * @param {number} tenantId - Tenant id.
   */
  i18n(tenantId: number) {
    return this.singletonService(tenantId, 'i18n', () => {
      throw new Error('I18n locals is not set yet.');
    });
  }

  /**
   * Retrieve tenant cache instance.
   * @param {number} tenantId - Tenant id.
   */
  cache(tenantId: number) {
    return this.singletonService(tenantId, 'cache', () => {
      return tenantCacheLoader(tenantId);
    });
  }

  settings(tenantId: number) {
    return this.singletonService(tenantId, 'settings', () => {
      throw new Error('Settings is not injected yet.');
    });
  }

  smsClient(tenantId: number) {
    return this.singletonService(tenantId, 'smsClient', () => {
      const settings = this.settings(tenantId);

      const token = settings.get({
        group: 'sms_integration',
        key: 'easysms_token',
      });
      return SmsClientLoader(token);
    });
  }
}
