const db = require('../../database/database');

const SYSTEM_POOL_NAMES = ['ABO 60', 'ABO 120', 'Business', 'Gigantea', 'Core'];
const ABO_60_POOL_NAME = 'ABO 60';
const ABO_120_POOL_NAME = 'ABO 120';

function resolveAboPoolName(totalPacks) {
  const safeTotalPacks = Number(totalPacks || 0);
  if (!Number.isFinite(safeTotalPacks) || safeTotalPacks <= 0) return ABO_60_POOL_NAME;
  return safeTotalPacks <= 11 ? ABO_60_POOL_NAME : ABO_120_POOL_NAME;
}

function selectPoolsForAbonement(pools, totalPacks) {
  if (!Array.isArray(pools) || !pools.length) return [];

  const targetAboPoolName = resolveAboPoolName(totalPacks);
  return pools.filter((pool) => {
    const poolName = String(pool?.pool_name || '').trim();
    if (poolName === ABO_60_POOL_NAME || poolName === ABO_120_POOL_NAME) {
      return poolName === targetAboPoolName;
    }
    return true;
  });
}

function toTwo(value) {
  return Number(Number(value || 0).toFixed(2));
}

function toFour(value) {
  return Number(Number(value || 0).toFixed(4));
}

class PoolInflowService {
  async removeStaleAboPoolInflowsForInvoice({ conn, invoiceId, selectedAboPoolName }) {
    if (!conn || !invoiceId || !selectedAboPoolName) return 0;

    const [res] = await conn.query(
      `DELETE pi
       FROM pool_inflows pi
       INNER JOIN pools p ON p.id = pi.pool_id
       WHERE pi.invoice_id = ?
         AND pi.event_type = 'invoice_paid'
         AND p.pool_name IN (?, ?)
         AND p.pool_name <> ?`,
      [
        Number(invoiceId),
        ABO_60_POOL_NAME,
        ABO_120_POOL_NAME,
        selectedAboPoolName,
      ]
    );

    return Number(res?.affectedRows || 0);
  }

  async upsertCapsuleSalesForInvoice({ conn, invoiceId, abonementId, paidAtDate, currency, byCoffee }) {
    const entries = Array.from(byCoffee.entries());
    for (const [coffeeId, capsulesCountRaw] of entries) {
      const capsulesCount = Number(capsulesCountRaw || 0);
      if (!Number.isFinite(capsulesCount) || capsulesCount <= 0) continue;

      const details = {
        source: 'invoice_paid',
        formula: 'packs * 10',
      };

      await conn.query(
        `INSERT INTO capsule_sales
           (invoice_id, abonement_id, coffee_table_id, capsules_count, sold_at, currency, details, created_at, updated_at)
         VALUES (?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
         ON DUPLICATE KEY UPDATE
           abonement_id = VALUES(abonement_id),
           capsules_count = VALUES(capsules_count),
           sold_at = VALUES(sold_at),
           currency = VALUES(currency),
           details = VALUES(details),
           updated_at = NOW()`,
        [
          Number(invoiceId),
          Number(abonementId),
          Number(coffeeId),
          capsulesCount,
          paidAtDate,
          currency || 'EUR',
          JSON.stringify(details),
        ]
      );
    }
  }

  async getCapsuleSalesMap({ conn, invoiceId }) {
    const [rows] = await conn.query(
      `SELECT coffee_table_id, capsules_count
       FROM capsule_sales
       WHERE invoice_id = ?`,
      [Number(invoiceId)]
    );

    const byCoffee = new Map();
    for (const row of rows || []) {
      const coffeeId = Number(row.coffee_table_id);
      const capsulesCount = Number(row.capsules_count || 0);
      if (!Number.isFinite(coffeeId) || coffeeId <= 0) continue;
      if (!Number.isFinite(capsulesCount) || capsulesCount <= 0) continue;
      byCoffee.set(coffeeId, (byCoffee.get(coffeeId) || 0) + capsulesCount);
    }
    return byCoffee;
  }

