import fs from "fs";
import Handlebars from 'handlebars';
import i18next from "i18next";
import Backend from 'i18next-fs-backend';
import nodemailer from 'nodemailer';
import { MailOptions } from 'nodemailer/lib/json-transport';
import Mail from 'nodemailer/lib/mailer';
import path from 'path';
import { config } from './config';
import { MailServerConfigError } from './errors/MailErrors';


/**
 * This class is responsible for all mail sending.
 * This uses the html and plaintext templates from src/templates.
 */
export class Mailer {
    private transport: Mail;
    private static interpolations = { copyright_owner: config.copyright_owner, event_name: config.event_name, contact_mail: config.contact_mail }

    /**
     * Main constructor.
     * Initializes i18n(ext), Handlebars and puppeteer.
     */
    constructor() {
        this.init();
    }

    /**
     * Main constructor.
     * Initializes i18n(ext), Handlebars and puppeteer.
     */
    public async init() {
        await i18next
            .use(Backend)
            .init({
                fallbackLng: 'en',
                lng: 'en',
                backend: {
                    loadPath: path.join(__dirname, '/locales/{{lng}}.json')
                }
            });
        await Handlebars.registerHelper('__',
            function (str) {
                return i18next.t(str, Mailer.interpolations).toString();
            }
        );
        this.transport = nodemailer.createTransport({
            host: config.mail_server,
            port: config.mail_port,
            auth: {
                user: config.mail_user,
                pass: config.mail_password
            }
        });

        this.transport.verify(function (error, success) {
            if (error) {
                throw new MailServerConfigError();
            }
        });
    }

    /**
     * Function for sending a reset mail from the reset mail template.
     * @param to_address The address the mail will be sent to. Should always get pulled from a user object.
     * @param token The requested password reset token - will be combined with the app_url to generate a password reset link.
     */
    public async sendResetMail(to_address: string, token: string, locale: string = "en") {
        await i18next.changeLanguage(locale);

        const replacements = {
            recipient_mail: to_address,
            copyright_owner: config.copyright_owner,
            link_imprint: `${config.app_url}/imprint`,
            link_privacy: `${config.app_url}/privacy`,
            reset_link: `${config.app_url}/reset/${(Buffer.from(token)).toString("base64")}`
        }

        const template_html = Handlebars.compile(fs.readFileSync(__dirname + '/templates/pw-reset.html', { encoding: 'utf8' }));
        const template_txt = Handlebars.compile(fs.readFileSync(__dirname + '/templates/pw-reset.txt', { encoding: 'utf8' }));
        const body_html = template_html(replacements);
        const body_txt = template_txt(replacements);

        const mail: MailOptions = {
            to: to_address,
            subject: i18next.t("lfk-password-reset", Mailer.interpolations).toString(),
            text: body_txt,
            html: body_html
        };
        await this.sendMail(mail);
    }

    /**
     * Function for sending a test mail from the test mail template.
     * @param to_address The address the mail will be sent to - usually the FROM address.
     */
    public async sendTestMail(locale: string = "en") {
        await i18next.changeLanguage(locale);
        const to_address: string = config.mail_from;
        const replacements = {
            recipient_mail: to_address,
            copyright_owner: config.copyright_owner,
            link_imprint: `${config.app_url}/imprint`,
            link_privacy: `${config.app_url}/privacy`
        }

        const template_html = Handlebars.compile(fs.readFileSync(__dirname + '/templates/test.html', { encoding: 'utf8' }));
        const template_txt = Handlebars.compile(fs.readFileSync(__dirname + '/templates/test.txt', { encoding: 'utf8' }));
        const body_html = template_html(replacements);
        const body_txt = template_txt(replacements);

        const mail: MailOptions = {
            to: to_address,
            subject: i18next.t("test-mail", Mailer.interpolations).toString(),
            text: body_txt,
            html: body_html
        };
        await this.sendMail(mail);
    }

    /**
     * Function for sending a reset mail from the reset mail template.
     * @param to_address The address the mail will be sent to. Should always get pulled from a runner object.
     * @param token The runner's selfservice token - will be combined with the app_url to generate a selfservice profile link.
     */
    public async sendWelcomeMail(to_address: string, token: string, locale: string = "en") {
        await i18next.changeLanguage(locale);
        token = Buffer.from(token).toString("base64");

        const replacements = {
            recipient_mail: to_address,
            copyright_owner: config.copyright_owner,
            link_imprint: `${config.app_url}/imprint`,
            link_privacy: `${config.app_url}/privacy`,
            selfservice_link: `${config.app_url}/selfservice/profile/${token}`,
            forgot_link: `${config.app_url}/selfservice/`,
            contact_mail: config.contact_mail,
            event_name: config.event_name
        }

        const template_html = Handlebars.compile(fs.readFileSync(__dirname + '/templates/welcome_runner.html', { encoding: 'utf8' }));
        const template_txt = Handlebars.compile(fs.readFileSync(__dirname + '/templates/welcome_runner.txt', { encoding: 'utf8' }));
        const body_html = template_html(replacements);
        const body_txt = template_txt(replacements);

        const mail: MailOptions = {
            to: to_address,
            subject: i18next.t("event_name-registration", Mailer.interpolations).toString(),
            text: body_txt,
            html: body_html
        };
        await this.sendMail(mail);
    }

    /**
     * Function for sending a selfservice link forgotten mail from the runner_forgot template.
     * @param to_address The address the mail will be sent to. Should always get pulled from a runner object.
     * @param token The runner's selfservice token - will be combined with the app_url to generate a selfservice profile link.
     */
    public async sendSelfserviceForgottenMail(to_address: string, token: string, locale: string = "en") {
        await i18next.changeLanguage(locale);
        token = Buffer.from(token).toString("base64");

        const replacements = {
            recipient_mail: to_address,
            copyright_owner: config.copyright_owner,
            link_imprint: `${config.app_url}/imprint`,
            link_privacy: `${config.app_url}/privacy`,
            selfservice_link: `${config.app_url}/selfservice/profile/${token}`,
            forgot_link: `${config.app_url}/selfservice/`,
            contact_mail: config.contact_mail,
            event_name: config.event_name
        }

        const template_html = Handlebars.compile(fs.readFileSync(__dirname + '/templates/runner_forgot.html', { encoding: 'utf8' }));
        const template_txt = Handlebars.compile(fs.readFileSync(__dirname + '/templates/runner_forgot.txt', { encoding: 'utf8' }));
        const body_html = template_html(replacements);
        const body_txt = template_txt(replacements);

        const mail: MailOptions = {
            to: to_address,
            subject: i18next.t("your-event_name-profile", Mailer.interpolations).toString(),
            text: body_txt,
            html: body_html
        };
        await this.sendMail(mail);
    }

    /**
     * Wrapper function for sending a mail via this object's transporter.
     * @param mail MailOptions object containing the 
     */
    public async sendMail(mail: MailOptions) {
        mail.from = config.mail_from;
        await this.transport.sendMail(mail);
    }
}