import * as handlebars from 'handlebars';
import * as Constants from "../config/constants";
import { EmailTemplateDao } from "../dao/emailTemplate.dao";
import { sequelize } from "../models";
import { LanguageService } from "../services/language.service";
import { Common } from "../../utils/common";
import { AppError } from "../../utils/errors";
import * as nodemailer from 'nodemailer';
import { SESClient, SendRawEmailCommand, CreateTemplateCommand, SendEmailCommand, SendBulkTemplatedEmailCommand, ListTemplatesCommand, Template, SendTemplatedEmailCommand } from '@aws-sdk/client-ses';
import { AttachmentService } from './attachment.service';
import AppCache from "../../utils/appCache";
import * as path from 'path';
import * as fs from 'fs';

const sesClient = new SESClient({
    region: process.env.SES_REGION || 'us-west-1',
    credentials: {
        accessKeyId: process.env.SES_ACCESS_KEY!,
        secretAccessKey: process.env.SES_ACCESS_SECRET!,
    },
});

let transporter: nodemailer.Transporter;
if (process.env.SMTP_HOST) {
    transporter = nodemailer.createTransport({
        host: process.env.SMTP_HOST,
        port: parseInt(process.env.SMTP_PORT || '587'),
        secure: process.env.SMTP_PORT === '465', // true for 465, false for other ports
        auth: {
            user: process.env.SMTP_USER,
            pass: process.env.SMTP_PASSWORD,
        },
    });
} else {
    transporter = nodemailer.createTransport({
        streamTransport: true,
        buffer: true, // Ensures the raw message is returned as a buffer
    });
}

export class EmailTemplateService {
    private accountId: number | null;
    private userId: number | null;
    private language: string;
    private scope: string[] | null;
    private config: userConfig | null;
    private emailTemplateDao: EmailTemplateDao;
    private languageService: LanguageService
    private attachmentService: AttachmentService
    constructor(
        private options: {
            language: string;
            scope: string[] | null;
            accountId: number | null;
            userId: number | null;
            config: userConfig | null;
        } = {
                language: process.env.DEFAULT_LANGUAGE_CODE!,
                scope: null,
                config: null,
                userId: null,
                accountId: null,
            }
    ) {
        this.language = options.language;
        this.scope = options.scope ?? [];
        this.config = options.config ?? null;
        this.userId = options.userId ?? null;
        this.accountId = options.accountId ?? null;
        this.emailTemplateDao = new EmailTemplateDao({
            userId: this.userId,
            accountId: this.accountId,
            language: this.language,
            scope: this.scope,
            config: this.config
        });
        this.languageService = new LanguageService({
            userId: this.userId,
            accountId: this.accountId,
            language: this.language,
            scope: this.scope,
            config: this.config
        });
        this.attachmentService = new AttachmentService({
            userId: this.userId,
            accountId: this.accountId,
            language: this.language,
            scope: this.scope,
            config: this.config
        });
    }