  async analyzePaidInvoice({ invoiceId, paidAt }) {
    const normalizedInvoiceId = Number(invoiceId);
    if (!Number.isFinite(normalizedInvoiceId) || normalizedInvoiceId <= 0) {
      return { ok: false, reason: 'invalid_invoice_id' };
    }

    const [invoiceRows] = await db.query(
      `SELECT id, source_type, source_id, currency, status, context
       FROM invoices
       WHERE id = ?
       LIMIT 1`,
      [normalizedInvoiceId]
    );
    const invoice = invoiceRows?.[0];
    if (!invoice) return { ok: false, reason: 'invoice_not_found' };
    if (String(invoice.status) !== 'paid') return { ok: false, reason: 'invoice_not_paid', invoice };
    if (String(invoice.source_type) !== 'subscription') return { ok: false, reason: 'unsupported_source_type', invoice };

    const abonementId = Number(invoice.source_id);
    if (!Number.isFinite(abonementId) || abonementId <= 0) {
      return { ok: false, reason: 'missing_abonement_relation', invoice };
    }

    const paidAtCandidate = paidAt ? new Date(paidAt) : new Date();
    const paidAtDate = Number.isFinite(paidAtCandidate.getTime()) ? paidAtCandidate : new Date();

    let context = {};
    try {
      context = invoice.context && typeof invoice.context === 'string' ? JSON.parse(invoice.context) : (invoice.context || {});
    } catch (_) {
      context = {};
    }
    const periodStart = context?.period_start ? new Date(context.period_start) : null;
    if (periodStart && Number.isFinite(periodStart.getTime()) && paidAtDate < periodStart) {
      return { ok: false, reason: 'paid_before_period_start', invoice, abonementId, paidAtDate, periodStart };
    }

    const [abonRows] = await db.query(
      `SELECT id, pack_breakdown, currency
       FROM coffee_abonements
       WHERE id = ?
       LIMIT 1`,
      [abonementId]
    );
    const abonement = abonRows?.[0];
    if (!abonement) return { ok: false, reason: 'abonement_not_found', invoice, abonementId };

    const breakdownRaw = abonement.pack_breakdown;
    let breakdown = [];
    try {
      breakdown = breakdownRaw
        ? (typeof breakdownRaw === 'string' ? JSON.parse(breakdownRaw) : breakdownRaw)
        : [];
    } catch (_) {
      breakdown = [];
    }

    const normalizedLines = Array.isArray(breakdown)
      ? breakdown
          .map((line) => ({
            coffeeId: Number(line?.coffee_table_id),
            packsCount: Number(line?.packs || 0),
            capsulesCount: Number(line?.packs || 0) * 10,
          }))
          .filter((line) => (
            Number.isFinite(line.coffeeId)
            && line.coffeeId > 0
            && Number.isFinite(line.packsCount)
            && line.packsCount > 0
            && Number.isFinite(line.capsulesCount)
            && line.capsulesCount > 0
          ))
      : [];

    if (!normalizedLines.length) return { ok: false, reason: 'no_breakdown_lines', invoice, abonementId };

    const totalPacks = normalizedLines.reduce((sum, line) => sum + Number(line.packsCount || 0), 0);

    const byCoffee = new Map();
    for (const line of normalizedLines) {
      byCoffee.set(line.coffeeId, (byCoffee.get(line.coffeeId) || 0) + line.capsulesCount);
    }

    const placeholders = SYSTEM_POOL_NAMES.map(() => '?').join(',');
     const [pools] = await db.query(
      `SELECT p.id,
              p.pool_name,
              MAX(COALESCE(r.price_per_capsule_gross, p.price, 0)) AS price_per_capsule_gross,
              COUNT(DISTINCT pm.user_id) AS members_count
       FROM pools p
       LEFT JOIN pool_capsule_rules r ON r.pool_id = p.id AND r.is_active = 1
       LEFT JOIN pool_members pm ON pm.pool_id = p.id
       WHERE p.is_active = 1
         AND p.pool_name IN (${placeholders})
       GROUP BY p.id, p.pool_name`,
      SYSTEM_POOL_NAMES
    );

    if (!Array.isArray(pools) || pools.length === 0) {
      return { ok: false, reason: 'no_active_system_pools', invoice, abonementId, normalizedLines };
    }

    const selectedPools = selectPoolsForAbonement(pools, totalPacks);
    if (!selectedPools.length) {
      return { ok: false, reason: 'no_matching_system_pools', invoice, abonementId, normalizedLines, totalPacks };
    }

    return {
      ok: true,
      reason: 'ok',
      invoice,
      abonementId,
      paidAtDate,
      byCoffee,
      pools: selectedPools,
      normalizedLines,
      totalPacks,
      selectedAboPoolName: resolveAboPoolName(totalPacks),
      currency: invoice.currency || abonement.currency || 'EUR',
    };
  }

