import Stripe from 'stripe';
import { prisma } from './db.js';
import { formatFiat, paymentsProvider } from './shopCatalog.js';
import { getPurchasableOsuPack } from './shopOsu.js';
import { checkAchievements } from './achievements.js';

function clientOrigin() {
  return String(process.env.CLIENT_ORIGIN || 'http://localhost:5888').replace(/\/$/, '');
}

export function getStripe() {
  const key = process.env.STRIPE_SECRET_KEY;
  if (!key) return null;
  return new Stripe(key, { apiVersion: '2026-06-24.dahlia' });
}

/**
 * Create a pending osu top-up order.
 * Mock: client confirms via /confirm.
 * Stripe: redirects to hosted Checkout; webhook credits osu.
 */
export async function createOsuCheckout(userId, packId) {
  const pack = await getPurchasableOsuPack(packId);
  if (!pack) {
    const err = new Error('Unknown or disabled osu pack');
    err.status = 400;
    throw err;
  }

  const provider = paymentsProvider();

  const order = await prisma.paymentOrder.create({
    data: {
      userId: Number(userId),
      packId: pack.id,
      osuAmount: pack.osu,
      priceCents: pack.priceCents,
      currency: pack.currency,
      status: 'pending',
      provider,
      meta: JSON.stringify({
        packName: pack.name,
        displayPrice: formatFiat(pack.priceCents, pack.currency),
      }),
    },
  });

  const packPayload = {
    id: pack.id,
    name: pack.name,
    osu: pack.osu,
    priceCents: pack.priceCents,
    currency: pack.currency,
    displayPrice: formatFiat(pack.priceCents, pack.currency),
  };

  if (provider === 'stripe') {
    const stripe = getStripe();
    if (!stripe) {
      const err = new Error('STRIPE_SECRET_KEY is not configured');
      err.status = 500;
      throw err;
    }

    const session = await stripe.checkout.sessions.create({
      mode: 'payment',
      client_reference_id: order.id,
      metadata: {
        orderId: order.id,
        userId: String(userId),
        packId: pack.id,
      },
      line_items: [
        {
          quantity: 1,
          price_data: {
            currency: String(pack.currency || 'eur').toLowerCase(),
            unit_amount: pack.priceCents,
            product_data: {
              name: `${pack.name} — ${pack.osu} osu`,
              description: 'CaseOrion premium wallet top-up',
              // Required by Stripe Managed Payments (default on new accounts).
              // https://docs.stripe.com/payments/managed-payments/eligibility
              tax_code: 'txcd_10000000',
            },
          },
        },
      ],
      success_url: `${clientOrigin()}/shop?osu=success&order=${encodeURIComponent(order.id)}`,
      cancel_url: `${clientOrigin()}/shop?osu=cancel`,
    });

    await prisma.paymentOrder.update({
      where: { id: order.id },
      data: {
        providerRef: session.id,
        meta: JSON.stringify({
          packName: pack.name,
          displayPrice: formatFiat(pack.priceCents, pack.currency),
          checkoutSessionId: session.id,
        }),
      },
    });

    return {
      order: serializeOrder({ ...order, providerRef: session.id }),
      pack: packPayload,
      mode: 'stripe',
      url: session.url,
      confirmable: false,
    };
  }

  return {
    order: serializeOrder(order),
    pack: packPayload,
    mode: 'mock',
    confirmable: true,
  };
}

/**
 * Complete a mock checkout and credit osu (idempotent).
 */
export async function confirmMockOsuPayment(userId, orderId) {
  const id = String(orderId || '');
  if (!id) {
    const err = new Error('Missing order id');
    err.status = 400;
    throw err;
  }

  if (paymentsProvider() === 'stripe' && process.env.NODE_ENV === 'production') {
    const err = new Error('Mock confirm is disabled when Stripe is active');
    err.status = 400;
    throw err;
  }

  return prisma.$transaction(async (tx) => {
    const order = await tx.paymentOrder.findUnique({ where: { id } });
    if (!order || order.userId !== Number(userId)) {
      const err = new Error('Order not found');
      err.status = 404;
      throw err;
    }
    if (order.status === 'paid') {
      const user = await tx.user.findUnique({ where: { id: userId } });
      return { order: serializeOrder(order), user, alreadyPaid: true };
    }
    if (order.status !== 'pending') {
      const err = new Error(`Order is ${order.status}`);
      err.status = 400;
      throw err;
    }
    if (order.provider !== 'mock') {
      const err = new Error('This order must be completed by the payment provider');
      err.status = 400;
      throw err;
    }

    return fulfillPaidOrder(tx, order);
  });
}

