const InvoiceRepository = require('../../repositories/invoice/InvoiceRepository');
const UnitOfWork = require('../../database/UnitOfWork'); // NEW
const PoolInflowService = require('../pool/PoolInflowService');
const DocumentTemplateService = require('../template/DocumentTemplateService');
const MailService = require('../email/MailService');
const { GetObjectCommand, PutObjectCommand } = require('@aws-sdk/client-s3');
const { s3: sharedExoscaleClient } = require('../../utils/exoscaleUploader');
const { logger } = require('../../middleware/logger');
const puppeteer = require('puppeteer');
const pool = require('../../database/database');
const { normalizeLanguageCode } = require('../../utils/languageUtils');

const CoffeeShippingFeeService = require('../subscriptions/CoffeeShippingFeeService');

class InvoiceService {
  constructor() {
    this.repo = new InvoiceRepository();
  }

  _templateHasVars(template, varNames) {
    if (!template) return false;
    return varNames.every((name) => {
      const re = new RegExp(`{{\\s*${name}\\s*}}`);
      return re.test(template);
    });
  }

  _resolvePieceCountForQr(abonement) {
    const breakdown = Array.isArray(abonement?.pack_breakdown) ? abonement.pack_breakdown : [];
    const totalPacks = breakdown.reduce((sum, item) => sum + Number(item?.packs || item?.quantity || 0), 0);
    const piecesByPack = totalPacks ? totalPacks * 10 : null;
    if (piecesByPack != null) {
      if (piecesByPack >= 120) return 120;
      if (piecesByPack >= 60) return 60;
      return null;
    }

    const packGroup = String(abonement?.pack_group || '').toLowerCase();
    if (packGroup.includes('120')) return 120;
    if (packGroup.includes('60')) return 60;

    return null;
  }

  async _resolveShippingFeeItem({ abonement, vatRate, lang }) {
    const pieceCount = this._resolvePieceCountForQr(abonement);
    if (!pieceCount) return null;

    const shippingFee = await CoffeeShippingFeeService.get(pieceCount);
    const unitPrice = Number(shippingFee?.price || 0);
    if (!(unitPrice > 0)) return null;

    return {
      product_id: null,
      sku: `SHIPPING-${pieceCount}`,
      description: lang === 'de' ? `Versandkosten (${pieceCount} Stk.)` : `Shipping fee (${pieceCount} pcs)` ,
      quantity: 1,
      unit_price: unitPrice,
      tax_rate: vatRate,
    };
  }

  async _buildInvoiceItems({ abonement, vatRate, lang }) {
    const breakdown = Array.isArray(abonement?.pack_breakdown) ? abonement.pack_breakdown : [];
    const items = breakdown.length
      ? breakdown.map((b) => ({
          product_id: Number(b.coffee_table_id) || null,
          sku: `COFFEE-${b.coffee_table_id || 'N/A'}`,
          description: b.coffee_title || `Coffee subscription: ${b.coffee_table_id}`,
          quantity: Number(b.packs || 1),
          unit_price: Number(b.price_per_pack || 0),
          tax_rate: b.tax_rate != null ? Number(b.tax_rate) : vatRate,
        }))
      : [
          {
            product_id: null,
            sku: 'SUBSCRIPTION',
            description: `Subscription ${abonement?.pack_group || ''}`,
            quantity: 1,
            unit_price: Number(abonement?.price || 0),
            tax_rate: vatRate,
          },
        ];

    const shippingItem = await this._resolveShippingFeeItem({ abonement, vatRate, lang });
    if (shippingItem) {
      items.push(shippingItem);
    }

    return items;
  }

  async _buildQrCodeImageTag({ abonement }) {
    return '';
  }