    // create a new email template
    createEmailTemplate = async (data: EmailTemplateCreateServiceInput): Promise<EmailTemplateObjectInteface> => {
        const transaction = await sequelize.transaction();
        try {
            const { title, code, subject, body, status, emailTemplateAttachmentIds } = data;
            const languages: LanguageInfo = await this.languageService.getRequestedAndDefaultLanguage();
            const emailTemplateObj: EmailTemplateObject = { code: Common.slugify(code), status: status ?? Constants.EMAIL_TEMPLATE.STATUS.ACTIVE };
            const emailTemplateContentObj: EmailTemplateContentObject = { title, subject, body };
            let messageReplacements = Common.fetchShortCodes(emailTemplateContentObj.body as unknown as string);
            let subjectReplacements = Common.fetchShortCodes(emailTemplateContentObj.subject);
            const replacementArray = [...new Set([...messageReplacements, ...subjectReplacements])];
            emailTemplateObj.replacements = replacementArray.join(',')
            emailTemplateContentObj.bodyText = (emailTemplateContentObj.body ? await Common.convertHtmlToText(emailTemplateContentObj.body.toString()) : '') as unknown as Text
            const doExistsByCodeInput: EmailTemplateDoExistsByCodeDaoInput = { code: emailTemplateObj.code };
            const exists = await this.emailTemplateDao.doExistsByCode(doExistsByCodeInput, { transaction });
            if (!exists) {
                if (emailTemplateAttachmentIds) {
                    // verify if attachments are valid
                    const verifyAttachments = await this.attachmentService.verifyFile(emailTemplateAttachmentIds);
                    if (verifyAttachments.length != emailTemplateAttachmentIds.length) {
                        throw new AppError(400, 'INVALID_DUPLICATE_ATTACHMENT_FOUND', { emailTemplateAttachments: 'INVALID_DUPLICATE_ATTACHMENT_FOUND' });
                    }
                }
                const createEmailTemplateInput: EmailTemplateCreateDaoInput = { emailTemplateObj, emailTemplateContentObj, emailTemplateAttachments: emailTemplateAttachmentIds, languages };
                let emailTemplateIdentifier: number = await this.emailTemplateDao.create(createEmailTemplateInput, { transaction });
                const setSortOrderInput: EmailTemplateSetSortOrderDaoInput = { id: emailTemplateIdentifier };
                await this.emailTemplateDao.setSortOrder(setSortOrderInput, { transaction })
                const getEmailTemplateByIdInput: EmailTemplateGetByIdDaoInput = { id: emailTemplateIdentifier };
                let emailTemplate: EmailTemplateObjectInteface = await this.emailTemplateDao.getEmailTemplateById(getEmailTemplateByIdInput, { transaction })
                await transaction.commit();
                return emailTemplate;
            }
            throw new AppError(400, 'EMAIL_TEMPLATE_ALREADY_EXISTS', { name: 'EMAIL_TEMPLATE_ALREADY_EXISTS' });
        } catch (err) {
            await transaction.rollback();
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // update the existing email template
    updateEmailTemplate = async (data: EmailTemplateUpdateServiceInput): Promise<EmailTemplateObjectInteface> => {
        const transaction = await sequelize.transaction();
        try {
            const { id, title, code, subject, body, status, emailTemplateAttachmentIds } = data;
            const languages: LanguageInfo = await this.languageService.getRequestedLanguage();
            const emailTemplateObj: EmailTemplateObject = { code: Common.slugify(code), status: status ?? Constants.EMAIL_TEMPLATE.STATUS.ACTIVE };
            const emailTemplateContentObj: EmailTemplateContentObject = { title, subject, body };
            emailTemplateContentObj.bodyText = (emailTemplateContentObj.body ? await Common.convertHtmlToText(emailTemplateContentObj.body.toString()) : '') as unknown as Text;
            const doExistsByIdInput: EmailTemplateDoExistsByIdDaoInput = { id };
            const exists = await this.emailTemplateDao.doExistsById(doExistsByIdInput, { transaction });
            if (exists) {
                if (emailTemplateObj.code) {
                    const codeInUseInput: EmailTemplateDoExistsByCodeDaoInput = { code: emailTemplateObj.code, excludeId: id };
                    let codeInUse = await this.emailTemplateDao.doExistsByCode(codeInUseInput, { transaction });
                    if (codeInUse) {
                        throw new AppError(400, 'EMAIL_TEMPLATE_ALREADY_EXISTS', { name: 'EMAIL_TEMPLATE_ALREADY_EXISTS' });
                    }
                }
                if (emailTemplateAttachmentIds) {
                    // verify if attachments are valid
                    const verifyAttachments = await this.attachmentService.verifyFile(emailTemplateAttachmentIds);
                    if (verifyAttachments.length != emailTemplateAttachmentIds.length) {
                        throw new AppError(400, 'INVALID_DUPLICATE_ATTACHMENT_FOUND', { emailTemplateAttachments: 'INVALID_DUPLICATE_ATTACHMENT_FOUND' });
                    }
                }
                const updateEmailTemplateInput: EmailTemplateUpdateDaoInput = { id, emailTemplateObj, emailTemplateContentObj, emailTemplateAttachments: emailTemplateAttachmentIds, languages };
                await this.emailTemplateDao.update(updateEmailTemplateInput, { transaction });
                const getEmailTemplateByIdInput: EmailTemplateGetByIdDaoInput = { id };
                let emailTemplate: EmailTemplateObjectInteface = await this.emailTemplateDao.getEmailTemplateById(getEmailTemplateByIdInput, { transaction });
                await transaction.commit();
                return emailTemplate;
            }
            throw new AppError(404, 'EMAIL_TEMPLATE_TYPE_NOT_FOUND', { name: 'EMAIL_TEMPLATE_TYPE_NOT_FOUND' });
        } catch (err) {
            await transaction.rollback();
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // delete the existing email template
    deleteEmailTemplate = async (data: EmailTemplateDeleteServiceInput): Promise<EmailTemplateObjectInteface> => {
        const transaction = await sequelize.transaction();
        try {
            const deleteEmailTemplateInput: EmailTemplateDeleteDaoInput = { id: data.id };
            await this.emailTemplateDao.delete(deleteEmailTemplateInput, { transaction });
            const getEmailTemplateByIdInput: EmailTemplateGetByIdDaoInput = { id: data.id, expanded: true, paranoid: false };
            let emailTemplate: EmailTemplateObjectInteface = await this.emailTemplateDao.getEmailTemplateById(getEmailTemplateByIdInput, { transaction })
            await transaction.commit();
            return emailTemplate;
        } catch (err) {
            await transaction.rollback();
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // get email template Type by id
    getById = async (data: EmailTemplateGetByIdServiceInput): Promise<EmailTemplateObjectInteface> => {
        try {
            const { id, expanded = true } = data;
            const getEmailTemplateByIdInput: EmailTemplateGetByIdDaoInput = { id, expanded };
            let emailTemplate: EmailTemplateObjectInteface = await this.emailTemplateDao.getEmailTemplateById(getEmailTemplateByIdInput);
            return emailTemplate;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // get email template by code
    getByCode = async (data: EmailTemplateGetByCodeServiceInput): Promise<EmailTemplateObjectInteface> => {
        try {
            const { code, expanded = true } = data;
            const getEmailTemplateByCodeInput: EmailTemplateGetByCodeDaoInput = { code, expanded };
            let emailTemplate: EmailTemplateObjectInteface = await this.emailTemplateDao.getEmailTemplateByCode(getEmailTemplateByCodeInput);
            return emailTemplate;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // list email templates
    getEmailTemplates = async (data: EmailTemplateGetListServiceInput): Promise<EmailTemplatePaginatedList> => {
        try {
            const { listRequest, language = process.env.DEFAULT_LANGUAGE_CODE! } = data;
            const { page, perPage } = listRequest;
            const getEmailTemplateListInput: EmailTemplateGetListDaoInput = { listRequest, language };
            let emailTemplates: EmailTemplatePaginatedData = await this.emailTemplateDao.getEmailTemplateList(getEmailTemplateListInput);
            let totalPages = Common.getTotalPages(emailTemplates.count, perPage);
            return {
                data: emailTemplates.rows,
                page: page,
                perPage: perPage,
                totalRecords: emailTemplates.count,
                totalPages: totalPages
            } as unknown as EmailTemplatePaginatedList;

        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // list all email templates
    getAllEmailTemplates = async (data: EmailTemplateGetAllListServiceInput): Promise<EmailTemplateObjectSummaryInteface[]> => {
        try {
            const { listRequest, language = process.env.DEFAULT_LANGUAGE_CODE! } = data;
            const getAllEmailTemplatesInput: EmailTemplateGetAllListDaoInput = { listRequest, language };
            let emailTemplates: EmailTemplateObjectSummaryInteface[] = await this.emailTemplateDao.getAllEmailTemplates(getAllEmailTemplatesInput);
            return emailTemplates;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // list email template revisions
    getEmailTemplatesRevisions = async (data: EmailTemplateGetRevisionsServiceInput): Promise<EmailTemplatePaginatedList> => {
        try {
            const { id, listRequest, language = process.env.DEFAULT_LANGUAGE_CODE! } = data;
            const { page, perPage } = listRequest;
            const getEmailTemplateRevisionListInput: EmailTemplateGetRevisionListDaoInput = { id, listRequest, language };
            let emailTemplates: EmailTemplatePaginatedData = await this.emailTemplateDao.getEmailTemplateRevisionList(getEmailTemplateRevisionListInput);
            let totalPages = Common.getTotalPages(emailTemplates.count, perPage);
            return {
                data: emailTemplates.rows,
                page: page,
                perPage: perPage,
                totalRecords: emailTemplates.count,
                totalPages: totalPages
            } as unknown as EmailTemplatePaginatedList;

        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // Restore revision
    restoreRevision = async (data: EmailTemplateRestoreRevisionServiceInput): Promise<EmailTemplateObjectInteface> => {
        const transaction = await sequelize.transaction();
        try {
            const restoreRevisionInput: EmailTemplateRestoreRevisionDaoInput = { id: data.id };
            let restoredEntiryId: number = await this.emailTemplateDao.restoreRevision(restoreRevisionInput, { transaction });
            const getEmailTemplateByIdInput: EmailTemplateGetByIdDaoInput = { id: restoredEntiryId };
            let emailTemplate = await this.emailTemplateDao.getEmailTemplateById(getEmailTemplateByIdInput, { transaction });
            await transaction.commit();
            return emailTemplate;

        } catch (err) {
            await transaction.rollback();
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // get email template id from code
    getEmailTemplateId = async (data: EmailTemplateGetIdServiceInput): Promise<number> => {
        try {
            const getEmailTemplateIdInput: EmailTemplateGetIdDaoInput = { code: data.code };
            let emailTemplateId = await this.emailTemplateDao.getIdFromCode(getEmailTemplateIdInput);
            return emailTemplateId;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }

    }

    // send email template
    sendEmailTemplate = async (code: string, to: string[], replacements: any = {}, attachments: number[] = [], cc: string[] = [], bcc: string[] = []) => {
        try {
            let key = "setting_" + this.accountId
            let templateSettings = AppCache.get(key) as Record<string, unknown>;
            let senderEmail = templateSettings['sender-email'] ? templateSettings['sender-email'] : process.env.FROM_EMAIL_EMAIL!;
            let senderName = templateSettings['sender-name'] ? templateSettings['sender-name'] : process.env.FROM_EMAIL_NAME!;
            let header = templateSettings['template-header'] ? templateSettings['template-header'] : "";
            let footer = templateSettings['template-footer'] ? templateSettings['template-footer'] : "";
            const getEmailTemplateByCodeInput: EmailTemplateGetByCodeServiceInput = { code };
            let emailTemplate = await this.getByCode(getEmailTemplateByCodeInput);

            let htmlTemplatePath = path.join(__dirname, '../config/emailTemplate.html');
            if (!fs.existsSync(htmlTemplatePath)) {
                htmlTemplatePath = path.join(process.cwd(), 'src/api/config/emailTemplate.html');
            }
            let htmlTemplate = await Common.readHTMLFile(htmlTemplatePath);

            let content = emailTemplate.body as unknown as string ?? '' as unknown as string;
            console.log('verificationCode :---', '{{verificationCode}}');
            console.log('verificationCode-code :---', '{{verification-code}}');
            // Handlebars cannot process variables with hyphens directly like {{verification-code}}
            content = content.replace(/\{\{\s*verification-code\s*\}\}/g, '{{verificationCode}}');
            let subject = emailTemplate.subject as unknown as string ?? '' as unknown as string;
            let templateFn = handlebars.compile(htmlTemplate);
            let mailtosend = templateFn({ content: content, header: header, footer: footer });
            var templateToSend = handlebars.compile(mailtosend);
            var htmlToSend = templateToSend(replacements);
            var mailSubject = handlebars.compile(subject);
            var updatedSubject = mailSubject(replacements);
            let text = await Common.convertHtmlToText(htmlToSend); // Ensure 'convertHtmlToText' is imported
            let mailAttachments: mailAttachment[] = attachments.length ? await this.attachmentService.getAttachmentsForEmail(attachments) : []
            console.log("TO Array:", to);
            console.log("TO Joined:", to.join(', '));
            const mailOptions = {
                from: `${senderName} <${senderEmail}>`,
                to: to.map(e => e.includes('<') ? e.substring(e.indexOf('<') + 1, e.indexOf('>')) : e).join(', '),
                cc: cc.map(e => e.includes('<') ? e.substring(e.indexOf('<') + 1, e.indexOf('>')) : e).join(', '),
                bcc: bcc.map(e => e.includes('<') ? e.substring(e.indexOf('<') + 1, e.indexOf('>')) : e).join(', '),
                subject: updatedSubject,
                text: text as string,
                html: htmlToSend,
                attachments: mailAttachments
            }
            if (process.env.SMTP_HOST) {
                await transporter.sendMail(mailOptions);
            } else {
                const mail = await transporter.sendMail(mailOptions);
                const rawMessage = mail.message as Buffer;
                const command = new SendRawEmailCommand({
                    RawMessage: {
                        Data: rawMessage,
                    },
                });
                const response = await sesClient.send(command);
            }
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // set sort order
    setSortOrder = async (data: EmailTemplateSetSortOrderServiceInput): Promise<boolean> => {
        const transaction = await sequelize.transaction();
        try {
            const setSortOrderInput: EmailTemplateSetSortOrderDaoInput = { id: data.id, before: data.before, after: data.after };
            let sortOrder: boolean = await this.emailTemplateDao.setSortOrder(setSortOrderInput, { transaction });
            await transaction.commit();
            return sortOrder;
        } catch (err) {
            await transaction.rollback();
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // update email Template status
    updateStatus = async (data: EmailTemplateUpdateStatusServiceInput): Promise<EmailTemplateObjectInteface> => {
        const transaction = await sequelize.transaction();
        try {
            const { id, status } = data;
            const getEmailTemplateByIdInput: EmailTemplateGetByIdDaoInput = { id };
            let emailTemplate = await this.emailTemplateDao.getEmailTemplateById(getEmailTemplateByIdInput, { transaction });
            if (emailTemplate) {
                const updateEmailTemplateStatusInput: EmailTemplateUpdateStatusDaoInput = { id, status };
                await this.emailTemplateDao.updateStatus(updateEmailTemplateStatusInput, { transaction });
                let emailTemplate = await this.getById({ id, expanded: true });
                await transaction.commit();
                return emailTemplate
            } else {
                throw new AppError(404, 'EMAIL_TEMPLATE_NOT_FOUND', { id: 'EMAIL_TEMPLATE_NOT_FOUND' });
            }
        } catch (err) {
            await transaction.rollback();
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    ensureTemplateExists = async (templateName: string, templateContent: Template): Promise<void> => {
        try {
            const list = await sesClient.send(new ListTemplatesCommand({}));
            const exists = list.TemplatesMetadata?.some((t) => t.Name === templateName);
            if (!exists) {
                await sesClient.send(new CreateTemplateCommand({ Template: templateContent }));
            } else {
            }
            return;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    sendBulkEmailsBCC = async (users: any, templateName: string, templateContent: Template) => {
        try {
            await this.ensureTemplateExists(templateName, templateContent);

            const destinations = users.map((user: any) => ({
                Destination: { ToAddresses: [user.email] },
                ReplacementTemplateData: JSON.stringify(user.templateData),
            }));

            const command = new SendBulkTemplatedEmailCommand({
                Source: process.env.FROM_EMAIL_EMAIL!,
                Template: templateName,
                DefaultTemplateData: JSON.stringify({}),
                Destinations: destinations,
            });
            let result = await sesClient.send(command)
            return result;
        } catch (err) {
            console.error("❌ Error sending bulk BCC emails:", err);
        }
    }
}
