import { Injectable, Logger } from '@nestjs/common';
import Email from 'email-templates';
import { google } from 'googleapis';
import { createTransport } from 'nodemailer';
import SMTPTransport from 'nodemailer/lib/smtp-transport';
import path from 'path';
import { EEnvVariable } from '../configuration/enums/env-variable.enum';
import { EEmailTemplate, EmailTemplateData } from './enums/eemail-template';
import { EMAIL_TEMPLATE_SEND_DATA } from './template-send-data';

@Injectable()
export class EmailService {
  private logger: Logger = new Logger('EmailService');

  // Do not access this variable directly. Use `getAccessToken` method instead
  private accessToken: string = null;
  private accessTokenExpiry: number = null;

  constructor() {
    this.initMailer();
  }

  private async initMailer() {
    await this.testTokenRetrieval();
  }

  private async testTokenRetrieval() {
    try {
      await this.getAccessToken();
      this.logger.log('Successfully tested Gmail access token retrieval');
    } catch (e) {
      this.logger.error(
        'Gmail access token retrieval testing failed. Functionality may not work',
        e
      );
    }
  }

  private async getAccessToken(): Promise<string> {
    const time = Date.now();
    if (time < this.accessTokenExpiry) {
      return this.accessToken;
    }

    const auth = new google.auth.OAuth2(
      process.env[EEnvVariable.MAILER_GOOGLE_CLIENT_ID],
      process.env[EEnvVariable.MAILER_GOOGLE_CLIENT_SECRET],
      'https://developers.google.com/oauthplayground'
    );
    auth.setCredentials({
      refresh_token: process.env[EEnvVariable.MAILER_GOOGLE_REFRESH_TOKEN],
    });

    const result = await auth.getAccessToken();
    this.accessTokenExpiry = result.res.data.expiry_date;
    this.accessToken = result.token;

    this.logger.debug(
      `Google Access token has been refreshed; expiry=${
        this.accessTokenExpiry
      } - remaining ${this.accessTokenExpiry - Date.now()} ms`
    );

    return this.accessToken;
  }

  public async send<T extends EEmailTemplate>(
    to: string,
    template: T,
    data: EmailTemplateData[T],
    priority: 'high' | 'normal' | 'low' = 'normal'
  ) {
    const renderedTemplate = new Email({
      preview: {
        template: path.join(
          __dirname,
          '../../resources/email-templates',
          `${template}.pug`
        ),
      },
      views: { root: path.join(__dirname, '../../resources/email-templates') },
    });

    await (await this.createTransporter()).sendMail({
      to,
      html: await renderedTemplate.render(template, data),
      priority,
      ...EMAIL_TEMPLATE_SEND_DATA[template](data),
    });
  }

  private async createTransporter() {
    const accessToken = await this.getAccessToken();
    return createTransport(
      new SMTPTransport({
        host: 'smtp.gmail.com',
        port: 465,
        secure: true,
        auth: {
          type: 'OAUTH2',
          user: process.env[EEnvVariable.MAILER_USER],
          accessToken,
        },
      })
    );
  }
}
