"use node";

import Stripe from "stripe";
import { action, internalAction } from "./_generated/server";
import { internal } from "./_generated/api";
import { v } from "convex/values";
import { getOrCreateStripeCustomer } from "./model/stripe";
import type { Id } from "./_generated/dataModel";
import type {
  CheckoutSessionResult,
  CheckoutSessionStatus,
  CartValidationResult,
} from "./model/checkout";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

export const createCheckoutSession = action({
  args: {
    addressId: v.id("addresses"),
    shipmentObjectId: v.string(),
    shippingRate: v.object({
      provider: v.string(),
      serviceName: v.string(),
      serviceToken: v.string(),
      amount: v.number(),
      currency: v.string(),
      estimatedDays: v.union(v.number(), v.null()),
      durationTerms: v.string(),
      carrierAccount: v.string(),
    }),
    sessionId: v.optional(v.string()),
  },
  handler: async (ctx, args): Promise<CheckoutSessionResult> => {
    const identity = await ctx.auth.getUserIdentity();
    if (!identity) {
      throw new Error("You must be signed in to checkout.");
    }

    const userId: Id<"users"> = await ctx.runQuery(
      internal.checkout.getCurrentUserId,
    );

    const user = await ctx.runQuery(internal.users.getById, { userId });

    const stripeCustomerId = await getOrCreateStripeCustomer({
      stripeCustomerId: user.stripeCustomerId,
      email: user.email,
      name: user.name,
      convexUserId: userId,
    });

    if (!user.stripeCustomerId) {
      await ctx.runMutation(internal.users.setStripeCustomerId, {
        userId,
        stripeCustomerId,
      });
    }

    const address = await ctx.runQuery(internal.checkout.getAddressById, {
      addressId: args.addressId,
    });
    if (address.userId !== userId) {
      throw new Error("Address does not belong to the current user.");
    }

    const cartResult: CartValidationResult | null = await ctx.runQuery(
      internal.checkout.validateCartInternal,
      { userId, sessionId: args.sessionId },
    );

    if (!cartResult) {
      throw new Error("Your cart is empty.");
    }
    if (!cartResult.valid) {
      throw new Error("Your cart has issues that need to be resolved.");
    }

    const lineItems: Stripe.Checkout.SessionCreateParams.LineItem[] =
      cartResult.items.map((item) => ({
        price_data: {
          currency: "gbp",
          product_data: {
            name: `${item.productName} — ${item.variantName}`,
          },
          unit_amount: Math.round(item.unitPrice),
        },
        quantity: item.quantity,
      }));

    const STOREFRONT_URL = process.env.STOREFRONT_URL;
    if (!STOREFRONT_URL) {
      throw new Error("STOREFRONT_URL environment variable is not set.");
    }

    const session: Stripe.Checkout.Session =
      await stripe.checkout.sessions.create({
        mode: "payment",
        ui_mode: "custom",
        customer: stripeCustomerId,
        line_items: lineItems,
        shipping_options: [
          {
            shipping_rate_data: {
              type: "fixed_amount",
              fixed_amount: {
                amount: Math.round(args.shippingRate.amount * 100),
                currency: "gbp",
              },
              display_name: `${args.shippingRate.provider} — ${args.shippingRate.serviceName}`,
              ...(args.shippingRate.estimatedDays != null && {
                delivery_estimate: {
                  minimum: {
                    unit: "business_day" as const,
                    value: args.shippingRate.estimatedDays,
                  },
                  maximum: {
                    unit: "business_day" as const,
                    value: args.shippingRate.estimatedDays,
                  },
                },
              }),
            },
          },
        ],
        return_url: `${STOREFRONT_URL}/checkout/success?session_id={CHECKOUT_SESSION_ID}`,
        metadata: {
          convexUserId: userId,
          addressId: args.addressId,
          shipmentObjectId: args.shipmentObjectId,
          shippingMethod: `${args.shippingRate.provider} — ${args.shippingRate.serviceName}`,
          shippingServiceCode: args.shippingRate.serviceToken,
          carrier: args.shippingRate.provider,
          carrierAccount: args.shippingRate.carrierAccount,
        },
      });

    if (!session.client_secret) {
      throw new Error("Stripe session missing client_secret.");
    }

    return { clientSecret: session.client_secret };
  },
});

export const getCheckoutSessionStatus = action({
  args: {
    sessionId: v.string(),
  },
  handler: async (_ctx, args): Promise<CheckoutSessionStatus> => {
    const session: Stripe.Checkout.Session =
      await stripe.checkout.sessions.retrieve(args.sessionId);

    return {
      status: session.status as "complete" | "expired" | "open",
      paymentStatus: session.payment_status,
      customerEmail: session.customer_details?.email ?? null,
    };
  },
});

export const handleWebhook = internalAction({
  args: {
    payload: v.string(),
    signature: v.string(),
  },
  handler: async (ctx, args): Promise<{ success: boolean }> => {
    const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
    if (!webhookSecret) {
      throw new Error("STRIPE_WEBHOOK_SECRET environment variable is not set.");
    }

    let event: Stripe.Event;
    try {
      event = stripe.webhooks.constructEvent(
        args.payload,
        args.signature,
        webhookSecret,
      );
    } catch (err) {
      console.error("Webhook signature verification failed:", err);
      return { success: false };
    }

    switch (event.type) {
      case "checkout.session.completed":
      case "checkout.session.async_payment_succeeded": {
        const session = event.data.object as Stripe.Checkout.Session;
        const metadata = session.metadata;

        if (
          !metadata?.convexUserId ||
          !metadata?.addressId ||
          !metadata?.shipmentObjectId ||
          !metadata?.shippingMethod ||
          !metadata?.shippingServiceCode ||
          !metadata?.carrier
        ) {
          console.error(
            "Missing required metadata on checkout session:",
            session.id,
          );
          return { success: false };
        }

        await ctx.runMutation(internal.orders.fulfillFromCheckout, {
          stripeCheckoutSessionId: session.id,
          stripePaymentIntentId:
            typeof session.payment_intent === "string"
              ? session.payment_intent
              : session.payment_intent?.id ?? null,
          convexUserId: metadata.convexUserId,
          addressId: metadata.addressId,
          shipmentObjectId: metadata.shipmentObjectId,
          shippingMethod: metadata.shippingMethod,
          shippingServiceCode: metadata.shippingServiceCode,
          carrier: metadata.carrier,
          amountTotal: session.amount_total,
          amountShipping:
            session.shipping_cost?.amount_total ??
            session.total_details?.amount_shipping ??
            0,
          currency: session.currency,
        });
        break;
      }
      case "checkout.session.expired":
        console.warn(
          "Checkout session expired:",
          (event.data.object as Stripe.Checkout.Session).id,
        );
        break;
      case "refund.updated": {
        const refund = event.data.object as Stripe.Refund;
        if (refund.status === "succeeded" && refund.payment_intent) {
          const paymentIntentId =
            typeof refund.payment_intent === "string"
              ? refund.payment_intent
              : refund.payment_intent.id;
          const order = await ctx.runQuery(
            internal.orders.getOrderByPaymentIntent,
            { stripePaymentIntentId: paymentIntentId },
          );
          if (order && order.paymentStatus !== "refunded") {
            await ctx.runMutation(internal.orders.applyRefund, {
              id: order._id,
              adminUserId: order.userId,
            });
          }
        }
        break;
      }
      default:
        console.log("Unhandled Stripe event type:", event.type);
    }

    return { success: true };
  },
});