  async bookForPaidInvoice({ invoiceId, paidAt, actorUserId = null }) {
    const analysis = await this.analyzePaidInvoice({ invoiceId, paidAt });
    if (!analysis.ok) {
      return { inserted: 0, skipped: 0, reason: analysis.reason };
    }

    const normalizedInvoiceId = Number(analysis.invoice.id);
    const abonementId = Number(analysis.abonementId);
    const paidAtDate = analysis.paidAtDate;
    const byCoffee = analysis.byCoffee;
    const pools = analysis.pools;
    const currency = analysis.currency;
    const selectedAboPoolName = analysis.selectedAboPoolName;
    const conn = await db.getConnection();
    let inserted = 0;
    try {
      let alreadyExists = 0;
      let staleRemoved = 0;
      await conn.beginTransaction();

      staleRemoved = await this.removeStaleAboPoolInflowsForInvoice({
        conn,
        invoiceId: normalizedInvoiceId,
        selectedAboPoolName,
      });

      await this.upsertCapsuleSalesForInvoice({
        conn,
        invoiceId: normalizedInvoiceId,
        abonementId,
        paidAtDate,
        currency,
        byCoffee,
      });

      const capsuleSalesByCoffee = await this.getCapsuleSalesMap({ conn, invoiceId: normalizedInvoiceId });
      const coffeeEntries = Array.from(capsuleSalesByCoffee.entries());
      const totalCandidates = pools.reduce((acc, pool) => {
        const memberMultiplier = pool.pool_name === 'Core' ? Number(pool.members_count || 0) : 1;
        if (memberMultiplier <= 0) return acc;
        return acc + coffeeEntries.length;
      }, 0);

      for (const pool of pools) {
        const pricePerCapsuleGross = toFour(pool.price_per_capsule_gross);
        const memberMultiplier = pool.pool_name === 'Core' ? Number(pool.members_count || 0) : 1;
        if (memberMultiplier <= 0) continue;

        for (const [coffeeId, capsulesCountRaw] of coffeeEntries) {
          const capsulesCount = Number(capsulesCountRaw || 0);
          if (!capsulesCount) continue;

          const amountGross = toTwo(capsulesCount * pricePerCapsuleGross * memberMultiplier);
          const details = {
            source: 'invoice_paid',
            formula: 'capsules_count * price_per_capsule_gross * member_multiplier',
            paid_at: paidAtDate,
            booking_basis: 'gross',
            compatibility_note: 'gross values stored in existing net columns',
            total_packs: analysis.totalPacks,
            selected_abo_pool: resolveAboPoolName(analysis.totalPacks),
            member_multiplier: memberMultiplier,
            core_members_count: pool.pool_name === 'Core' ? memberMultiplier : null,
          };

          const [res] = await conn.query(
            `INSERT INTO pool_inflows
               (pool_id, invoice_id, abonement_id, coffee_table_id, event_type, capsules_count, price_per_capsule_net, amount_net, currency, details, created_by_user_id, created_at)
             VALUES (?, ?, ?, ?, 'invoice_paid', ?, ?, ?, ?, ?, ?, NOW())
             ON DUPLICATE KEY UPDATE id = id`,
            [
              Number(pool.id),
              normalizedInvoiceId,
              abonementId,
              Number(coffeeId),
              capsulesCount,
              pricePerCapsuleGross,
              amountGross,
              currency,
              JSON.stringify(details),
              actorUserId || null,
            ]
          );

          if (res && Number(res.affectedRows) === 1) inserted += 1;
          else alreadyExists += 1;
        }
      }

      await conn.commit();
      return {
        inserted,
        alreadyExists,
        staleRemoved,
        skipped: Math.max(0, totalCandidates - inserted - alreadyExists),
        reason: 'ok',
      };
    } catch (err) {
      await conn.rollback();
      throw err;
    } finally {
      conn.release();
    }
  }