/** Mark order paid + credit osu (idempotent). Shared by mock confirm & Stripe webhook. */
async function fulfillPaidOrder(tx, order) {
  if (order.status === 'paid') {
    const user = await tx.user.findUnique({ where: { id: order.userId } });
    return { order: serializeOrder(order), user, alreadyPaid: true };
  }

  const now = new Date();
  const updatedOrder = await tx.paymentOrder.update({
    where: { id: order.id },
    data: { status: 'paid', paidAt: now },
  });

  const user = await tx.user.update({
    where: { id: order.userId },
    data: { osuBalance: { increment: order.osuAmount } },
  });

  await tx.transaction.create({
    data: {
      userId: order.userId,
      type: 'osu_purchase',
      amount: String(order.osuAmount),
      meta: JSON.stringify({
        orderId: order.id,
        packId: order.packId,
        priceCents: order.priceCents,
        currency: order.currency,
        provider: order.provider,
        providerRef: order.providerRef || '',
      }),
    },
  });

  return { order: serializeOrder(updatedOrder), user, alreadyPaid: false };
}

/**
 * Fulfill a Stripe Checkout session (webhook). Idempotent.
 */
export async function fulfillStripeCheckoutSession(session) {
  const orderId =
    session?.metadata?.orderId || session?.client_reference_id || '';
  if (!orderId) {
    const err = new Error('Checkout session missing order id');
    err.status = 400;
    throw err;
  }

  const result = await prisma.$transaction(async (tx) => {
    const order = await tx.paymentOrder.findUnique({ where: { id: String(orderId) } });
    if (!order) {
      const err = new Error('Order not found');
      err.status = 404;
      throw err;
    }
    if (order.provider !== 'stripe') {
      const err = new Error('Order is not a Stripe order');
      err.status = 400;
      throw err;
    }
    if (order.status === 'paid') {
      const user = await tx.user.findUnique({ where: { id: order.userId } });
      return { order: serializeOrder(order), user, alreadyPaid: true };
    }

    // Persist session id if not already stored
    let working = order;
    if (session?.id && order.providerRef !== session.id) {
      working = await tx.paymentOrder.update({
        where: { id: order.id },
        data: { providerRef: session.id },
      });
    }

    return fulfillPaidOrder(tx, working);
  });

  if (result.user?.id && !result.alreadyPaid) {
    checkAchievements(result.user.id).catch((err) => console.error('achievements', err));
  }

  return result;
}

/**
 * Verify Stripe webhook signature and handle checkout.session.completed.
 * `rawBody` must be the raw Buffer/string (not parsed JSON).
 */
export async function handleStripeWebhook(rawBody, signatureHeader) {
  const stripe = getStripe();
  const secret = process.env.STRIPE_WEBHOOK_SECRET;
  if (!stripe || !secret) {
    const err = new Error('Stripe webhook is not configured');
    err.status = 500;
    throw err;
  }

  let event;
  try {
    event = stripe.webhooks.constructEvent(rawBody, signatureHeader, secret);
  } catch (err) {
    const e = new Error(`Webhook signature verification failed: ${err.message}`);
    e.status = 400;
    throw e;
  }

  if (event.type === 'checkout.session.completed') {
    const session = event.data.object;
    if (session.payment_status === 'paid' || session.status === 'complete') {
      await fulfillStripeCheckoutSession(session);
    }
  }

  return { received: true, type: event.type };
}

export async function getOrderForUser(userId, orderId) {
  const order = await prisma.paymentOrder.findUnique({ where: { id: String(orderId || '') } });
  if (!order || order.userId !== Number(userId)) return null;
  return serializeOrder(order);
}

export function serializeOrder(order) {
  return {
    id: order.id,
    packId: order.packId,
    osuAmount: order.osuAmount,
    priceCents: order.priceCents,
    currency: order.currency,
    status: order.status,
    provider: order.provider,
    displayPrice: formatFiat(order.priceCents, order.currency),
    createdAt: order.createdAt,
    paidAt: order.paidAt || null,
  };
}
