import { Resend } from "resend";
import { prisma } from "@/lib/prisma";
import { renderEmail, type EmailTemplate } from "@/lib/email/templates";

/**
 * Email sender — idempotent, dengan fallback retry queue.
 *
 * Flow:
 * 1. Cek `EmailSent` by `idempotencyKey`. Kalau sudah terkirim, skip (return).
 * 2. Render template → `{ subject, html }`.
 * 3. Try sync send via Resend.
 * 4. Sukses → insert `EmailSent`.
 * 5. Gagal → insert `EmailJob` (cron retry).
 *
 * Caller pattern: `void emailService.send(...)` (fire-and-forget). Service ini
 * tidak throw — semua error di-handle internal supaya server action tidak gagal.
 */

interface SendInput {
  to: string;
  idempotencyKey: string;
  template: EmailTemplate;
}

let _resend: Resend | null = null;
function getResend(): Resend | null {
  const apiKey = process.env.RESEND_API_KEY;
  if (!apiKey) return null;
  if (!_resend) _resend = new Resend(apiKey);
  return _resend;
}

function emailFrom(): string {
  return process.env.EMAIL_FROM ?? "SeTrip <onboarding@resend.dev>";
}

export const emailService = {
  async send(input: SendInput): Promise<void> {
    try {
      // 1. Idempotency check
      const existing = await prisma.emailSent.findUnique({
        where: { idempotencyKey: input.idempotencyKey },
        select: { id: true },
      });
      if (existing) return;

      // 2. Render
      const rendered = renderEmail(input.template);

      // 3. Try sync send
      const resend = getResend();
      if (!resend) {
        // Env tidak di-set — enqueue saja supaya tetap ter-log.
        await enqueueJob(input, rendered);
        console.warn(
          "[email] RESEND_API_KEY tidak di-set, email di-queue:",
          input.template.template,
          input.to
        );
        return;
      }

      try {
        const result = await resend.emails.send({
          from: emailFrom(),
          to: input.to,
          subject: rendered.subject,
          html: rendered.html,
        });
        if (result.error) {
          throw new Error(result.error.message ?? "Resend send failed");
        }
        // 4. Mark sent
        await prisma.emailSent.create({
          data: {
            idempotencyKey: input.idempotencyKey,
            to: input.to,
            template: input.template.template,
            subject: rendered.subject,
            html: rendered.html,
            providerMessageId: result.data?.id ?? null,
          },
        });
      } catch (err) {
        // 5. Enqueue retry
        await enqueueJob(input, rendered);
        console.error(
          "[email] sync send failed, queued for retry:",
          input.template.template,
          input.to,
          err
        );
      }
    } catch (err) {
      // Catch-all — jangan biarkan email error ngerusak action utama.
      console.error("[email] unexpected error:", err);
    }
  },

  /**
   * Process pending/failed jobs di queue. Dipanggil dari cron handler.
   * Max 5 attempts dengan exponential backoff (5min × 2^attempts).
   */
  async processQueue(limit = 50): Promise<{
    picked: number;
    succeeded: number;
    failed: number;
  }> {
    const now = new Date();
    const jobs = await prisma.emailJob.findMany({
      where: {
        status: { in: ["PENDING", "FAILED"] },
        attempts: { lt: 5 },
        scheduledAt: { lte: now },
      },
      orderBy: { scheduledAt: "asc" },
      take: limit,
    });

    let succeeded = 0;
    let failed = 0;

    const resend = getResend();
    if (!resend) {
      console.warn("[email] processQueue: RESEND_API_KEY tidak di-set, skip");
      return { picked: jobs.length, succeeded: 0, failed: 0 };
    }

    for (const job of jobs) {
      // Re-check idempotency — bisa jadi email sudah terkirim oleh sync attempt sejak job di-enqueue.
      const alreadySent = await prisma.emailSent.findUnique({
        where: { idempotencyKey: job.idempotencyKey },
        select: { id: true },
      });
      if (alreadySent) {
        await prisma.emailJob.update({
          where: { id: job.id },
          data: { status: "SUCCESS", lastAttemptAt: now },
        });
        succeeded++;
        continue;
      }

      // Mark PROCESSING (best-effort lock)
      await prisma.emailJob.update({
        where: { id: job.id },
        data: { status: "PROCESSING", attempts: job.attempts + 1, lastAttemptAt: now },
      });

      try {
        const result = await resend.emails.send({
          from: emailFrom(),
          to: job.to,
          subject: job.subject,
          html: job.html,
        });
        if (result.error) throw new Error(result.error.message ?? "Resend failed");

        await prisma.$transaction([
          prisma.emailSent.create({
            data: {
              idempotencyKey: job.idempotencyKey,
              to: job.to,
              template: job.template,
              subject: job.subject,
              html: job.html,
              providerMessageId: result.data?.id ?? null,
            },
          }),
          prisma.emailJob.update({
            where: { id: job.id },
            data: { status: "SUCCESS" },
          }),
        ]);
        succeeded++;
      } catch (err) {
        const nextAttempt = job.attempts + 1;
        const backoffMin = Math.min(60, 5 * Math.pow(2, nextAttempt - 1));
        await prisma.emailJob.update({
          where: { id: job.id },
          data: {
            status: "FAILED",
            lastError: err instanceof Error ? err.message : String(err),
            scheduledAt: new Date(now.getTime() + backoffMin * 60 * 1000),
          },
        });
        failed++;
      }
    }

    return { picked: jobs.length, succeeded, failed };
  },

  /**
   * Admin "Retry now" untuk satu EmailJob — kirim ulang langsung tanpa
   * menunggu cron. Idempotent: kalau email sudah tercatat terkirim, job
   * ditandai SUCCESS tanpa kirim ulang.
   */
  async retryJob(jobId: string): Promise<{ ok: boolean; error?: string }> {
    const job = await prisma.emailJob.findUnique({ where: { id: jobId } });
    if (!job) return { ok: false, error: "Email job tidak ditemukan" };

    const alreadySent = await prisma.emailSent.findUnique({
      where: { idempotencyKey: job.idempotencyKey },
      select: { id: true },
    });
    if (alreadySent) {
      await prisma.emailJob.update({
        where: { id: jobId },
        data: { status: "SUCCESS", lastAttemptAt: new Date() },
      });
      return { ok: true };
    }

    const resend = getResend();
    if (!resend) return { ok: false, error: "RESEND_API_KEY belum di-set" };

    const now = new Date();
    try {
      const result = await resend.emails.send({
        from: emailFrom(),
        to: job.to,
        subject: job.subject,
        html: job.html,
      });
      if (result.error) throw new Error(result.error.message ?? "Resend failed");
      await prisma.$transaction([
        prisma.emailSent.create({
          data: {
            idempotencyKey: job.idempotencyKey,
            to: job.to,
            template: job.template,
            subject: job.subject,
            html: job.html,
            providerMessageId: result.data?.id ?? null,
          },
        }),
        prisma.emailJob.update({
          where: { id: jobId },
          data: {
            status: "SUCCESS",
            attempts: job.attempts + 1,
            lastAttemptAt: now,
          },
        }),
      ]);
      return { ok: true };
    } catch (err) {
      const message = err instanceof Error ? err.message : String(err);
      await prisma.emailJob.update({
        where: { id: jobId },
        data: {
          status: "FAILED",
          attempts: job.attempts + 1,
          lastAttemptAt: now,
          lastError: message,
        },
      });
      return { ok: false, error: message };
    }
  },

  /**
   * Admin "Resend" untuk EmailSent yang sudah pernah terkirim — mis. user
   * lapor tidak menerima. Pakai idempotencyKey turunan supaya tidak bentrok
   * dengan email asli. Butuh `html` tersimpan (row lama tidak bisa di-resend).
   */
  async resendEmail(
    emailSentId: string
  ): Promise<{ ok: boolean; error?: string }> {
    const original = await prisma.emailSent.findUnique({
      where: { id: emailSentId },
    });
    if (!original) return { ok: false, error: "Email tidak ditemukan" };
    if (!original.html) {
      return {
        ok: false,
        error:
          "Body email lama tidak tersimpan — tidak bisa di-resend (dikirim sebelum fitur ini ada).",
      };
    }

    const resend = getResend();
    if (!resend) return { ok: false, error: "RESEND_API_KEY belum di-set" };

    try {
      const result = await resend.emails.send({
        from: emailFrom(),
        to: original.to,
        subject: original.subject,
        html: original.html,
      });
      if (result.error) throw new Error(result.error.message ?? "Resend failed");
      await prisma.emailSent.create({
        data: {
          idempotencyKey: `${original.idempotencyKey}#resend-${Date.now()}`,
          to: original.to,
          template: original.template,
          subject: original.subject,
          html: original.html,
          providerMessageId: result.data?.id ?? null,
        },
      });
      return { ok: true };
    } catch (err) {
      return {
        ok: false,
        error: err instanceof Error ? err.message : String(err),
      };
    }
  },
};

async function enqueueJob(
  input: SendInput,
  rendered: { subject: string; html: string }
): Promise<void> {
  await prisma.emailJob.create({
    data: {
      idempotencyKey: input.idempotencyKey,
      to: input.to,
      template: input.template.template,
      subject: rendered.subject,
      html: rendered.html,
      status: "PENDING",
    },
  });
}
