import {
  BadRequestException, ConflictException, ForbiddenException, Injectable, Logger, NotFoundException,
} from '@nestjs/common';
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
import { DataSource, Repository } from 'typeorm';

import { AuditReport } from '../entities/audit-report.entity';
import { AuditReportClause } from '../entities/audit-report-clause.entity';
import { AuditReportVersion } from '../entities/audit-report-version.entity';
import { MailsService } from '../../mails/mails.service';
import { buildReportSubmittedEmail } from '../templates/report-submitted-email-template';
import { buildReportApprovedEmail } from '../templates/report-approved-email-template';
import { buildReportReturnedEmail } from '../templates/report-returned-email-template';
import { AuditReportStorageService } from '../../audit-schedules/services/audit-report-storage.service';
import { ReportBuilderService } from './report-builder.service';
import { ReportRenderService } from './report-render.service';
import { ReportPdfService } from './report-pdf.service';
import { createHmac, timingSafeEqual } from 'crypto';
/** Role ids that receive the "report submitted for approval" email (same ids as the audit-request emails). */
const SUPER_ADMIN_ROLE_ID = 1;
const COORDINATOR_ROLE_ID = 5;

/**
 * draft ──submit──▶ submitted ──approve──▶ approved
 *   ▲                  │                       │
 *   └──── returned ◀───┘ (return)              └─ reopen (approver) ─▶ returned, version + 1
 */
@Injectable()
export class ReportWorkflowService {
  private readonly logger = new Logger(ReportWorkflowService.name);

  constructor(
    @InjectRepository(AuditReport, 'scheme_dbs') private readonly reports: Repository<AuditReport>,
    @InjectRepository(AuditReportClause, 'scheme_dbs') private readonly clauses: Repository<AuditReportClause>,
    @InjectRepository(AuditReportVersion, 'scheme_dbs') private readonly versions: Repository<AuditReportVersion>,
    @InjectDataSource('scheme_dbs') private readonly db: DataSource,
    private readonly builder: ReportBuilderService,
    private readonly renderer: ReportRenderService,
    private readonly mails: MailsService,
    private readonly storage: AuditReportStorageService,
    private readonly pdf: ReportPdfService,
  ) { }

  // ───────────────────────── submit ─────────────────────────
  async submit(id: number, userId: number) {
    const r = await this.builder.loadForWrite(id, userId);

    // Completeness check: every checkpoint answered, conclusion chosen, NC rows have evidence.
    // rows the auditor disabled (extras.disabled_clauses) are not printed, so they need no status
    const off = new Set<number>((Array.isArray(r.extras_json?.disabled_clauses) ? r.extras_json.disabled_clauses : []).map(Number));
    const rows = (await this.clauses.find({ where: { report_id: id } })).filter((c) => !off.has(c.id));
    const unanswered = rows
      .filter((c) => !c.status)
      .map((c) => (c.title && c.group_key === 'clause' ? `${c.code} (${c.title})` : c.title || c.code));
    const problems: string[] = [];
    if (unanswered.length) {
      problems.push(
        `${unanswered.length} checkpoint(s) have no status (C / NC / OFI / NA): ${unanswered.slice(0, 8).join(', ')}${unanswered.length > 8 ? ' …' : ''}`,
      );
    }
    const noEvidence = rows.filter((c) => (c.status === 'NC' || c.status === 'OFI') && !(c.evidence_text || '').trim());
    if (noEvidence.length) problems.push(`${noEvidence.length} NC/OFI checkpoint(s) have no evidence written.`);
    if (r.stage_type === 'stage1' ? !r.recommendation : !r.conclusion) {
      problems.push(r.stage_type === 'stage1' ? 'Choose the Stage 2 recommendation.' : 'Choose the conclusion and recommendation.');
    }
    if (problems.length) throw new BadRequestException({ message: 'The report is not complete yet.', problems });

    r.status = 'submitted';
    r.submitted_by = userId;
    r.submitted_at = new Date();
    r.return_reason = null;
    await this.reports.save(r);
    // await this.notifyApprovers(r, 'submitted for review');
    this.notifyReportSubmitted(id, userId);
    return { ok: true, status: r.status };
  }