  _escapeHtml(value) {
    return String(value ?? '')
      .replace(/&/g, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')
      .replace(/"/g, '&quot;')
      .replace(/'/g, '&#39;');
  }

  _formatAmount(amount, currency = 'EUR') {
    const numeric = Number(amount || 0);
    return `${numeric.toFixed(2)} ${currency}`;
  }

  _firstNonEmpty(...values) {
    for (const value of values) {
      if (value === undefined || value === null) continue;
      const normalized = String(value).trim();
      if (normalized) return normalized;
    }
    return '';
  }

  _normalizeInvoiceUserType(value) {
    return String(value || '').trim().toLowerCase() === 'company' ? 'company' : 'personal';
  }

  _normalizeInvoiceTemplateTaxMode(value) {
    const normalized = String(value || '').trim().toLowerCase();
    if (normalized === 'standard' || normalized === 'reverse_charge' || normalized === 'both') {
      return normalized;
    }
    return 'both';
  }

  _matchesLegacyReverseChargeTemplate(template, taxMode) {
    if (taxMode !== 'reverse_charge' || !template) return false;
    const haystack = [template?.name, template?.description, template?.storageKey].filter(Boolean).join(' ').toLowerCase();
    return /reverse[\s_-]*charge/.test(haystack);
  }

  async _loadInvoiceUserProfile(userId) {
    if (!userId) return null;

    try {
      const [rows] = await pool.query(
        `SELECT u.id, u.email, u.user_type,
                cp.company_name, cp.registration_number, cp.atu_number, cp.country AS company_country
           FROM users u
           LEFT JOIN company_profiles cp ON cp.user_id = u.id
          WHERE u.id = ?
          LIMIT 1`,
        [userId],
      );

      return rows?.[0] || null;
    } catch (error) {
      logger.warn('InvoiceService._loadInvoiceUserProfile:error', {
        userId,
        message: error?.message,
      });
      return null;
    }
  }

  async _buildInvoiceBillingContext({ abonement, invoice = null, invoiceUserId = null, userProfile = null } = {}) {
    const profile = userProfile || await this._loadInvoiceUserProfile(invoiceUserId);
    const userType = this._normalizeInvoiceUserType(profile?.user_type);

    const shippingFullName = this._firstNonEmpty(
      [abonement?.first_name, abonement?.last_name].filter(Boolean).join(' '),
      invoice?.buyer_name,
      profile?.company_name,
      abonement?.email,
      invoice?.buyer_email,
      'Customer',
    );

    const customerName = userType === 'company'
      ? this._firstNonEmpty(
          abonement?.invoice_full_name,
          profile?.company_name,
          invoice?.buyer_name,
          shippingFullName,
        )
      : this._firstNonEmpty(
          abonement?.invoice_full_name,
          invoice?.buyer_name,
          shippingFullName,
        );

    const email = this._firstNonEmpty(
      abonement?.invoice_email,
      invoice?.buyer_email,
      abonement?.email,
      profile?.email,
    );

    const street = this._firstNonEmpty(
      abonement?.invoice_street,
      invoice?.buyer_street,
      abonement?.street,
    );

    const postalCode = this._firstNonEmpty(
      abonement?.invoice_postal_code,
      invoice?.buyer_postal_code,
      abonement?.postal_code,
    );

    const city = this._firstNonEmpty(
      abonement?.invoice_city,
      invoice?.buyer_city,
      abonement?.city,
    );

    const country = this._firstNonEmpty(
      invoice?.buyer_country,
      abonement?.country,
      profile?.company_country,
    );

    return {
      customerName: customerName || shippingFullName || '-',
      email,
      street,
      postalCode,
      city,
      country,
      postalCity: [postalCode, city].filter(Boolean).join(' '),
      userType,
      companyName: this._firstNonEmpty(profile?.company_name, abonement?.invoice_full_name),
      uidNumber: this._normalizeUid(profile?.atu_number || profile?.registration_number || ''),
      profile,
    };
  }

  _selectInvoiceTemplate(templates, { lang = 'en', userType = 'personal', taxMode = 'standard' } = {}) {
    if (!Array.isArray(templates) || !templates.length) return null;

    const requestedLanguage = normalizeLanguageCode(lang) || 'en';
    const languageCandidates = requestedLanguage === 'en' ? ['en'] : [requestedLanguage, 'en'];
    const safeUserType = this._normalizeInvoiceUserType(userType);
    const safeTaxMode = this._normalizeInvoiceTemplateTaxMode(taxMode);
    const matchesTaxMode = (template, mode) => this._normalizeInvoiceTemplateTaxMode(template?.tax_mode) === mode;

    const matchesLanguage = (template, candidateLanguage) => normalizeLanguageCode(template?.lang) === candidateLanguage;
    const userTypeCandidates = [safeUserType, 'both'];

    for (const candidateLanguage of languageCandidates) {
      for (const candidateUserType of userTypeCandidates) {
        const exact = templates.find((template) => (
          matchesLanguage(template, candidateLanguage)
          && template?.user_type === candidateUserType
          && matchesTaxMode(template, safeTaxMode)
        ));
        if (exact) return exact;

        const legacyReverseCharge = templates.find((template) => (
          matchesLanguage(template, candidateLanguage)
          && template?.user_type === candidateUserType
          && this._matchesLegacyReverseChargeTemplate(template, safeTaxMode)
        ));
        if (legacyReverseCharge) return legacyReverseCharge;

        const bothTaxModes = templates.find((template) => (
          matchesLanguage(template, candidateLanguage)
          && template?.user_type === candidateUserType
          && matchesTaxMode(template, 'both')
        ));
        if (bothTaxModes) return bothTaxModes;
      }
    }

    for (const candidateUserType of userTypeCandidates) {
      const exact = templates.find((template) => template?.user_type === candidateUserType && matchesTaxMode(template, safeTaxMode));
      if (exact) return exact;

      const legacyReverseCharge = templates.find((template) => (
        template?.user_type === candidateUserType
        && this._matchesLegacyReverseChargeTemplate(template, safeTaxMode)
      ));
      if (legacyReverseCharge) return legacyReverseCharge;

      const bothTaxModes = templates.find((template) => template?.user_type === candidateUserType && matchesTaxMode(template, 'both'));
      if (bothTaxModes) return bothTaxModes;
    }

    return templates[0];
  }

  async _s3BodyToString(body) {
    if (!body) return '';
    if (typeof body.transformToString === 'function') {
      return body.transformToString();
    }
    const chunks = [];
    for await (const chunk of body) {
      chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
    }
    return Buffer.concat(chunks).toString('utf8');
  }

  _getEmailSubject(lang, invoiceNumber) {
    return lang === 'de'
      ? `ProfitPlanet Rechnung ${invoiceNumber}`
      : `ProfitPlanet Invoice ${invoiceNumber}`;
  }

  _buildItemsText(items, currency) {
    if (!Array.isArray(items) || !items.length) return '- Subscription item';
    return items.map((item, index) => {
      const qty = Number(item.quantity || 0);
      const lineGross = Number(item.line_gross || 0);
      return `${index + 1}. ${item.description || 'Coffee'} | qty: ${qty} | total: ${this._formatAmount(lineGross, currency)}`;
    }).join('\n');
  }

  _buildItemsHtml(items, currency) {
    if (!Array.isArray(items) || !items.length) {
      return '<li>Subscription item</li>';
    }
    return items.map((item) => {
      const desc = this._escapeHtml(item.description || 'Coffee');
      const qty = Number(item.quantity || 0);
      const unit = this._formatAmount(item.unit_price || 0, currency);
      const total = this._formatAmount(item.line_gross || 0, currency);
      return `<li><strong>${desc}</strong> — ${qty} x ${this._escapeHtml(unit)} = ${this._escapeHtml(total)}</li>`;
    }).join('');
  }

  _buildInvoiceText({ invoice, items, abonement, lang }) {
    const isDe = lang === 'de';
    const customerName = invoice?.buyer_name || [abonement?.first_name, abonement?.last_name].filter(Boolean).join(' ') || '-';
    const issuedAt = invoice.issued_at ? new Date(invoice.issued_at).toISOString().slice(0, 10) : new Date().toISOString().slice(0, 10);

    return [
      isDe ? 'Ihre Rechnung wurde erstellt.' : 'Your invoice has been created.',
      `${isDe ? 'Rechnungsnummer' : 'Invoice number'}: ${invoice.invoice_number}`,
      `${isDe ? 'Kunde' : 'Customer'}: ${customerName}`,
      `${isDe ? 'Datum' : 'Date'}: ${issuedAt}`,
      `${isDe ? 'Positionen' : 'Items'}:`,
      this._buildItemsText(items, invoice.currency),
      `${isDe ? 'Gesamtbetrag' : 'Total'}: ${this._formatAmount(invoice.total_gross, invoice.currency)}`,
    ].join('\n');
  }

  _buildInvoiceMailText({ invoice, items, abonement, lang }) {
    const isDe = lang === 'de';
    const customerName = invoice?.buyer_name || [abonement?.first_name, abonement?.last_name].filter(Boolean).join(' ') || '-';
    return [
      isDe ? `Vielen Dank für Ihr Abonnement, ${customerName}.` : `Thank you for your subscription, ${customerName}.`,
      isDe ? 'Ihre Rechnung ist als PDF im Anhang enthalten.' : 'Your invoice is attached as a PDF.',
      `${isDe ? 'Rechnungsnummer' : 'Invoice number'}: ${invoice.invoice_number}`,
      `${isDe ? 'Gesamtbetrag' : 'Total'}: ${this._formatAmount(invoice.total_gross, invoice.currency)}`,
      '',
      `${isDe ? 'Positionen' : 'Items'}:`,
      this._buildItemsText(items, invoice.currency),
    ].join('\n');
  }

  _buildInvoiceMailHtml({ invoice, abonement, lang }) {
    const isDe = lang === 'de';
    const customerName = invoice?.buyer_name || [abonement?.first_name, abonement?.last_name].filter(Boolean).join(' ') || 'Customer';
    const logoUrl = process.env.MAIL_LOGO_URL || process.env.BREVO_LOGO_URL || process.env.APP_LOGO_URL || '';

    return `<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>${this._escapeHtml(invoice.invoice_number)}</title>
  </head>
  <body style="margin:0;padding:0;background:#f5f7fb;font-family:Arial,sans-serif;color:#1f2937;">
    <table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="background:#f5f7fb;padding:24px 0;">
      <tr>
        <td align="center">
          <table role="presentation" width="640" cellspacing="0" cellpadding="0" style="max-width:640px;background:#ffffff;border-radius:12px;overflow:hidden;box-shadow:0 8px 24px rgba(0,0,0,0.08);">
            <tr>
              <td style="padding:24px 28px;background:#111827;color:#ffffff;">
                ${logoUrl ? `<img src="${this._escapeHtml(logoUrl)}" alt="ProfitPlanet" style="max-height:44px;display:block;margin-bottom:12px;">` : ''}
                <h1 style="margin:0;font-size:22px;line-height:1.3;">${isDe ? 'Danke für Ihr Abonnement!' : 'Thank you for your subscription!'}</h1>
              </td>
            </tr>
            <tr>
              <td style="padding:24px 28px;">
                <p style="margin:0 0 12px 0;font-size:15px;line-height:1.6;">${isDe ? 'Hallo' : 'Hi'} ${this._escapeHtml(customerName)},</p>
                <p style="margin:0 0 18px 0;font-size:15px;line-height:1.6;">${isDe ? 'vielen Dank für Ihr Abonnement. Ihre Rechnung haben wir als PDF angehängt.' : 'thank you for your subscription. We attached your invoice as a PDF.'}</p>

                <table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="border:1px solid #e5e7eb;border-radius:8px;overflow:hidden;margin-bottom:18px;">
                  <tr>
                    <td style="padding:12px 14px;background:#f9fafb;font-size:13px;color:#6b7280;">${isDe ? 'Rechnungsnummer' : 'Invoice number'}</td>
                    <td style="padding:12px 14px;background:#f9fafb;font-size:13px;text-align:right;font-weight:700;">${this._escapeHtml(invoice.invoice_number || '')}</td>
                  </tr>
                  <tr>
                    <td style="padding:12px 14px;font-size:13px;color:#6b7280;">${isDe ? 'Gesamtbetrag' : 'Total'}</td>
                    <td style="padding:12px 14px;font-size:13px;text-align:right;font-weight:700;">${this._escapeHtml(this._formatAmount(invoice.total_gross, invoice.currency))}</td>
                  </tr>
                </table>

                <p style="margin:0;font-size:13px;color:#6b7280;">${isDe ? 'Falls diese E-Mail nicht korrekt angezeigt wird, nutzen Sie bitte den Textinhalt oder kontaktieren Sie unseren Support.' : 'If this email is not displayed correctly, please use the text version or contact support.'}</p>
              </td>
            </tr>
          </table>
        </td>
      </tr>
    </table>
  </body>
</html>`;
  }

  _buildItemsTableRows(items, currency) {
    if (!Array.isArray(items) || !items.length) {
      return `<tr><td>1</td><td>Subscription item</td><td>1</td><td>-</td><td>-</td></tr>`;
    }
    return items.map((item, i) => {
      const desc = this._escapeHtml(item.description || 'Coffee');
      const qty = Number(item.quantity || 0);
      const unit = this._formatAmount(item.unit_price || 0, currency);
      const total = this._formatAmount(item.line_gross || 0, currency);
      return `<tr><td>${i + 1}</td><td>${desc}</td><td>${qty}</td><td>${unit}</td><td>${total}</td></tr>`;
    }).join('');
  }

  async _loadInvoiceHtmlTemplate(options = {}) {
    return this._loadInvoiceTemplateHtml(options);
  }

  _getProfitPlanetBankBlockHtml({ bankAccountHolder, bankIban, bankBic }) {
    return `<strong>${this._escapeHtml(bankAccountHolder)}</strong><br>${this._escapeHtml(bankIban)}<br>${this._escapeHtml(bankBic)}`;
  }

  async _loadCompanyInfo() {
    try {
      const [rows] = await pool.query(
        `SELECT company_name, company_street, company_postal_city, company_country,
                company_logo_base64, company_logo_mime_type
           FROM company_settings
          WHERE id = 1
          LIMIT 1`
      );
      return rows?.[0] || {};
    } catch (e) {
      logger.warn('InvoiceService._loadCompanyInfo:error', { message: e?.message });
      return {};
    }
  }

  _buildCompanyLogoTag(companyInfo) {
    const base64 = typeof companyInfo?.company_logo_base64 === 'string' ? companyInfo.company_logo_base64.trim() : '';
    const mimeType = typeof companyInfo?.company_logo_mime_type === 'string' ? companyInfo.company_logo_mime_type.trim() : '';
    if (!base64 || !mimeType || !mimeType.startsWith('image/')) return '';

    const alt = this._escapeHtml(companyInfo?.company_name || 'Company logo');
    return `<img src="data:${mimeType};base64,${base64}" alt="${alt}">`;
  }

  _prepareVariablesForTemplate(templateHtml, variables) {
    // Ensure backwards compatibility with older templates that only contain {{paymentInfoText}}
    // by injecting the Profit Planet bank block into paymentInfoText.
    if (!templateHtml) return variables;

    const supportsBankVars = this._templateHasVars(templateHtml, ['bankAccountHolder', 'bankIban', 'bankBic']);

    const bankBlock = this._getProfitPlanetBankBlockHtml({
      bankAccountHolder: variables.bankAccountHolder || 'Profit Planet GmbH',
      bankIban: variables.bankIban || '',
      bankBic: variables.bankBic || '',
    });

    const next = { ...variables };
    if (!supportsBankVars) {
      // Replace the default instruction text entirely with bank info
      next.paymentInfoText = bankBlock;
    }

    return next;
  }

  async _buildInvoiceTemplateVariables({ invoice, items, abonement, lang }) {
    const isDe = lang === 'de';
    const isGift = abonement?.details?.is_for_self === false;
    const issuedAt = invoice.issued_at ? new Date(invoice.issued_at).toISOString().slice(0, 10) : new Date().toISOString().slice(0, 10);
    const dueAt = invoice.due_at ? new Date(invoice.due_at).toISOString().slice(0, 10) : '-';
    const vatRate = invoice.vat_rate != null ? Number(invoice.vat_rate) : 0;
    const taxMode = String(invoice?.context?.tax_mode || 'standard').toLowerCase();
    const isReverseCharge = taxMode === 'reverse_charge';
    const invoiceUserId = invoice?.user_id || abonement?.user_id || abonement?.purchaser_user_id || null;
    const billingContext = await this._buildInvoiceBillingContext({
      abonement,
      invoice,
      invoiceUserId,
    });
    const reverseChargeNoticeText = isDe
      ? 'Bei dieser Rechnung handelt es sich um eine Rechnung nach dem Reverse Charge Verfahren. Demnach wird keine Umsatzsteuer ausgewiesen. Die Steuerschuldnerschaft liegt beim Leistungsempfänger. Die Umsatzsteuer ist entsprechend vom Leistungsempfänger anzumelden und abzuführen.'
      : 'This invoice is issued under the reverse charge procedure. Accordingly, no VAT is shown. The tax liability is transferred to the recipient of the service. The recipient must declare and pay the VAT in accordance with the applicable regulations.';

    // Hardcoded bank info (Profit Planet)
    const bankAccountHolder = 'Profit Planet GmbH';
    const bankIban = 'AT16 2081 5000 4639 9507';
    const bankBic = 'STSPAT2GXXX';

    // Hardcoded footer/contact info (Profit Planet)
    const footerText = [
      'Profit Planet GmbH',
      'Kärntner Straße 227',
      '8053 Graz',
      '',
      'Kontakt',
      'Telefon: 0676 344 0274',
      'E-Mail: office@profit-planet.com',
      '',
      'Profit Planet GmbH',
      bankIban,
      bankBic,
    ].join('<br>');

    const storedCompanyInfo = await this._loadCompanyInfo();
    const companyInfo = {
      company_name: storedCompanyInfo.company_name || 'Profit Planet GmbH',
      company_street: storedCompanyInfo.company_street || 'Kärntner Straße 227',
      company_postal_city: storedCompanyInfo.company_postal_city || '8053 Graz',
      company_country: storedCompanyInfo.company_country || 'Austria',
      company_logo_base64: storedCompanyInfo.company_logo_base64 || null,
      company_logo_mime_type: storedCompanyInfo.company_logo_mime_type || null,
    };

    // For gift subscriptions: "Bill To" = recipient, "Ordered by" = purchaser
    // For self subscriptions: "Bill To" = the subscriber
    let customerName = billingContext.customerName || invoice.buyer_name || '-';
    let customerEmail = billingContext.email || '';
    let customerStreet = billingContext.street || invoice.buyer_street || '';
    let customerPostalCity = billingContext.postalCity || [invoice.buyer_postal_code, invoice.buyer_city].filter(Boolean).join(' ');
    let customerCountry = billingContext.country || invoice.buyer_country || '';
    let orderedByBlock = '';
    const taxTreatmentBlock = isReverseCharge
      ? ''
      : `<div class="tax-banner"><div class="tax-banner-title">${isDe ? 'Steuerhinweis' : 'Tax treatment'}</div><p><strong>${this._escapeHtml(isDe ? 'MwSt.' : 'Tax')}</strong> (${this._escapeHtml(vatRate ? `${vatRate}%` : '0%')})</p></div>`;

    if (isGift) {
      // Recipient info for "Bill To"
      const recipientName = abonement?.details?.recipient_name || '';
      const recipientEmail = abonement?.email || invoice.buyer_email || '';
      customerName = recipientName || recipientEmail || '-';
      customerEmail = recipientName ? recipientEmail : '';
      customerStreet = abonement?.street || invoice?.buyer_street || '';
      customerPostalCity = [abonement?.postal_code || invoice?.buyer_postal_code, abonement?.city || invoice?.buyer_city].filter(Boolean).join(' ');
      customerCountry = abonement?.country || invoice?.buyer_country || '';

      // Purchaser info for "Ordered by"
      const purchaserName = billingContext.customerName || invoice.buyer_name || [abonement?.first_name, abonement?.last_name].filter(Boolean).join(' ') || '';
      if (purchaserName) {
        const orderedByLabel = isDe ? 'Bestellt von' : 'Ordered By';
        orderedByBlock = `<div class="meta-block"><h3>${this._escapeHtml(orderedByLabel)}</h3><p><span class="highlight">${this._escapeHtml(purchaserName)}</span></p></div>`;
      }
    }

    const qrCodeImage = await this._buildQrCodeImageTag({ abonement });

    return {
      lang: isDe ? 'de' : 'en',
      documentTitle: isDe ? 'Rechnung' : 'Invoice',
      invoiceNumber: this._escapeHtml(invoice.invoice_number || ''),
      invoiceNumberLabel: isDe ? 'Rechnungsnummer' : 'Invoice Number',
      fromLabel: isDe ? 'Von' : 'From',
      toLabel: isDe ? 'An' : 'Bill To',
      detailsLabel: isDe ? 'Details' : 'Details',
      dateLabel: isDe ? 'Datum' : 'Date',
      dueDateLabel: isDe ? 'Fällig am' : 'Due Date',
      statusLabel: 'Status',
      invoiceStatus: this._escapeHtml((invoice.status || 'issued').toUpperCase()),
      companyName: this._escapeHtml(companyInfo.company_name || 'ProfitPlanet GmbH'),
      companyStreet: this._escapeHtml(companyInfo.company_street || ''),
      companyPostalCity: this._escapeHtml(companyInfo.company_postal_city || ''),
      companyCountry: this._escapeHtml(companyInfo.company_country || 'Germany'),
      companyLogo: this._buildCompanyLogoTag(companyInfo),
      invoiceUserType: billingContext.userType,
      invoiceTaxMode: isReverseCharge ? 'reverse_charge' : 'standard',
      customerName: this._escapeHtml(customerName),
      customerEmail: this._escapeHtml(customerEmail),
      customerStreet: this._escapeHtml(customerStreet),
      customerPostalCity: this._escapeHtml(customerPostalCity),
      customerCountry: this._escapeHtml(customerCountry),
      customerCompanyName: this._escapeHtml(billingContext.companyName || ''),
      customerUidNumber: this._escapeHtml(invoice?.context?.uid_number || billingContext.uidNumber || ''),
      orderedByBlock,
      taxTreatmentBlock,
      issuedAt: this._escapeHtml(issuedAt),
      dueAt: this._escapeHtml(dueAt),
      descriptionHeader: isDe ? 'Beschreibung' : 'Description',
      qtyHeader: isDe ? 'Menge' : 'Qty',
      unitPriceHeader: isDe ? 'Stückpreis' : 'Unit Price',
      totalHeader: isDe ? 'Gesamt' : 'Total',
      itemsRows: this._buildItemsTableRows(items, invoice.currency),
      subtotalLabel: isDe ? 'Nettobetrag' : 'Subtotal (net)',
      taxLabel: isReverseCharge ? (isDe ? 'Reverse Charge' : 'Reverse charge') : (isDe ? 'MwSt.' : 'Tax'),
      vatRateDisplay: isReverseCharge ? (isDe ? 'nicht ausgewiesen' : 'not charged') : (vatRate ? `${vatRate}%` : '0%'),
      totalNet: this._escapeHtml(this._formatAmount(invoice.total_net, invoice.currency)),
      totalTax: this._escapeHtml(this._formatAmount(invoice.total_tax, invoice.currency)),
      totalLabel: isDe ? 'Gesamtbetrag (brutto)' : 'Total (gross)',
      totalGross: this._escapeHtml(this._formatAmount(invoice.total_gross, invoice.currency)),
      paymentInfoTitle: isDe ? 'Zahlungsinformationen' : 'Payment Information',
      paymentInfoText: isDe
        ? (isReverseCharge
          ? 'Reverse-Charge-Verfahren: Steuerschuldnerschaft des Leistungsempfängers. Bitte überweisen Sie den Gesamtbetrag unter Angabe der Rechnungsnummer.'
          : 'Bitte überweisen Sie den Gesamtbetrag unter Angabe der Rechnungsnummer.')
        : (isReverseCharge
          ? 'Reverse charge applies: VAT liability shifts to the recipient. Please transfer the total amount stating the invoice number as reference.'
          : 'Please transfer the total amount stating the invoice number as reference.'),
      reverseChargeClass: isReverseCharge ? 'reverse-charge-active' : 'reverse-charge-inactive',
      reverseChargeSectionClass: isReverseCharge ? '' : 'is-hidden',
      standardTaxSectionClass: isReverseCharge ? 'is-hidden' : '',
      reverseChargeNoticeText: isReverseCharge ? this._escapeHtml(reverseChargeNoticeText) : '',
      bankAccountHolder: this._escapeHtml(bankAccountHolder),
      bankIban: this._escapeHtml(bankIban),
      bankBic: this._escapeHtml(bankBic),
      qrCodeImage,
      footerText,
      // Legacy key used by S3-stored templates
      itemsHtml: this._buildItemsHtml(items, invoice.currency),
    };
  }

  async _buildFallbackInvoiceHtml({ invoice, items, abonement, lang }) {
    const variables = await this._buildInvoiceTemplateVariables({ invoice, items, abonement, lang });

    const template = await this._loadInvoiceHtmlTemplate({
      lang,
      userType: variables.invoiceUserType,
      taxMode: variables.invoiceTaxMode,
    });
    if (template) {
      const varsForTemplate = this._prepareVariablesForTemplate(template, variables);
      return this._renderTemplate(template, varsForTemplate);
    }

    // Absolute fallback if no active contract-manager template is available
    const isDe = lang === 'de';
    return `<!doctype html>
<html>
  <head><meta charset="utf-8"><title>${this._escapeHtml(invoice.invoice_number)}</title></head>
  <body>
    <h2>${isDe ? 'Rechnung' : 'Invoice'} ${this._escapeHtml(invoice.invoice_number)}</h2>
    <p>${isDe ? 'Kunde' : 'Customer'}: ${variables.customerName}</p>
    <p>${isDe ? 'Datum' : 'Date'}: ${variables.issuedAt}</p>
    <h3>${isDe ? 'Positionen' : 'Items'}</h3>
    <ul>${variables.itemsHtml}</ul>
    <p><strong>${isDe ? 'Gesamtbetrag' : 'Total'}: ${variables.totalGross}</strong></p>
  </body>
</html>`;
  }

  async _loadInvoiceTemplateHtml({ lang = 'en', userType = 'personal', taxMode = 'standard' } = {}) {
    try {
      const templates = await DocumentTemplateService.getActiveTemplatesForUserType(userType, 'invoice', null, taxMode);
      if (!Array.isArray(templates) || !templates.length) return null;

      const selected = this._selectInvoiceTemplate(templates, { lang, userType, taxMode });
      if (!selected?.storageKey) return null;

      const command = new GetObjectCommand({
        Bucket: process.env.EXOSCALE_BUCKET,
        Key: selected.storageKey,
      });
      const obj = await sharedExoscaleClient.send(command);
      const html = await this._s3BodyToString(obj.Body);
      if (!html) return null;
      return html;
    } catch (error) {
      logger.warn('InvoiceService._loadInvoiceTemplateHtml:error', { message: error?.message });
      return null;
    }
  }

  _renderTemplate(template, variables) {
    if (!template) return null;
    return template.replace(/{{\s*([\w]+)\s*}}/g, (_, key) => variables[key] ?? '');
  }

  async _renderPdfFromHtml(html) {
    const browser = await puppeteer.launch({
      headless: 'new',
      args: ['--no-sandbox', '--disable-setuid-sandbox']
    });
    try {
      const page = await browser.newPage();
      await page.setContent(html, { waitUntil: 'networkidle0' });
      const pdfBuffer = await page.pdf({
        format: 'A4',
        printBackground: true,
        margin: { top: '16mm', right: '14mm', bottom: '16mm', left: '14mm' }
      });
      return Buffer.isBuffer(pdfBuffer) ? pdfBuffer : Buffer.from(pdfBuffer);
    } finally {
      await browser.close();
    }
  }

  async _storeInvoicePdf(invoice, pdfBuffer) {
    if (!pdfBuffer) return null;
    const safeUser = invoice.user_id || 'unknown';
    const key = `invoices/${safeUser}/${invoice.invoice_number || `invoice-${invoice.id}`}.pdf`;
    await sharedExoscaleClient.send(new PutObjectCommand({
      Bucket: process.env.EXOSCALE_BUCKET,
      Key: key,
      Body: pdfBuffer,
      ContentType: 'application/pdf',
    }));
    await this.repo.updateStorageKey(invoice.id, key);
    return key;
  }

  async _sendInvoiceEmail({ invoice, abonement, lang = 'en' }) {
    const recipientEmail = invoice.buyer_email || abonement?.email;
    if (!recipientEmail) {
      logger.warn('InvoiceService._sendInvoiceEmail:missing_recipient', { invoiceId: invoice.id });
      return;
    }

    const items = await this.repo.getItemsByInvoiceId(invoice.id);
    const text = this._buildInvoiceMailText({ invoice, items, abonement, lang });
    const subject = this._getEmailSubject(lang, invoice.invoice_number);

    // Build the full set of template variables once – used by both S3 templates and the emergency HTML fallback
    const variables = await this._buildInvoiceTemplateVariables({ invoice, items, abonement, lang });

    const templateHtml = await this._loadInvoiceTemplateHtml({
      lang,
      userType: variables.invoiceUserType,
      taxMode: variables.invoiceTaxMode,
    });
    let html = null;

    if (templateHtml) {
      const supportsBankVars = this._templateHasVars(templateHtml, ['bankAccountHolder', 'bankIban', 'bankBic']);
      logger.info('InvoiceService._sendInvoiceEmail:template_compat', {
        invoiceId: invoice?.id,
        lang,
        supportsBankVars,
      });

      const varsForTemplate = this._prepareVariablesForTemplate(templateHtml, variables);
      html = this._renderTemplate(templateHtml, varsForTemplate);
    }

    const htmlForPdf = html || await this._buildFallbackInvoiceHtml({ invoice, items, abonement, lang });
    const pdfBuffer = await this._renderPdfFromHtml(htmlForPdf);
    await this._storeInvoicePdf(invoice, pdfBuffer);

    const mailHtml = this._buildInvoiceMailHtml({ invoice, abonement, lang });

    await MailService.sendInvoiceEmail({
      email: recipientEmail,
      subject,
      text,
      html: mailHtml,
      attachments: [{
        name: `${invoice.invoice_number || `invoice-${invoice.id}`}.pdf`,
        content: pdfBuffer.toString('base64')
      }],
      lang,
    });
  }

  // NEW: resolve current standard VAT rate for a buyer country code
  async _resolveCountryByInput(conn, countryInput) {
    const raw = String(countryInput || '').trim();
    if (!raw) return null;

    const code = raw.toUpperCase();
    const [byCode] = await conn.query(
      `SELECT id, country_code, country_name FROM countries WHERE UPPER(country_code) = ? LIMIT 1`,
      [code],
    );
    if (byCode?.[0]) return byCode[0];

    const [byName] = await conn.query(
      `SELECT id, country_code, country_name FROM countries WHERE LOWER(country_name) = LOWER(?) LIMIT 1`,
      [raw],
    );
    return byName?.[0] || null;
  }

  _normalizeUid(value) {
    return String(value || '').trim().toUpperCase().replace(/[^A-Z0-9]/g, '');
  }

  _isLikelyValidUid(value) {
    const uid = this._normalizeUid(value);
    return /^[A-Z]{2}[A-Z0-9]{6,14}$/.test(uid);
  }

  async _loadCompanyTaxProfile(userId) {
    return this._loadInvoiceUserProfile(userId);
  }

  async resolveTaxDecisionForSubscription({ buyerCountry, invoiceOwnerUserId, invoiceOwnerProfile = null, uidOverride = null }) {
    const uow = new UnitOfWork();
    await uow.start();

    try {
      const country = await this._resolveCountryByInput(uow.getConnection(), buyerCountry);

      let vatRate = null;
      if (country?.id) {
        const [rows] = await uow.getConnection().query(
          `SELECT standard_rate FROM vat_rates WHERE country_id = ? AND effective_to IS NULL LIMIT 1`,
          [country.id],
        );
        vatRate = rows?.[0]?.standard_rate == null ? null : Number(rows[0].standard_rate);
      }

      await uow.commit();

      const companyProfile = invoiceOwnerProfile || await this._loadCompanyTaxProfile(invoiceOwnerUserId);
  const uidCandidate = uidOverride || companyProfile?.atu_number || companyProfile?.registration_number || '';
      const normalizedUid = this._normalizeUid(uidCandidate);
      const hasValidUid = this._isLikelyValidUid(normalizedUid);
      const countryCode = String(country?.country_code || '').toUpperCase();
      const invoiceUserType = this._normalizeInvoiceUserType(companyProfile?.user_type);

      // Reverse charge for company customers with a valid UID outside seller country (AT).
      const isReverseCharge = Boolean(invoiceUserType === 'company' && hasValidUid && countryCode && countryCode !== 'AT');

      return {
        vatRate: isReverseCharge ? 0 : vatRate,
        isReverseCharge,
        countryCode: countryCode || null,
        uid: hasValidUid ? normalizedUid : null,
        userType: invoiceUserType,
      };
    } catch (e) {
      await uow.rollback();
      throw e;
    }
  }

  async resolveVatRateForCountry(countryCode) {
    const decision = await this.resolveTaxDecisionForSubscription({
      buyerCountry: countryCode,
      invoiceOwnerUserId: null,
    });
    return decision?.vatRate ?? null;
  }

  // Issue invoice for a subscription period, with items from pack_breakdown
  async issueForAbonement(abonement, periodStart, periodEnd, { actorUserId, lang = 'en', uidOverride = null, taxModeHint = null } = {}) {
    console.log('[INVOICE ISSUE] Inputs:', {
      abonement_id: abonement?.id,
      abonement_user_id: abonement?.user_id,
      abonement_purchaser_user_id: abonement?.purchaser_user_id,
      actorUserId,
      periodStart,
      periodEnd,
    });

    const userIdForInvoice = actorUserId ?? abonement.user_id ?? abonement.purchaser_user_id ?? null;
    const invoiceUserProfile = await this._loadInvoiceUserProfile(userIdForInvoice);
    const billingContext = await this._buildInvoiceBillingContext({
      abonement,
      invoiceUserId: userIdForInvoice,
      userProfile: invoiceUserProfile,
    });
    const buyerName = billingContext.customerName || null;
    const buyerEmail = billingContext.email || null;
    const addr = {
      street: billingContext.street || null,
      postal_code: billingContext.postalCode || null,
      city: billingContext.city || null,
      country: billingContext.country || null,
    };
    const currency = abonement.currency || 'EUR';

    // CHANGED: resolve tax mode for this subscription (standard VAT vs reverse charge)
    const taxDecision = await this.resolveTaxDecisionForSubscription({
      buyerCountry: addr.country,
      invoiceOwnerUserId: userIdForInvoice,
      invoiceOwnerProfile: invoiceUserProfile,
      uidOverride,
    });
    const vat_rate = taxDecision?.vatRate ?? null;

    logger.info('InvoiceService.issueForAbonement:tax_decision', {
      userId: userIdForInvoice,
      buyerCountry: addr.country || null,
      profileUserType: invoiceUserProfile?.user_type || null,
      providedUidOverride: uidOverride || null,
      taxModeHint: taxModeHint || null,
      taxMode: taxDecision?.isReverseCharge ? 'reverse_charge' : 'standard',
      resolvedUid: taxDecision?.uid || null,
    });

    const items = await this._buildInvoiceItems({ abonement, vatRate: vat_rate, lang });

    const context = {
      source: 'abonement',
      pack_group: abonement.pack_group || null,
      period_start: periodStart,
      period_end: periodEnd,
      referred_by: abonement.referred_by || null,
      tax_mode: taxDecision?.isReverseCharge ? 'reverse_charge' : 'standard',
      customer_country_code: taxDecision?.countryCode || null,
      uid_number: taxDecision?.uid || null,
      invoice_user_type: billingContext.userType,
    };

    console.log('[INVOICE ISSUE] Resolved user_id for invoice:', userIdForInvoice);

    const invoice = await this.repo.createInvoiceWithItems({
      source_type: 'subscription',
      source_id: abonement.id,
      user_id: userIdForInvoice,
      buyer_name: buyerName,
      buyer_email: buyerEmail,
      buyer_street: addr.street,
      buyer_postal_code: addr.postal_code,
      buyer_city: addr.city,
      buyer_country: addr.country,
      currency,
      items,
      status: 'issued',
      issued_at: new Date(),
      due_at: periodEnd,
      context,
      vat_rate, // NEW: persist on invoice
    });

    console.log('[INVOICE ISSUE] Created invoice:', {
      id: invoice?.id,
      user_id: invoice?.user_id,
      source_type: invoice?.source_type,
      source_id: invoice?.source_id,
      total_net: invoice?.total_net,
      total_tax: invoice?.total_tax,
      total_gross: invoice?.total_gross,
    });

    try {
      await this._sendInvoiceEmail({
        invoice,
        abonement,
        lang,
      });
      logger.info('InvoiceService.issueForAbonement:invoice_email_sent', {
        invoiceId: invoice?.id,
        userId: invoice?.user_id,
        hasEmail: Boolean(invoice?.buyer_email || abonement?.email),
      });
    } catch (mailError) {
      logger.error('InvoiceService.issueForAbonement:invoice_email_error', {
        invoiceId: invoice?.id,
        message: mailError?.message,
        stack: mailError?.stack,
        brevoStatus: mailError?.statusCode ?? mailError?.response?.status ?? null,
        brevoData: mailError?.body ?? mailError?.response?.data ?? mailError?.response?.text ?? null,
      });
    }

    return invoice;
  }

  async markPaid(invoiceId, { payment_method, transaction_id, amount, paid_at = new Date(), details } = {}) {
    const paidInvoice = await this.repo.markPaid(invoiceId, { payment_method, transaction_id, amount, paid_at, details });

    let poolResult = null;
    try {
      const inflowResult = await PoolInflowService.bookForPaidInvoice({
        invoiceId: paidInvoice?.id,
        paidAt: paid_at,
        actorUserId: null,
      });
      poolResult = inflowResult;
      console.log('[INVOICE PAID] Pool inflow booking result:', {
        invoiceId: paidInvoice?.id,
        ...inflowResult,
      });
    } catch (e) {
      poolResult = { error: e?.message || 'Pool inflow booking failed' };
      console.error('[INVOICE PAID] Pool inflow booking failed:', e);
    }

    // Attach pool result to returned data so the frontend can display it
    if (paidInvoice) {
      paidInvoice._poolResult = poolResult;
    }
    return paidInvoice;
  }

  async syncOverdueStatuses() {
    return this.repo.markIssuedPastDueAsOverdue();
  }

  async listMine(userId, { status, limit = 50, offset = 0 } = {}) {
    await this.syncOverdueStatuses();
    return this.repo.listByUser(userId, { status, limit, offset });
  }

  async listByAbonement(abonementId) {
    await this.syncOverdueStatuses();
    return this.repo.findByAbonement(abonementId);
  }

  async adminList({ status, limit = 200, offset = 0 } = {}) {
    await this.syncOverdueStatuses();
    return this.repo.listAll({ status, limit, offset });
  }

  async getRevenueSummary() {
    const summary = await this.repo.getPaidRevenueSummary();
    return {
      totalPaidAllTime: Number(summary?.total_paid_all_time || 0),
      currency: summary?.currency || 'EUR',
      paidInvoiceCount: Number(summary?.paid_invoice_count || 0),
    };
  }

  async updateStatus(invoiceId, newStatus) {
    const invoice = await this.repo.getById(invoiceId);
    if (!invoice) throw new Error(`Invoice ${invoiceId} not found.`);

    // If transitioning to 'paid', use the full markPaid flow for pool inflow booking
    if (newStatus === 'paid' && invoice.status !== 'paid') {
      return this.markPaid(invoiceId, {
        payment_method: 'admin_manual',
        amount: invoice.total_gross ?? 0,
        paid_at: new Date(),
      });
    }

    return this.repo.updateStatus(invoiceId, newStatus);
  }

  async getInvoiceDetail(invoiceId) {
    await this.syncOverdueStatuses();
    const invoice = await this.repo.getById(invoiceId);
    if (!invoice) throw new Error(`Invoice ${invoiceId} not found.`);
    const items = await this.repo.getItemsByInvoiceId(invoiceId);
    const payments = await this.repo.getPaymentsByInvoiceId(invoiceId);
    return { invoice, items, payments };
  }

  async getInvoicePdfStream(invoiceId, user) {
    const invoice = await this.repo.getById(invoiceId);
    if (!invoice) throw new Error(`Invoice ${invoiceId} not found.`);

    // Non-admin users can only access their own invoices
    const isAdmin = user?.role === 'admin' || user?.role === 'super_admin';
    if (!isAdmin && String(invoice.user_id) !== String(user?.id)) {
      throw new Error('Invoice not found.');
    }

    if (!invoice.pdf_storage_key) {
      throw new Error('No PDF available for this invoice.');
    }

    const command = new GetObjectCommand({
      Bucket: process.env.EXOSCALE_BUCKET,
      Key: invoice.pdf_storage_key,
    });
    const obj = await sharedExoscaleClient.send(command);
    return obj.Body;
  }

  /**
   * Send an email report of all paid invoices to a given email.
   * Optionally filter by date range.
   * @param {{ email: string, from?: string, to?: string }} opts
   * @returns {{ sentCount: number }}
   */
  async sendEmailReport({ email, from, to }) {
    if (!email) throw new Error('email is required');

    const allInvoices = await this.repo.listAll({ status: 'paid', limit: 10000, offset: 0 });

    // Optionally filter by date range
    let paidInvoices = allInvoices;
    if (from) {
      const fromDate = new Date(from);
      paidInvoices = paidInvoices.filter((inv) => {
        const d = new Date(inv.issued_at || inv.created_at);
        return d >= fromDate;
      });
    }
    if (to) {
      const toDate = new Date(to);
      toDate.setHours(23, 59, 59, 999);
      paidInvoices = paidInvoices.filter((inv) => {
        const d = new Date(inv.issued_at || inv.created_at);
        return d <= toDate;
      });
    }

    if (!paidInvoices.length) {
      throw new Error('No paid invoices found matching the criteria.');
    }

    // Collect PDF attachments for each paid invoice
    const attachments = [];
    for (const inv of paidInvoices) {
      if (inv.pdf_storage_key) {
        try {
          const command = new GetObjectCommand({
            Bucket: process.env.EXOSCALE_BUCKET,
            Key: inv.pdf_storage_key,
          });
          const obj = await sharedExoscaleClient.send(command);
          if (typeof obj.Body.transformToByteArray === 'function') {
            const bytes = await obj.Body.transformToByteArray();
            attachments.push({
              name: `${inv.invoice_number || `invoice-${inv.id}`}.pdf`,
              content: Buffer.from(bytes).toString('base64'),
            });
          } else {
            const chunks = [];
            for await (const chunk of obj.Body) {
              chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
            }
            attachments.push({
              name: `${inv.invoice_number || `invoice-${inv.id}`}.pdf`,
              content: Buffer.concat(chunks).toString('base64'),
            });
          }
        } catch (e) {
          logger.warn('InvoiceService.sendEmailReport:pdf_download_error', {
            invoiceId: inv.id,
            storageKey: inv.pdf_storage_key,
            message: e?.message,
          });
        }
      }
    }

    // Build email body with a summary table
    const totalGross = paidInvoices.reduce((sum, inv) => sum + Number(inv.total_gross || 0), 0);
    const currency = paidInvoices[0]?.currency || 'EUR';
    const dateRange = [from, to].filter(Boolean).join(' – ') || 'All time';

    const invoiceRows = paidInvoices
      .map(
        (inv) =>
          `<tr>
            <td style="padding:8px 12px;border-bottom:1px solid #e5e7eb;">${this._escapeHtml(inv.invoice_number || '')}</td>
            <td style="padding:8px 12px;border-bottom:1px solid #e5e7eb;">${this._escapeHtml(inv.buyer_name || '-')}</td>
            <td style="padding:8px 12px;border-bottom:1px solid #e5e7eb;">${inv.issued_at ? new Date(inv.issued_at).toISOString().slice(0, 10) : '-'}</td>
            <td style="padding:8px 12px;border-bottom:1px solid #e5e7eb;text-align:right;">${this._formatAmount(inv.total_gross, inv.currency)}</td>
          </tr>`,
      )
      .join('');

    const subject = `ProfitPlanet – Paid Invoices Report (${dateRange})`;

    const html = `<!doctype html>
<html>
  <head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1.0"></head>
  <body style="margin:0;padding:0;background:#f5f7fb;font-family:Arial,sans-serif;color:#1f2937;">
    <table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="background:#f5f7fb;padding:24px 0;">
      <tr><td align="center">
        <table role="presentation" width="640" cellspacing="0" cellpadding="0" style="max-width:640px;background:#ffffff;border-radius:12px;overflow:hidden;box-shadow:0 8px 24px rgba(0,0,0,0.08);">
          <tr><td style="padding:24px 28px;background:#111827;color:#ffffff;">
            <h1 style="margin:0;font-size:22px;">Paid Invoices Report</h1>
            <p style="margin:6px 0 0 0;font-size:13px;color:#9ca3af;">${this._escapeHtml(dateRange)}</p>
          </td></tr>
          <tr><td style="padding:24px 28px;">
            <p style="margin:0 0 16px 0;font-size:15px;">This report contains <strong>${paidInvoices.length}</strong> paid invoice(s).</p>
            <table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="border:1px solid #e5e7eb;border-radius:8px;overflow:hidden;">
              <thead>
                <tr style="background:#f9fafb;">
                  <th style="padding:10px 12px;text-align:left;font-size:13px;color:#6b7280;">Invoice #</th>
                  <th style="padding:10px 12px;text-align:left;font-size:13px;color:#6b7280;">Customer</th>
                  <th style="padding:10px 12px;text-align:left;font-size:13px;color:#6b7280;">Date</th>
                  <th style="padding:10px 12px;text-align:right;font-size:13px;color:#6b7280;">Total</th>
                </tr>
              </thead>
              <tbody>${invoiceRows}</tbody>
              <tfoot>
                <tr style="background:#f9fafb;">
                  <td colspan="3" style="padding:10px 12px;font-weight:700;font-size:13px;">Total</td>
                  <td style="padding:10px 12px;text-align:right;font-weight:700;font-size:13px;">${this._formatAmount(totalGross, currency)}</td>
                </tr>
              </tfoot>
            </table>
            ${attachments.length ? '<p style="margin:16px 0 0 0;font-size:13px;color:#6b7280;">The individual invoice PDFs are attached to this email.</p>' : '<p style="margin:16px 0 0 0;font-size:13px;color:#6b7280;">No PDF attachments were available at this time.</p>'}
          </td></tr>
        </table>
      </td></tr>
    </table>
  </body>
</html>`;

    const text = paidInvoices
      .map((inv) => `${inv.invoice_number} | ${inv.buyer_name || '-'} | ${inv.issued_at ? new Date(inv.issued_at).toISOString().slice(0, 10) : '-'} | ${this._formatAmount(inv.total_gross, inv.currency)}`)
      .join('\n');

    await MailService.sendInvoiceEmail({
      email,
      subject,
      text: `Paid Invoices Report (${dateRange})\n\n${text}\n\nTotal: ${this._formatAmount(totalGross, currency)}`,
      html,
      lang: 'en',
      attachments,
    });

    logger.info('InvoiceService.sendEmailReport:sent', {
      recipientEmail: email,
      sentCount: paidInvoices.length,
      attachmentCount: attachments.length,
      dateRange,
    });

    return { sentCount: paidInvoices.length };
  }

  async adminCreateManual(fields, pdfBuffer = null) {
    const { uploadBuffer } = require('../../utils/exoscaleUploader');

    const invoice = await this.repo.createManualInvoice({
      source_type: 'manual',
      buyer_name: fields.buyer_name || null,
      buyer_email: fields.buyer_email || null,
      buyer_street: fields.buyer_street || null,
      buyer_postal_code: fields.buyer_postal_code || null,
      buyer_city: fields.buyer_city || null,
      buyer_country: fields.buyer_country || null,
      currency: fields.currency || 'EUR',
      total_net: fields.total_net != null ? Number(fields.total_net) : 0,
      total_tax: fields.total_tax != null ? Number(fields.total_tax) : 0,
      total_gross: Number(fields.total_gross || 0),
      vat_rate: fields.vat_rate != null ? Number(fields.vat_rate) : null,
      status: fields.status || 'issued',
      issued_at: fields.issued_at ? new Date(fields.issued_at) : new Date(),
      due_at: fields.due_at ? new Date(fields.due_at) : null,
      context: { source: 'admin_manual_upload' },
    });

    if (pdfBuffer && pdfBuffer.length > 0) {
      const { objectKey } = await uploadBuffer(
        pdfBuffer,
        `invoice-${invoice.invoice_number}.pdf`,
        'application/pdf',
        `invoices/admin/${invoice.id}`,
      );
      await this.repo.updateStorageKey(invoice.id, objectKey);
      return this.repo.getById(invoice.id);
    }

    return invoice;
  }
}

module.exports = InvoiceService;
