import {
  BadRequestError,
  NotFoundError,
} from "../../common/errors/http-errors";
import { stripe } from "../../config/stripe.config";
import * as paymentRepo from "./payment.repository";
import { Request } from "express";
import { logger } from "../../common/utils/logger";
import { emitPaymentUpdate, emitTaskStatusUpdate } from "../../config/socket";
import { prisma } from "../../config/prisma";

async function createStripeSession(
  invoice: {
    id: string;
    invoiceNumber: string;
    status: string;
    taskId: string | null;
    totalAmount: { toString(): string };
  },
  clientId: string,
  clientEmail: string
) {
  const totalAmount = Number(invoice.totalAmount);

  if (!Number.isFinite(totalAmount) || totalAmount <= 0) {
    throw new BadRequestError("Invalid invoice amount");
  }

  const amountInPaise = Math.round(totalAmount * 100);

  // Stripe minimum for INR is ₹50
  if (amountInPaise < 5000) {
    throw new BadRequestError(
      `Invoice amount ₹${totalAmount.toFixed(2)} is below the minimum payable amount of ₹50.`
    );
  }

  const metadata = {
    invoiceId: invoice.id,
    clientId,
    ...(invoice.taskId ? { taskId: invoice.taskId } : {}),
  };

  return stripe.checkout.sessions.create({
    mode: "payment",
    line_items: [
      {
        price_data: {
          currency: "inr",
          product_data: {
            name: `TaxFend Invoice ${invoice.invoiceNumber}`,
            description: `Payment for invoice ${invoice.invoiceNumber}`,
          },
          unit_amount: amountInPaise,
        },
        quantity: 1,
      },
    ],
    customer_email: clientEmail,
    success_url:
      `${process.env.FRONTEND_URL}/client/invoices/${invoice.id}` +
      "?payment=success",
    cancel_url:
      `${process.env.FRONTEND_URL}/client/invoices/${invoice.id}` +
      "?payment=cancelled",
    metadata, // session-level metadata (used by checkout.session.completed)
    payment_intent_data: {
      metadata, // 👈 CRITICAL: without this, pi.metadata is always empty
    },
  });
}

export async function createCheckoutSession(userId: string, invoiceId: string) {
  const client = await paymentRepo.findClientByUserId(userId);
  if (!client) throw new NotFoundError("Client not found");

  const invoice = await paymentRepo.findInvoiceForPayment(invoiceId, client.id);
  if (!invoice) throw new NotFoundError("Invoice not found");
  if (invoice.status === "PAID")
    throw new BadRequestError("Invoice is already paid");
  if (invoice.status === "CANCELLED")
    throw new BadRequestError("Invoice is cancelled");

  const session = await createStripeSession(invoice, client.id, client.email);
  if (!session.url)
    throw new BadRequestError("Unable to create Stripe checkout session");

  await paymentRepo.updateInvoicePaymentLink(invoice.id, session.url);

  // Avoid duplicate rows — reuse existing INITIATED payment, just update transactionRef
  const existingPayment = await paymentRepo.findInitiatedPayment(invoice.id);
  if (existingPayment) {
    await paymentRepo.updatePaymentTransactionRef(
      existingPayment.id,
      session.id
    );
  } else {
    await paymentRepo.createPayment({
      invoiceId: invoice.id,
      clientId: client.id,
      amount: Number(invoice.totalAmount),
      method: "CARD",
      transactionRef: session.id,
    });
  }

  return {
    sessionId: session.id,
    checkoutUrl: session.url,
  };
}

export async function generateInvoicePaymentLink(invoiceId: string) {
  const invoice = await paymentRepo.findInvoiceWithClient(invoiceId);
  if (!invoice) throw new NotFoundError("Invoice not found");
  if (invoice.status === "PAID")
    throw new BadRequestError("Invoice is already paid");
  if (invoice.status === "CANCELLED")
    throw new BadRequestError("Invoice is cancelled");

  const session = await createStripeSession(
    invoice,
    invoice.clientId,
    invoice.client.email
  );
  if (!session.url)
    throw new BadRequestError("Unable to create Stripe checkout session");

  await paymentRepo.updateInvoicePaymentLink(invoice.id, session.url);
  return session.url;
}

export async function getClientInvoice(userId: string, invoiceId: string) {
  const client = await paymentRepo.findClientByUserId(userId);
  if (!client) throw new NotFoundError("Client not found");
  const invoice = await paymentRepo.findInvoiceForClient(invoiceId, client.id);
  if (!invoice) throw new NotFoundError("Invoice not found");
  return invoice;
}

