import { pool, query } from "../../db/pool.js";
import { env } from "../../config/env.js";
import { recordAppEvent } from "../../lib/app-log.js";
import { sendPushNotification } from "../../lib/push.js";
import { sendOutboxEmail } from "../../lib/mailer.js";
import { escapeHtml, renderTikeTrackEmailTemplate } from "../../lib/email-template.js";
import { deletePushSubscription, listPushSubscriptions } from "../account/account.service.js";
import { listWarrantyReminderCandidates } from "../products/products.service.js";

export interface WarrantyReminderDaySummary {
  reminderDays: number;
  candidates: number;
  createdOutbox: number;
  sent: number;
  skipped: number;
  emailFailed: number;
  pushSent: number;
  pushFailed: number;
  pushSkipped: number;
  pushExpired: number;
}

export interface WarrantyReminderCycleSummary {
  reminderDays: number[];
  batchLimit: number;
  candidates: number;
  createdOutbox: number;
  sent: number;
  skipped: number;
  emailFailed: number;
  pushSent: number;
  pushFailed: number;
  pushSkipped: number;
  pushExpired: number;
  remainingLimit: number;
  days: WarrantyReminderDaySummary[];
}

function buildReminderSubject(daysAhead: number): string {
  return daysAhead === 30
    ? "Tu garantía vence en 30 días"
    : `Tu garantía vence en ${daysAhead} días`;
}

function buildReminderBody(input: {
  name: string;
  productName: string;
  brand: string;
  model: string;
  category: string;
  warrantyEndDate: string;
  daysAhead: number;
}): string {
  return [
    `Hola ${input.name},`,
    "",
    `Te avisamos de que la garantía de "${input.productName}" está próxima a vencer.`,
    `Producto: ${input.productName}`,
    `Marca: ${input.brand}`,
    `Modelo: ${input.model}`,
    `Categoría: ${input.category}`,
    `Fecha de vencimiento: ${input.warrantyEndDate}`,
    "",
    input.daysAhead === 30
      ? "Queda aproximadamente un mes para revisar el ticket, la factura o la gestión de la garantía."
      : `Quedan aproximadamente ${input.daysAhead} días para revisar el ticket, la factura o la gestión de la garantía.`,
    "",
    "Puedes entrar en TikeTrack para revisar el detalle completo del producto.",
  ].join("\n");
}

function buildPushPayload(input: {
  productId: number;
  productName: string;
  brand: string;
  model: string;
  daysAhead: number;
}): Record<string, unknown> {
  return {
    title: input.daysAhead === 30
      ? "Tu garantía vence en 30 días"
      : `Tu garantía vence en ${input.daysAhead} días`,
    body: `${input.productName} (${input.brand} ${input.model})`,
    url: `${env.WEB_BASE_URL}/productos/${input.productId}`,
    tag: `warranty-reminder-${input.productId}-${input.daysAhead}`,
    renotify: false,
  };
}

function buildReminderHtml(input: {
  name: string;
  productName: string;
  brand: string;
  model: string;
  category: string;
  warrantyEndDate: string;
  daysAhead: number;
  productId: number;
}): string {
  const isOneMonth = input.daysAhead === 30;
  const remaining = isOneMonth
    ? "Queda aproximadamente un mes para revisar el ticket, la factura o la gestión de la garantía."
    : `Quedan aproximadamente ${input.daysAhead} días para revisar el ticket, la factura o la gestión de la garantía.`;
  return renderTikeTrackEmailTemplate({
    preheader: `La garantía de ${input.productName} vence pronto.`,
    eyebrow: "Aviso de garantía",
    title: `Hola ${input.name}, tu garantía vence pronto`,
    introHtml: `<p>Te avisamos de que la garantía de <strong>${escapeHtml(input.productName)}</strong> está próxima a vencer.</p>`,
    action: { label: "Ver producto", url: `${env.WEB_BASE_URL}/productos/${input.productId}` },
    detailRows: [
      { label: "Producto", value: input.productName },
      { label: "Marca", value: input.brand || "No indicada" },
      { label: "Modelo", value: input.model || "No indicado" },
      { label: "Categoría", value: input.category || "No indicada" },
      { label: "Fecha de vencimiento", value: input.warrantyEndDate },
    ],
    outroHtml: `<p>${remaining}</p><p>Puedes entrar en TikeTrack para revisar el detalle completo del producto.</p>`,
  });
}

