const cron = require('node-cron');
const AbonemmentRepository = require('../../repositories/abonemments/AbonemmentRepository');
const InvoiceRepository = require('../../repositories/invoice/InvoiceRepository');
const InvoiceService = require('../invoice/InvoiceService');
const MailService = require('../email/MailService');
const { logger } = require('../../middleware/logger');

class RenewalCronService {
  constructor() {
    this.repo = new AbonemmentRepository();
    this.invoiceRepo = new InvoiceRepository();
    this.invoiceService = new InvoiceService();
  }

  addInterval(date, interval, count) {
    const d = new Date(date);
    if (interval === 'day') d.setDate(d.getDate() + count);
    if (interval === 'week') d.setDate(d.getDate() + 7 * count);
    if (interval === 'month') d.setMonth(d.getMonth() + count);
    if (interval === 'year') d.setFullYear(d.getFullYear() + count);
    return d;
  }

  /**
   * Get the latest invoice for a subscription.
   */
  async getLatestInvoice(abonementId) {
    const invoices = await this.invoiceRepo.findByAbonement(abonementId);
    return invoices.length ? invoices[0] : null; // already sorted DESC
  }

  // ──────────────────────────────────────────────
  //  Process active subscriptions due for renewal
  //
  //  Simple prepaid flow:
  //  - Previous invoice paid (or first cycle) → generate new invoice + advance next_billing_at
  //  - Previous invoice NOT paid → skip (advance next_billing_at so we don't get stuck)
  //  - Gift-abo without registered user → skip (don't advance)
  // ──────────────────────────────────────────────

  async processActiveRenewals(now) {
    let dueAbonements;
    try {
      dueAbonements = await this.repo.listDueForBilling(now);
    } catch (err) {
      logger.error('RenewalCron:fetch_due_error', { message: err?.message });
      return;
    }

    if (!dueAbonements.length) {
      logger.info('RenewalCron:no_due_subscriptions');
      return;
    }

    logger.info('RenewalCron:found_due', { count: dueAbonements.length });
    const results = { renewed: 0, skipped_unpaid: 0, skipped_gift: 0, skipped_other: 0, errors: [] };

    for (const abon of dueAbonements) {
      try {
        // Re-check status (race-condition protection)
        const fresh = await this.repo.getAbonementById(abon.id);
        if (!fresh || fresh.status !== 'active') {
          results.skipped_other++;
          continue;
        }

        // Gift-abo where recipient hasn't registered yet → skip, don't advance
        if (!fresh.user_id) {
          logger.info('RenewalCron:gift_no_user', { abonementId: fresh.id });
          results.skipped_gift++;
          continue;
        }

        // Check last invoice payment status
        const lastInvoice = await this.getLatestInvoice(fresh.id);

        if (!lastInvoice || lastInvoice.status === 'paid') {
          // First cycle or previous invoice paid → renew (generate invoice + advance)
          await this.renewSingleAbonement(fresh);
          results.renewed++;
        } else {
          // Previous invoice NOT paid → skip this cycle, just advance next_billing_at
          const oldNextBilling = new Date(fresh.next_billing_at);
          const newNextBilling = this.addInterval(
            oldNextBilling,
            fresh.billing_interval || 'month',
            fresh.interval_count || 1,
          );

          await this.repo.transitionBilling(fresh.id, newNextBilling, {
            event_type: 'skipped',
            actor_user_id: null,
            details: {
              pack_group: fresh.pack_group,
              trigger: 'auto_renewal_cron',
              reason: 'previous_invoice_unpaid',
              unpaid_invoice_id: lastInvoice.id,
              previous_next_billing_at: oldNextBilling.toISOString(),
            },
          });

          logger.info('RenewalCron:skipped_unpaid', {
            abonementId: fresh.id,
            unpaidInvoiceId: lastInvoice.id,
            invoiceStatus: lastInvoice.status,
            advancedTo: newNextBilling.toISOString(),
          });

          results.skipped_unpaid++;
        }
      } catch (err) {
        results.errors.push({ abonementId: abon.id, message: err?.message });
        logger.error('RenewalCron:renewal_failed', {
          abonementId: abon.id,
          message: err?.message,
          stack: err?.stack,
        });
      }
    }

    logger.info('RenewalCron:phase_complete', results);
  }