export async function getAdminInvoice(invoiceId: string) {
  const invoice = await paymentRepo.findInvoiceForAdmin(invoiceId);
  if (!invoice) throw new NotFoundError("Invoice not found");
  return invoice;
}

export async function getAdminTransactions(
  userId: string,
  page: number,
  limit: number,
  status?: string
) {
  const user = await import("../../config/prisma").then(({ prisma }) =>
    prisma.user.findUnique({
      where: { id: userId },
      select: { organizationId: true },
    })
  );
  if (!user?.organizationId) throw new NotFoundError("Organization not found");
  return paymentRepo.findAdminTransactions(
    user.organizationId,
    page,
    limit,
    status
  );
}

export async function getClientTransactions(
  userId: string,
  page: number,
  limit: number,
  status?: string
) {
  const client = await paymentRepo.findClientByUserId(userId);
  if (!client) throw new NotFoundError("Client not found");
  return paymentRepo.findClientTransactions(client.id, page, limit, status);
}

export async function handleStripeWebhook(req: Request) {
  const sig = req.headers["stripe-signature"] as string;
  const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;

  let event: any;
  try {
    let bodyStr: string;
    if (Buffer.isBuffer(req.body)) {
      bodyStr = req.body.toString("utf8");
    } else if (typeof req.body === "string") {
      bodyStr = req.body;
    } else {
      bodyStr = JSON.stringify(req.body);
    }

    event =
      webhookSecret && sig
        ? stripe.webhooks.constructEvent(
            Buffer.isBuffer(req.body) ? req.body : bodyStr,
            sig,
            webhookSecret
          )
        : JSON.parse(bodyStr);
  } catch (err) {
    logger.warn({ err }, "Stripe webhook signature verification failed");
    throw new BadRequestError("Webhook signature invalid");
  }

  logger.info({ eventType: event.type }, "Stripe webhook received");

  // ── SUCCESS ──────────────────────────────────────────────────────────────
  if (event.type === "checkout.session.completed") {
    const session = event.data.object;
    const invoiceId = session.metadata?.invoiceId;
    if (invoiceId) {
      await paymentRepo.markInvoicePaid(invoiceId, session.id);
      const invoice = await prisma.invoice.findUnique({
        where: { id: invoiceId },
        select: { taskId: true, task: { select: { id: true, organizationId: true } } },
      });
      if (invoice?.task?.organizationId) {
        const orgId = invoice.task.organizationId;
        emitPaymentUpdate(orgId, { invoiceId, status: "PAID", taskId: invoice.taskId });
        if (invoice.taskId) {
          emitTaskStatusUpdate(orgId, { taskId: invoice.taskId, status: "COMPLETED" });
        }
      }
    }
    return;
  }

  // Fallback: direct PI charge or missed checkout.session.completed
  if (event.type === "payment_intent.succeeded") {
    const pi = event.data.object;
    const invoiceId = pi.metadata?.invoiceId;
    if (!invoiceId) return;
    await paymentRepo.markInvoicePaid(invoiceId, pi.id);
    const invoice = await prisma.invoice.findUnique({
      where: { id: invoiceId },
      select: { taskId: true, task: { select: { id: true, organizationId: true } } },
    });
    if (invoice?.task?.organizationId) {
      const orgId = invoice.task.organizationId;
      emitPaymentUpdate(orgId, { invoiceId, status: "PAID", taskId: invoice.taskId });
      if (invoice.taskId) {
        emitTaskStatusUpdate(orgId, { taskId: invoice.taskId, status: "COMPLETED" });
      }
    }
    return;
  }

  // ── FAILED ───────────────────────────────────────────────────────────────
  if (event.type === "payment_intent.payment_failed") {
    const pi = event.data.object;
    const invoiceId = pi.metadata?.invoiceId;
    if (!invoiceId) return;
    logger.warn({ invoiceId, piId: pi.id }, "payment_intent.payment_failed");
    await paymentRepo.markPaymentFailed(invoiceId);
    const invoice = await prisma.invoice.findUnique({
      where: { id: invoiceId },
      select: { taskId: true, task: { select: { organizationId: true } } },
    });
    if (invoice?.task?.organizationId) {
      emitPaymentUpdate(invoice.task.organizationId, {
        invoiceId,
        status: "FAILED",
        taskId: invoice.taskId,
      });
    }
    return;
  }

  // checkout expired (user closed tab without paying)
  if (event.type === "checkout.session.expired") {
    const session = event.data.object;
    const invoiceId = session.metadata?.invoiceId;
    if (!invoiceId) return;
    logger.info({ invoiceId }, "checkout.session.expired");
    await paymentRepo.markPaymentFailed(invoiceId);
    return;
  }
}