  async getInvoiceInflowDiagnostics({ invoiceId, paidAt }) {
    const analysis = await this.analyzePaidInvoice({ invoiceId, paidAt });
    if (!analysis.ok) {
      return {
        ok: false,
        reason: analysis.reason,
        invoiceId: Number(invoiceId),
      };
    }

    const invoiceIdNum = Number(analysis.invoice.id);
    const poolEntries = [];
    let coffeeEntries = [];

    const [salesRows] = await db.query(
      `SELECT coffee_table_id, capsules_count
       FROM capsule_sales
       WHERE invoice_id = ?`,
      [invoiceIdNum]
    );

    if (Array.isArray(salesRows) && salesRows.length) {
      coffeeEntries = salesRows
        .map((row) => [Number(row.coffee_table_id), Number(row.capsules_count || 0)])
        .filter(([coffeeId, capsulesCount]) => Number.isFinite(coffeeId) && coffeeId > 0 && Number.isFinite(capsulesCount) && capsulesCount > 0);
    } else {
      coffeeEntries = Array.from(analysis.byCoffee.entries());
    }

    for (const pool of analysis.pools) {
      const pricePerCapsuleGross = toFour(pool.price_per_capsule_gross);
      const memberMultiplier = pool.pool_name === 'Core' ? Number(pool.members_count || 0) : 1;
      if (memberMultiplier <= 0) continue;

      for (const [coffeeId, capsulesCountRaw] of coffeeEntries) {
        const capsulesCount = Number(capsulesCountRaw || 0);
        const amountGross = toTwo(capsulesCount * pricePerCapsuleGross * memberMultiplier);
        const [existingRows] = await db.query(
          `SELECT id, created_at
           FROM pool_inflows
           WHERE pool_id = ? AND invoice_id = ? AND coffee_table_id = ? AND event_type = 'invoice_paid'
           LIMIT 1`,
          [Number(pool.id), invoiceIdNum, Number(coffeeId)]
        );

        poolEntries.push({
          pool_id: Number(pool.id),
          pool_name: pool.pool_name,
          coffee_table_id: Number(coffeeId),
          capsules_count: capsulesCount,
          price_per_capsule_gross: pricePerCapsuleGross,
          member_multiplier: memberMultiplier,
          core_members_count: pool.pool_name === 'Core' ? memberMultiplier : null,
          amount_gross: amountGross,
          already_booked: !!existingRows?.length,
          existing_inflow_id: existingRows?.[0]?.id || null,
        });
      }
    }

    return {
      ok: true,
      reason: 'ok',
      invoice_id: invoiceIdNum,
      abonement_id: Number(analysis.abonementId),
      paid_at: analysis.paidAtDate,
      currency: analysis.currency,
      candidates: poolEntries,
      will_book_count: poolEntries.filter((x) => !x.already_booked).length,
      already_booked_count: poolEntries.filter((x) => x.already_booked).length,
    };
  }
}

module.exports = new PoolInflowService();