async function ensureReminderDeliveryRecord(input: {
  userId: number;
  productId: number;
  reminderDays: number;
  reminderDate: string;
}): Promise<{
  deliveryId: number;
  created: boolean;
  emailStatus: "pending" | "sent" | "failed" | "skipped";
  pushStatus: "pending" | "sent" | "failed" | "skipped" | "expired";
  emailOutboxId: number | null;
}> {
  const client = await pool.connect();
  try {
    await client.query("BEGIN");

    const existing = await client.query<{
      id: number;
      email_status: "pending" | "sent" | "failed" | "skipped";
      push_status: "pending" | "sent" | "failed" | "skipped" | "expired";
      email_outbox_id: number | null;
    }>(
      `SELECT id, email_status, push_status, email_outbox_id
       FROM warranty_notification_deliveries
       WHERE user_id = $1
         AND product_id = $2
         AND reminder_days = $3
         AND reminder_date = $4::date
       LIMIT 1`,
      [input.userId, input.productId, input.reminderDays, input.reminderDate]
    );

    if (existing.rows[0]) {
      await client.query("COMMIT");
      return {
        deliveryId: existing.rows[0].id,
        created: false,
        emailStatus: existing.rows[0].email_status,
        pushStatus: existing.rows[0].push_status,
        emailOutboxId: existing.rows[0].email_outbox_id,
      };
    }

    const delivery = await client.query<{ id: number }>(
      `INSERT INTO warranty_notification_deliveries (
         user_id, product_id, reminder_days, reminder_date
       ) VALUES ($1, $2, $3, $4::date)
       RETURNING id`,
      [input.userId, input.productId, input.reminderDays, input.reminderDate]
    );

    const deliveryId = delivery.rows[0]!.id;
    await client.query(
      `INSERT INTO warranty_reminder_log (
         user_id, product_id, reminder_days, reminder_date, email_outbox_id
       ) VALUES ($1, $2, $3, $4::date, NULL)`,
      [input.userId, input.productId, input.reminderDays, input.reminderDate]
    );

    await client.query("COMMIT");
    return {
      deliveryId,
      created: true,
      emailStatus: "pending",
      pushStatus: "pending",
      emailOutboxId: null,
    };
  } catch (err) {
    await client.query("ROLLBACK");
    throw err;
  } finally {
    client.release();
  }
}