  // ──────────────────────────────────────────────
  //  Helpers
  // ──────────────────────────────────────────────

  async renewSingleAbonement(abon) {
    const abonId = abon.id;
    const oldNextBilling = new Date(abon.next_billing_at);
    const newNextBilling = this.addInterval(
      oldNextBilling,
      abon.billing_interval || 'month',
      abon.interval_count || 1,
    );

    logger.info('RenewalCron:renewing', {
      abonementId: abonId,
      oldNextBilling: oldNextBilling.toISOString(),
      newNextBilling: newNextBilling.toISOString(),
    });

    // Update billing date + history
    const renewed = await this.repo.transitionBilling(abonId, newNextBilling, {
      event_type: 'renewed',
      actor_user_id: null,
      details: {
        pack_group: abon.pack_group,
        trigger: 'auto_renewal_cron',
        previous_next_billing_at: oldNextBilling.toISOString(),
      },
    });

    // Issue new invoice
    let invoice = null;
    try {
      const lang = abon.language || abon.lang || 'de';
      invoice = await this.invoiceService.issueForAbonement(
        renewed,
        oldNextBilling,
        newNextBilling,
        { actorUserId: null, lang },
      );

      logger.info('RenewalCron:invoice_issued', {
        abonementId: abonId,
        invoiceId: invoice?.id,
        invoiceNumber: invoice?.invoice_number,
      });

      await this.repo.appendHistory(abonId, 'invoice_issued', null, {
        pack_group: renewed.pack_group,
        invoiceId: invoice.id,
        trigger: 'auto_renewal_cron',
      }, new Date());
    } catch (invoiceErr) {
      logger.error('RenewalCron:invoice_error', {
        abonementId: abonId,
        message: invoiceErr?.message,
      });
    }

    // Send renewal email with invoice
    try {
      const recipientEmail = abon.email || invoice?.buyer_email;
      if (recipientEmail) {
        const lang = abon.language || abon.lang || 'de';
        const customerName = [abon.first_name, abon.last_name].filter(Boolean).join(' ') || recipientEmail;

        await MailService.sendRenewalEmail({
          email: recipientEmail,
          customerName,
          invoiceNumber: invoice?.invoice_number || '-',
          totalGross: invoice?.total_gross
            ? `${Number(invoice.total_gross).toFixed(2)} ${invoice.currency || abon.currency || 'EUR'}`
            : '-',
          nextBillingDate: newNextBilling.toISOString().slice(0, 10),
          lang,
        });
      }
    } catch (mailErr) {
      logger.error('RenewalCron:renewal_email_error', {
        abonementId: abonId,
        message: mailErr?.message,
      });
    }

    return { abonementId: abonId, invoiceId: invoice?.id || null };
  }

  // ──────────────────────────────────────────────
  //  Main entry point
  // ──────────────────────────────────────────────

  async processDueRenewals() {
    const now = new Date();
    logger.info('RenewalCron:tick_start', { now: now.toISOString() });

    await this.processActiveRenewals(now);

    logger.info('RenewalCron:tick_end', { now: now.toISOString() });
  }

  /**
   * Start the cron schedule.
   * Runs every day at 02:00 AM.
   */
  start() {
    cron.schedule('0 2 * * *', async () => {
      try {
        await this.processDueRenewals();
      } catch (err) {
        logger.error('RenewalCron:unhandled_error', {
          message: err?.message,
          stack: err?.stack,
        });
      }
    });

    logger.info('RenewalCron:scheduled', { schedule: '0 2 * * * (daily at 02:00)' });
  }
}

module.exports = RenewalCronService;