  // ───────────────────────── approve ─────────────────────────
  /**
   * Freezes the data, creates the final PDF and stores it with the existing audit-report storage service,
   * then points the audit row at it (stg1_audit_report for Stage 1, stg2_audit_report for every other stage)
   * and marks the row QAQC-approved, so the report tracker, the QAQC queue and the client portal all see it.
   */
  async approve(id: number, userId: number) {
    const r = await this.builder.getReportOrFail(id);
    if (!(await this.builder.canApprove(userId, r.company_id))) {
      throw new ForbiddenException("Only the scheme department of this company's scheme can approve reports.");
    }
    if (r.status !== 'submitted') throw new ConflictException(`Only a submitted report can be approved (this one is ${r.status}).`);

    const vm: any = await this.builder.buildViewModel(id);
    vm.meta.draft = false;
    vm.meta.status = 'approved';
    vm.meta.approved_on = new Date().toISOString().slice(0, 10);

    // PDF + Word from one build: the PDF is stored, both are emailed (2026-11-06)
    const { pdf, docx } = await this.pdf.buildPdfAndDocx(vm, r);
    const stage1 = r.stage_type === 'stage1';
    const saved = await this.storage.saveReport(stage1 ? 'stage1_report' : 'stage2_report', r.audit_schedule_row_id, {
      originalname: `${r.report_no ?? `report-${r.id}`}-v${r.version}.pdf`,
      mimetype: 'application/pdf',
      buffer: pdf,
      size: pdf.length,
    });
    const column = stage1 ? 'stg1_audit_report' : 'stg2_audit_report';

    await this.reports.manager.transaction(async (m) => {
      r.status = 'approved';
      r.approved_by = userId;
      r.approved_at = new Date();
      r.snapshot_json = JSON.stringify(vm);
      r.pdf_path = saved.dbPath;
      await m.save(r);
      await m.save(
        m.create(AuditReportVersion, {
          report_id: id, version: r.version, snapshot_json: r.snapshot_json, pdf_path: saved.dbPath,
          note: 'Approved', generated_by: userId,
        }),
      );
      // With several reports on one audit (one per standard + a combined one) the audit row points at the
      // combined report when it exists; otherwise at the latest approved one.
      const [cmb] = await m.query(
        'SELECT id FROM audit_reports WHERE audit_schedule_row_id = ? AND standard_id = 0 AND id <> ? LIMIT 1',
        [r.audit_schedule_row_id, id],
      );
      const movePointer = r.standard_id === 0 || !cmb;
      await m.query(
        `UPDATE audit_schedule_rows
            SET ${movePointer ? `\`${column}\` = ?,` : ''} report_qaqc_approved = 1, report_qaqc_approved_by = ?,
                report_qaqc_approved_at = NOW(), report_qaqc_rejected_reason = NULL, updated_at = NOW()
          WHERE id = ?`,
        movePointer ? [saved.dbPath, userId, r.audit_schedule_row_id] : [userId, r.audit_schedule_row_id],
      );
    });

    this.notifyReportApproved(r, vm, pdf, userId, docx);
    return { ok: true, status: r.status, version: r.version, pdf_path: saved.dbPath, audit_row_column: column };
  }

  /**
   * Emails every coordinator and super admin that a report was submitted for approval, with the PDF attached.
   * The submitting auditor also receives a copy (subject marked "(copy)"). One email per recipient.
   * Sent FROM the auditor's mailbox. Never throws.
   */
  private async notifyReportSubmitted(id: number, auditorUserId: number): Promise<void> {
    try {
      const r = await this.builder.getReportOrFail(id);
      const vm: any = await this.builder.buildViewModel(id);
      const { pdf, docx } = await this.pdf.buildPdfAndDocx(vm, r); // Word file attached too (2026-11-06)

      // Coordinators + super admins
      const staff: { id: number; email: string; firstName: string; lastName: string }[] = await this.db.query(
        `SELECT DISTINCT u.id, u.email, u.firstName, u.lastName
           FROM users u
           INNER JOIN user_roles ur ON ur.user_id = u.id
          WHERE ur.role_id IN (?, ?)
            AND u.email IS NOT NULL AND u.email <> ''
            AND u.isActive = 1`,
        [SUPER_ADMIN_ROLE_ID, COORDINATOR_ROLE_ID],
      );

      // The submitting auditor (receives a copy)
      const [auditor] = await this.db.query(
        `SELECT email, firstName, lastName FROM users WHERE id = ? LIMIT 1`,
        [auditorUserId],
      );
      // Fixed extra recipients from the environment (same treatment as coordinators / super admins)
      const extraRecipients = (process.env.AUDIT_REPORT_SUBMIT_EXTRA_EMAILS || '')
        .split(',')
        .map((e) => e.trim())
        .filter(Boolean)
        .map((email) => ({ email, firstName: '', lastName: '' }));
      // Build the recipient list, without duplicates
      const seen = new Set<string>();
      const targets = [
        ...staff.map((u) => ({ ...u, isCopy: false })),
        ...extraRecipients.map((u) => ({ ...u, isCopy: false })),
        ...(auditor?.email ? [{ ...auditor, isCopy: true }] : []),
      ].filter((u) => {
        const key = String(u.email).trim().toLowerCase();
        if (!key || seen.has(key)) return false;
        seen.add(key);
        return true;
      });

      if (!targets.length) {
        this.logger.warn(`[REPORT-MAIL] no recipients for report #${id}`);
        return;
      }

      const auditorName =
        `${auditor?.firstName ?? ''} ${auditor?.lastName ?? ''}`.trim() || auditor?.email || 'Auditor';
      const reportNo = r.report_no ?? `report-${r.id}`;
      const companyName = vm.cover?.company ?? vm.org?.name ?? '—';
      const submittedAt = new Date().toLocaleString('en-GB', {
        timeZone: 'Asia/Dubai', day: '2-digit', month: 'short', year: 'numeric',
        hour: '2-digit', minute: '2-digit',
      });
      const conclusion = vm.conclusion_options?.find((o: any) => o.on)?.text ?? '—';

      for (const u of targets) {
        const recipientName = `${u.firstName ?? ''} ${u.lastName ?? ''}`.trim() || 'Team';

        const approveUrl = (u as any).id
          ? `${process.env.APP_URL || 'https://crm.qrsyst.com'}/approve-report?token=${this.signApprovalToken(r.id, Number((u as any).id))}`
          : undefined;

        const html = buildReportSubmittedEmail({
          recipientName,
          auditorName,
          auditorEmail: auditor?.email ?? '',
          companyName,
          reportNo,
          stageLabel: vm.meta?.stage_label ?? '',
          standardName: vm.meta?.standard_name ?? '',
          auditDate: vm.org?.audit_date ?? '',
          leadAuditor: vm.lead_auditor ?? auditorName,
          conclusion,
          ncMajor: vm.nc_totals?.major ?? 0,
          ncMinor: vm.nc_totals?.minor ?? 0,
          ncObs: vm.nc_totals?.obs ?? 0,
          submittedAt,
          attachmentName: docx ? `${reportNo}.pdf + ${reportNo}.docx` : `${reportNo}.pdf`,
          reviewUrl: `${process.env.APP_URL || 'https://crm.qrsyst.com'}/modules/audits/${r.audit_schedule_row_id}`,
          isDraftWatermark: vm.meta?.draft !== false,
          approveUrl,
        });

        try {
          await this.mails.sendAsUser(auditorUserId, {
            to: u.email,
            subject: `${reportNo} — ${companyName} · Report submitted for approval${u.isCopy ? ' (copy)' : ''}`,
            html,
            attachments: [
              { filename: `${reportNo}.pdf`, content: pdf, contentType: 'application/pdf' },
              ...(docx ? [{ filename: `${reportNo}.docx`, content: docx, contentType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' }] : []),
            ],
          });
        } catch (e: any) {
          this.logger.warn(`[REPORT-MAIL] to ${u.email} failed: ${e?.message}`);
        }
      }

      this.logger.log(
        `[REPORT-MAIL] report #${id} emailed to ${targets.length} recipient(s) from user ${auditorUserId}`,
      );
    } catch (e: any) {
      this.logger.warn(`[REPORT-MAIL] submit notification failed for report #${id}: ${e?.message}`);
    }
  }
  /**
 * Tells the auditor(s) who created or submitted the report that it is approved.
 * Sends the final PDF, from the approver's mailbox. Never throws.
 */
  private async notifyReportApproved(r: AuditReport, vm: any, pdf: Buffer, approverId: number, docx?: Buffer): Promise<void> {
    try {
      const authorIds = Array.from(new Set([r.created_by, r.submitted_by].filter((x): x is number => !!x)));
      if (!authorIds.length) return;

      const authors: { email: string; firstName: string; lastName: string }[] = await this.db.query(
        `SELECT email, firstName, lastName FROM users
          WHERE id IN (${authorIds.map(() => '?').join(',')})
            AND email IS NOT NULL AND email <> ''`,
        authorIds,
      );

      const [approver] = await this.db.query(
        `SELECT email, firstName, lastName FROM users WHERE id = ? LIMIT 1`,
        [approverId],
      );
      const approverName =
        `${approver?.firstName ?? ''} ${approver?.lastName ?? ''}`.trim() || approver?.email || 'Scheme Department';

      const reportNo = r.report_no ?? `report-${r.id}`;
      const companyName = vm.cover?.company ?? vm.org?.name ?? '—';
      const approvedAt = new Date().toLocaleString('en-GB', {
        timeZone: 'Asia/Dubai', day: '2-digit', month: 'short', year: 'numeric',
        hour: '2-digit', minute: '2-digit',
      });
      const conclusion = vm.conclusion_options?.find((o: any) => o.on)?.text ?? '—';
      const baseUrl = process.env.APP_URL || 'https://crm.qrsyst.com';

      const seen = new Set<string>();
      for (const a of authors) {
        const key = String(a.email).trim().toLowerCase();
        if (seen.has(key)) continue;
        seen.add(key);

        const html = buildReportApprovedEmail({
          recipientName: `${a.firstName ?? ''} ${a.lastName ?? ''}`.trim() || 'Team',
          approverName,
          companyName,
          reportNo,
          stageLabel: vm.meta?.stage_label ?? '',
          standardName: vm.meta?.standard_name ?? '',
          auditDate: vm.org?.audit_date ?? '',
          conclusion,
          ncMajor: vm.nc_totals?.major ?? 0,
          ncMinor: vm.nc_totals?.minor ?? 0,
          ncObs: vm.nc_totals?.obs ?? 0,
          approvedAt,
          version: r.version,
          attachmentName: docx ? `${reportNo}.pdf + ${reportNo}.docx` : `${reportNo}.pdf`,
          reviewUrl: `${baseUrl}/modules/audits/${r.audit_schedule_row_id}`,
        });

        try {
          await this.mails.sendAsUser(approverId, {
            to: a.email,
            subject: `${reportNo} — ${companyName} · Audit report approved`,
            html,
            attachments: [
              { filename: `${reportNo}.pdf`, content: pdf, contentType: 'application/pdf' },
              ...(docx ? [{ filename: `${reportNo}.docx`, content: docx, contentType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' }] : []),
            ],
          });
        } catch (e: any) {
          this.logger.warn(`[REPORT-MAIL] approved notice to ${a.email} failed: ${e?.message}`);
        }
      }
    } catch (e: any) {
      this.logger.warn(`[REPORT-MAIL] approved notification failed for report #${r.id}: ${e?.message}`);
    }
  }
   /**
   * Tells the report's author(s) that it was returned or reopened. Sent from the approver's mailbox.
   * Never throws.
   */
  private async notifyReportReturned(
    r: AuditReport,
    reason: string,
    action: 'returned' | 'reopened',
    actorId: number,
  ): Promise<void> {
    try {
      const authorIds = Array.from(new Set([r.created_by, r.submitted_by].filter((x): x is number => !!x)));
      if (!authorIds.length) return;

      const authors: { email: string; firstName: string; lastName: string }[] = await this.db.query(
        `SELECT email, firstName, lastName FROM users
          WHERE id IN (${authorIds.map(() => '?').join(',')})
            AND email IS NOT NULL AND email <> ''`,
        authorIds,
      );
      const [actor] = await this.db.query(
        `SELECT email, firstName, lastName FROM users WHERE id = ? LIMIT 1`,
        [actorId],
      );
      const actorName =
        `${actor?.firstName ?? ''} ${actor?.lastName ?? ''}`.trim() || actor?.email || 'Scheme Department';

      const vm: any = await this.builder.buildViewModel(r.id);
      const reportNo = r.report_no ?? `report-${r.id}`;
      const companyName = vm.cover?.company ?? vm.org?.name ?? '—';
      const actedAt = new Date().toLocaleString('en-GB', {
        timeZone: 'Asia/Dubai', day: '2-digit', month: 'short', year: 'numeric',
        hour: '2-digit', minute: '2-digit',
      });

      const seen = new Set<string>();
      for (const a of authors) {
        const key = String(a.email).trim().toLowerCase();
        if (seen.has(key)) continue;
        seen.add(key);

        const html = buildReportReturnedEmail({
          recipientName: `${a.firstName ?? ''} ${a.lastName ?? ''}`.trim() || 'Team',
          actorName,
          companyName,
          reportNo,
          stageLabel: vm.meta?.stage_label ?? '',
          standardName: vm.meta?.standard_name ?? '',
          reason,
          action,
          version: r.version,
          actedAt,
          reviewUrl: `${process.env.APP_URL || 'https://crm.qrsyst.com'}/modules/audits/${r.audit_schedule_row_id}`,
        });

        try {
          await this.mails.sendAsUser(actorId, {
            to: a.email,
            subject: `${reportNo} — ${companyName} · Report ${action === 'reopened' ? 'reopened' : 'returned for correction'}`,
            html,
          });
        } catch (e: any) {
          this.logger.warn(`[REPORT-MAIL] returned notice to ${a.email} failed: ${e?.message}`);
        }
      }
    } catch (e: any) {
      this.logger.warn(`[REPORT-MAIL] returned notification failed for report #${r.id}: ${e?.message}`);
    }
  }
  // ───────────────────────── return / reopen ─────────────────────────
  async returnToAuditor(id: number, reason: string, userId: number) {
    const r = await this.builder.getReportOrFail(id);
    if (!(await this.builder.canApprove(userId, r.company_id))) {
      throw new ForbiddenException('Only the scheme department can return reports.');
    }
    if (r.status !== 'submitted') throw new ConflictException(`Only a submitted report can be returned (this one is ${r.status}).`);
    r.status = 'returned';
    r.return_reason = reason;
    await this.reports.save(r);
    this.notifyReportReturned(r, reason, 'returned', userId);
    return { ok: true, status: r.status };
  }
  // ───────────────────────── one-click approve from email ─────────────────────────
  private approvalSecret(): string {
    const secret = process.env.AUDIT_APPROVE_TOKEN_SECRET;
    if (!secret) throw new Error('AUDIT_APPROVE_TOKEN_SECRET is not set');
    return secret;
  }

  /** Signed, expiring token that identifies one report and one recipient. */
  signApprovalToken(reportId: number, userId: number, ttlHours = 72): string {
    const payload = Buffer.from(
      JSON.stringify({ r: reportId, u: userId, e: Date.now() + ttlHours * 3600_000 }),
    ).toString('base64url');
    const sig = createHmac('sha256', this.approvalSecret()).update(payload).digest('base64url');
    return `${payload}.${sig}`;
  }

  verifyApprovalToken(token: string): { r: number; u: number } {
    const [payload, sig] = String(token || '').split('.');
    if (!payload || !sig) throw new ForbiddenException('Invalid approval link.');

    const expected = createHmac('sha256', this.approvalSecret()).update(payload).digest('base64url');
    const ok = sig.length === expected.length && timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
    if (!ok) throw new ForbiddenException('Invalid approval link.');

    const data = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8'));
    if (Date.now() > data.e) {
      throw new ForbiddenException('This approval link has expired. Please open the report in the system.');
    }
    return { r: Number(data.r), u: Number(data.u) };
  }

  /** Called by the confirmation page. Approval runs as the recipient, so their permissions are checked. */
  async approveByToken(token: string) {
    const { r, u } = this.verifyApprovalToken(token);
    return this.approve(r, u);
  }
  /** Re-opens an APPROVED report for correction. The old PDF stays as a version; the next approval is version + 1. */
  async reopen(id: number, reason: string, userId: number) {
    const r = await this.builder.getReportOrFail(id);
    if (!(await this.builder.canApprove(userId, r.company_id))) {
      throw new ForbiddenException('Only the scheme department can reopen approved reports.');
    }
    if (r.status !== 'approved') throw new ConflictException('Only an approved report can be reopened (locked reports cannot).');
    r.status = 'returned';
    r.version += 1;
    r.return_reason = reason;
    r.snapshot_json = null;
    await this.reports.save(r);
    this.notifyReportReturned(r, reason, 'reopened', userId);
    return { ok: true, status: r.status, version: r.version };
  }

  /** Locks an approved report permanently (e.g. once the certificate is issued). */
  async lock(id: number, userId: number) {
    const r = await this.builder.getReportOrFail(id);
    if (!(await this.builder.canApprove(userId, r.company_id))) {
      throw new ForbiddenException('Only the scheme department can lock reports.');
    }
    if (r.status !== 'approved') throw new ConflictException('Only an approved report can be locked.');
    r.status = 'locked';
    r.locked_at = new Date();
    await this.reports.save(r);
    return { ok: true, status: r.status };
  }

  // ───────────────────────── download of the stored final PDF ─────────────────────────
  async openFinalPdf(id: number) {
    const r = await this.builder.getReportOrFail(id);
    if (!r.pdf_path || !['approved', 'locked'].includes(r.status)) {
      throw new NotFoundException('No approved PDF for this report yet.');
    }
    return this.storage.openFile(r.pdf_path); // { stream, size, mimeType, filename }
  }

  // ───────────────────────── email ─────────────────────────
  private async userEmails(ids: number[]): Promise<string[]> {
    if (!ids.length) return [];
    const rows = await this.db.query(`SELECT email FROM users WHERE id IN (${ids.map(() => '?').join(',')})`, ids);
    return (rows as any[]).map((x) => String(x.email || '').trim()).filter(Boolean);
  }

  private async approverEmails(companyId: number): Promise<string[]> {
    const roles = ['superadmin', 'admin', ...(process.env.AUDIT_REPORT_APPROVER_ROLES || 'scheme').split(',')]
      .map((x) => x.toLowerCase().replace(/[\s_-]+/g, ''))
      .filter(Boolean);
    const rows = await this.db.query(
      `SELECT DISTINCT u.email, r.name AS role_name, u.primary_scheme, c.scheme AS company_scheme
         FROM users u
         INNER JOIN user_roles ur ON ur.user_id = u.id
         INNER JOIN roles r ON r.id = ur.role_id
         LEFT JOIN companies c ON c.id = ?
        WHERE u.email IS NOT NULL AND u.email <> '' AND u.isActive = 1`,
      [companyId],
    );
    const to = (rows as any[])
      .filter((x) => roles.includes(String(x.role_name).toLowerCase().replace(/[\s_-]+/g, '')))
      // admins see every scheme; the scheme role only its own
      .filter((x) => /admin/i.test(String(x.role_name).replace(/[\s_-]+/g, '')) || x.primary_scheme === x.company_scheme)
      .map((x) => String(x.email));
    const extra = (process.env.AUDIT_REPORT_NOTIFY_EMAIL || '').split(',').map((x) => x.trim()).filter(Boolean);
    return Array.from(new Set([...to, ...extra]));
  }

  private mailBody(r: AuditReport, line: string): string {
    const url = process.env.FRONTEND_URL || process.env.CRM_URL || '';
    const link = url ? `<p><a href="${url}/dashboard/audit-reports/${r.id}">Open the report</a></p>` : '';
    return `<p>Audit report <b>${r.report_no ?? r.id}</b> was ${line}.</p>${link}`;
  }

  private async send(to: string[], subject: string, html: string) {
    for (const addr of to) {
      try {
        await this.mails.sendCustom(addr, subject, html);
      } catch (e: any) {
        this.logger.warn(`[REPORT-MAIL] to ${addr} failed: ${e.message}`);
      }
    }
  }

  private async notifyApprovers(r: AuditReport, line: string) {
    await this.send(await this.approverEmails(r.company_id), `Audit report ${r.report_no} ${line}`, this.mailBody(r, line));
  }

  private async notifyAuthor(r: AuditReport, line: string) {
    const to = await this.userEmails([r.created_by, r.submitted_by].filter((x): x is number => !!x));
    await this.send(Array.from(new Set(to)), `Audit report ${r.report_no} ${line.split(':')[0]}`, this.mailBody(r, line));
  }
}