async function runWarrantyReminderDay(daysAhead: number, limit: number): Promise<WarrantyReminderDaySummary> {
  if (limit <= 0) {
    return {
      reminderDays: daysAhead,
      candidates: 0,
      createdOutbox: 0,
      sent: 0,
      skipped: 0,
      emailFailed: 0,
      pushSent: 0,
      pushFailed: 0,
      pushSkipped: 0,
      pushExpired: 0,
    };
  }

  const candidates = await listWarrantyReminderCandidates(daysAhead, limit);

  let createdOutbox = 0;
  let sent = 0;
  let skipped = 0;
  let emailFailed = 0;
  let pushSent = 0;
  let pushFailed = 0;
  let pushSkipped = 0;
  let pushExpired = 0;

  for (const candidate of candidates) {
    const subject = buildReminderSubject(daysAhead);
    const body = buildReminderBody({
      name: candidate.user_name,
      productName: candidate.product_name,
      brand: candidate.brand,
      model: candidate.model,
      category: candidate.category,
      warrantyEndDate: candidate.warranty_end_date,
      daysAhead,
    });
    const htmlBody = buildReminderHtml({
      name: candidate.user_name,
      productName: candidate.product_name,
      brand: candidate.brand,
      model: candidate.model,
      category: candidate.category,
      warrantyEndDate: candidate.warranty_end_date,
      daysAhead,
      productId: candidate.product_id,
    });

    const delivery = await ensureReminderDeliveryRecord({
      userId: candidate.user_id,
      productId: candidate.product_id,
      reminderDays: daysAhead,
      reminderDate: candidate.effective_end_date,
    });

    const alreadyDone =
      delivery.emailStatus !== "pending" &&
      delivery.pushStatus !== "pending" &&
      delivery.emailStatus !== "failed" &&
      delivery.pushStatus !== "failed";
    if (!delivery.created && alreadyDone) {
      skipped += 1;
      continue;
    }

    let emailStatus = delivery.emailStatus;
    let emailOutboxId = delivery.emailOutboxId;

    if (candidate.warranty_email_enabled) {
      if (emailStatus !== "sent") {
        if (!emailOutboxId) {
          const outbox = await query<{ id: number }>(
            `INSERT INTO email_outbox (user_id, recipient, subject, body, html_body)
             VALUES ($1, $2, $3, $4, $5)
             RETURNING id`,
            [candidate.user_id, candidate.user_email, subject, body, htmlBody]
          );
          emailOutboxId = outbox[0]?.id ?? null;
          if (emailOutboxId) {
            createdOutbox += 1;
          }
        }

        let delivered = false;
        if (emailOutboxId) {
          delivered = await sendOutboxEmail({
            id: emailOutboxId,
            recipient: candidate.user_email,
            subject,
            body,
            htmlBody,
          });
        }

        emailStatus = delivered ? "sent" : "failed";
        if (delivered) {
          sent += 1;
        } else {
          emailFailed += 1;
        }
      }
    } else if (emailStatus === "pending") {
      emailStatus = "skipped";
      skipped += 1;
    }

    await query(
      `UPDATE warranty_notification_deliveries
       SET email_status = $2,
           email_outbox_id = $3,
           updated_at = NOW()
       WHERE id = $1`,
      [delivery.deliveryId, emailStatus, emailOutboxId]
    );

    let pushStatus = delivery.pushStatus;
    if (candidate.warranty_push_enabled) {
      if (pushStatus !== "sent") {
        const subscriptions = await listPushSubscriptions(candidate.user_id);
        if (subscriptions.length === 0) {
          pushStatus = "skipped";
          pushSkipped += 1;
        } else {
          const pushPayload = buildPushPayload({
            productId: candidate.product_id,
            productName: candidate.product_name,
            brand: candidate.brand,
            model: candidate.model,
            daysAhead,
          });

          const pushResults = await Promise.all(
            subscriptions.map((subscription) =>
              sendPushNotification(subscription, pushPayload)
            )
          );

          let finalPushStatus: "sent" | "failed" | "skipped" | "expired" = "skipped";
          for (let i = 0; i < pushResults.length; i += 1) {
            const result = pushResults[i];
            const subscription = subscriptions[i];
            if (result === "sent") {
              pushSent += 1;
              finalPushStatus = "sent";
              continue;
            }
            if (result === "expired") {
              pushExpired += 1;
              await deletePushSubscription(candidate.user_id, subscription.endpoint);
              if (finalPushStatus !== "sent") finalPushStatus = "expired";
              continue;
            }
            if (result === "failed") {
              pushFailed += 1;
              if (finalPushStatus !== "sent" && finalPushStatus !== "expired") finalPushStatus = "failed";
            } else {
              pushSkipped += 1;
            }
          }
          pushStatus = finalPushStatus;
        }
      }
    } else if (pushStatus === "pending") {
      pushStatus = "skipped";
      pushSkipped += 1;
    }

    await query(
      `UPDATE warranty_notification_deliveries
       SET push_status = $2,
           updated_at = NOW()
       WHERE id = $1`,
      [delivery.deliveryId, pushStatus]
    );
  }

  return {
    reminderDays: daysAhead,
    candidates: candidates.length,
    createdOutbox,
    sent,
    skipped,
    emailFailed,
    pushSent,
    pushFailed,
    pushSkipped,
    pushExpired,
  };
}

export async function runWarrantyReminderCycle(): Promise<WarrantyReminderCycleSummary> {
  const reminderDays = env.WARRANTY_REMINDER_DAYS;
  const batchLimit = env.NOTIFICATION_EMAIL_BATCH_LIMIT;
  let remainingLimit = batchLimit;
  const days: WarrantyReminderDaySummary[] = [];

  for (const daysAhead of reminderDays) {
    const result = await runWarrantyReminderDay(daysAhead, remainingLimit);
    days.push(result);
    remainingLimit = Math.max(0, remainingLimit - result.createdOutbox);
  }

  const summary: WarrantyReminderCycleSummary = {
    reminderDays,
    batchLimit,
    candidates: days.reduce((total, item) => total + item.candidates, 0),
    createdOutbox: days.reduce((total, item) => total + item.createdOutbox, 0),
    sent: days.reduce((total, item) => total + item.sent, 0),
    skipped: days.reduce((total, item) => total + item.skipped, 0),
    emailFailed: days.reduce((total, item) => total + item.emailFailed, 0),
    pushSent: days.reduce((total, item) => total + item.pushSent, 0),
    pushFailed: days.reduce((total, item) => total + item.pushFailed, 0),
    pushSkipped: days.reduce((total, item) => total + item.pushSkipped, 0),
    pushExpired: days.reduce((total, item) => total + item.pushExpired, 0),
    remainingLimit,
    days,
  };

  await recordAppEvent({
    level: "info",
    source: "maintenance.warranty_reminders",
    message: "Warranty reminder cycle executed",
    details: { ...summary },
  });

  return summary;
}
