import { SubscriberArgs, SubscriberConfig } from "@medusajs/framework";
import { ContainerRegistrationKeys } from "@medusajs/framework/utils";
import { createShipment as createMondialRelayShipment } from "@frontboi/mondial-relay/node";
import { createShipmentWorkflow } from "@medusajs/core-flows";

type EventData = {
  id?: string;
  order_id?: string;
  fulfillment_id?: string;
};

type FulfillmentRecord = {
  id: string;
  shipping_option_id?: string | null;
  shipped_at?: string | Date | null;
  canceled_at?: string | Date | null;
  data?: Record<string, unknown> | null;
  labels?: Array<{ id?: string; tracking_number?: string | null }> | null;
  delivery_address?: {
    first_name?: string | null;
    last_name?: string | null;
    address_1?: string | null;
    address_2?: string | null;
    city?: string | null;
    postal_code?: string | null;
    country_code?: string | null;
    phone?: string | null;
  } | null;
  items?: Array<{
    quantity?: number | null;
    title?: string | null;
  }>;
};

type OrderRecord = {
  id: string;
  display_id?: number | string | null;
  email?: string | null;
  customer?: {
    first_name?: string | null;
    last_name?: string | null;
    phone?: string | null;
  } | null;
  shipping_address?: {
    first_name?: string | null;
    last_name?: string | null;
    phone?: string | null;
  } | null;
  billing_address?: {
    first_name?: string | null;
    last_name?: string | null;
    phone?: string | null;
  } | null;
};

const DEFAULT_ITEM_WEIGHT_GRAMS = 500;
const MAX_PARCEL_WEIGHT_GRAMS = 30000;
const MIN_XL_RELAY_WEIGHT_GRAMS = 5000;

function asString(value: unknown) {
  return typeof value === "string" ? value.trim() : "";
}

function asNumber(value: unknown) {
  const parsed = Number(value);
  return Number.isFinite(parsed) ? parsed : 0;
}

function sanitizePhone(value: string) {
  return value.replace(/[^\d+]/g, "").slice(0, 20);
}

function sanitizePostcode(value: string) {
  return value.replace(/\s+/g, "").toUpperCase();
}

function compact(value: string, maxLength: number) {
  return value.slice(0, maxLength);
}

function normalizePersonName(value: string) {
  const cleaned = asString(value);
  const lowered = cleaned.toLowerCase();
  if (!cleaned || lowered === "point" || lowered === "relais" || lowered === "relay") {
    return "";
  }
  return cleaned;
}

function parseMondialRelayOptionIdsFromEnv() {
  return asString(process.env.MONDIAL_RELAY_SHIPPING_OPTION_IDS)
    .split(",")
    .map((id) => id.trim())
    .filter(Boolean);
}

function looksLikeMondialRelayFulfillment(fulfillment: FulfillmentRecord) {
  const data = (fulfillment.data || {}) as Record<string, unknown>;
  const metadata = (data.metadata || {}) as Record<string, unknown>;
  const metadataId = asString(metadata.id).toLowerCase();
  const optionName = asString(data.shipping_option_name).toLowerCase();
  const hasRelayId = Boolean(
    asString(data.parcel_shop_id) || asString(data.relay_point_id) || asString(data.parcelShopId),
  );

  if (hasRelayId) {
    return true;
  }

  if (metadataId.includes("mondial") || metadataId.includes("relay")) {
    return true;
  }

  return optionName.includes("mondial") || optionName.includes("relay");
}

function isMondialRelayFulfillment(fulfillment: FulfillmentRecord) {
  const configuredIds = parseMondialRelayOptionIdsFromEnv();
  const shippingOptionId = asString(fulfillment.shipping_option_id);
  if (configuredIds.length && shippingOptionId) {
    return configuredIds.includes(shippingOptionId);
  }

  return looksLikeMondialRelayFulfillment(fulfillment);
}

function getRelayLocationFromFulfillment(fulfillment: FulfillmentRecord) {
  const data = (fulfillment.data || {}) as Record<string, unknown>;
  const rawParcelShopId =
    asString(data.parcel_shop_id) || asString(data.relay_point_id) || asString(data.parcelShopId);
  const rawCountry = asString(data.parcel_shop_country_code || fulfillment.delivery_address?.country_code || "FR")
    .toUpperCase();

  if (!rawParcelShopId) {
    return "";
  }

  if (rawParcelShopId.includes("-")) {
    return rawParcelShopId;
  }

  return `${rawCountry}-${rawParcelShopId}`;
}

function getOrderWeightGrams(fulfillment: FulfillmentRecord) {
  const data = (fulfillment.data || {}) as Record<string, unknown>;
  const weightFromData = asNumber(data.total_weight);
  if (weightFromData > 0) {
    return Math.max(1, Math.min(MAX_PARCEL_WEIGHT_GRAMS, Math.round(weightFromData)));
  }

  const itemWeight = (fulfillment.items || []).reduce((sum, item) => {
    const quantity = Math.max(1, Number(item.quantity || 1));
    return sum + quantity * DEFAULT_ITEM_WEIGHT_GRAMS;
  }, 0);

  return Math.max(1, Math.min(MAX_PARCEL_WEIGHT_GRAMS, Math.round(itemWeight)));
}

function buildSenderFromEnv() {
  const title = asString(process.env.BUSINESS_TITLE || "Mr");
  const firstname = asString(process.env.BUSINESS_FIRSTNAME);
  const lastname = asString(process.env.BUSINESS_LASTNAME);
  const street = asString(process.env.BUSINESS_STREET);
  const postcode = sanitizePostcode(asString(process.env.BUSINESS_POSTCODE));
  const city = asString(process.env.BUSINESS_CITY);
  const country = asString(process.env.BUSINESS_COUNTRY || "FR").toUpperCase();
  const phone = sanitizePhone(asString(process.env.BUSINESS_PHONE));
  const email = asString(process.env.BUSINESS_EMAIL);

  if (!firstname || !lastname || !street || !postcode || !city || !email) {
    throw new Error("Sender business address is incomplete in env (BUSINESS_*).");
  }

  return {
    Title: (title === "Mme" ? "Mme" : "Mr") as "Mr" | "Mme",
    Firstname: compact(firstname, 15),
    Lastname: compact(lastname, 15),
    Streetname: compact(street, 40),
    HouseNo: "",
    PostCode: compact(postcode, 10),
    City: compact(city, 30),
    CountryCode: compact(country, 2),
    MobileNo: compact(phone, 20),
    PhoneNo: "",
    Email: compact(email, 70),
  };
}

function buildRecipient(fulfillment: FulfillmentRecord, order: OrderRecord | null) {
  const address = fulfillment.delivery_address || {};
  const firstName =
    normalizePersonName(asString(order?.customer?.first_name)) ||
    normalizePersonName(asString(order?.shipping_address?.first_name)) ||
    normalizePersonName(asString(order?.billing_address?.first_name)) ||
    normalizePersonName(asString(address.first_name)) ||
    "Client";
  const lastName =
    normalizePersonName(asString(order?.customer?.last_name)) ||
    normalizePersonName(asString(order?.shipping_address?.last_name)) ||
    normalizePersonName(asString(order?.billing_address?.last_name)) ||
    normalizePersonName(asString(address.last_name)) ||
    "Stylunique";
  const street = asString(address.address_1);
  const postcode = sanitizePostcode(asString(address.postal_code));
  const city = asString(address.city);
  const country = asString(address.country_code || "FR").toUpperCase();
  const phone = sanitizePhone(
    asString(order?.shipping_address?.phone || order?.billing_address?.phone || address.phone),
  );
  const email = asString(order?.email || process.env.BUSINESS_EMAIL || "");

  if (!street || !postcode || !city || !country || !email) {
    throw new Error("Recipient address on fulfillment is incomplete for Mondial Relay.");
  }

  return {
    Title: "Mr" as const,
    Firstname: compact(firstName, 15),
    Lastname: compact(lastName, 15),
    Streetname: compact(street, 40),
    HouseNo: "",
    PostCode: compact(postcode, 10),
    City: compact(city, 30),
    CountryCode: compact(country, 2),
    MobileNo: compact(phone, 20),
    PhoneNo: "",
    Email: compact(email, 70),
  };
}

function getOrderDisplayNumber(order: OrderRecord | null, fulfillmentId: string) {
  if (!order) {
    return fulfillmentId.replace(/^ful_/, "").slice(0, 15);
  }

  const displayId = asString(order.display_id);
  return displayId || order.id.replace(/^order_/, "").slice(0, 15);
}

function hasAlreadyShipped(fulfillment: FulfillmentRecord) {
  if (fulfillment.canceled_at) {
    return true;
  }

  if (fulfillment.shipped_at) {
    return true;
  }

  return (fulfillment.labels || []).some((label) => Boolean(asString(label.tracking_number)));
}

async function loadOrder(query: any, orderId: string) {
  if (!orderId) {
    return null;
  }

  try {
    const response = await query.graph({
      entity: "order",
      fields: [
        "id",
        "display_id",
        "email",
        "customer.first_name",
        "customer.last_name",
        "customer.phone",
        "shipping_address.first_name",
        "shipping_address.last_name",
        "shipping_address.phone",
        "billing_address.first_name",
        "billing_address.last_name",
        "billing_address.phone",
      ],
      filters: { id: orderId },
    });
    return (response.data?.[0] || null) as OrderRecord | null;
  } catch {
    return null;
  }
}

async function loadFulfillment(query: any, fulfillmentId: string) {
  const response = await query.graph({
    entity: "fulfillment",
    fields: [
      "id",
      "shipping_option_id",
      "shipped_at",
      "canceled_at",
      "data",
      "labels.id",
      "labels.tracking_number",
      "delivery_address.first_name",
      "delivery_address.last_name",
      "delivery_address.address_1",
      "delivery_address.address_2",
      "delivery_address.city",
      "delivery_address.postal_code",
      "delivery_address.country_code",
      "delivery_address.phone",
      "items.quantity",
      "items.title",
    ],
    filters: { id: fulfillmentId },
  });

  return (response.data?.[0] || null) as FulfillmentRecord | null;
}

async function createShipmentLabel(fulfillment: FulfillmentRecord, order: OrderRecord | null, logger: any) {
  const login = asString(process.env.MONDIAL_RELAY_LOGIN);
  const password = asString(process.env.MONDIAL_RELAY_PASSWORD);
  const customerId =
    asString(process.env.MONDIAL_RELAY_CUSTOMER_ID) || asString(process.env.MONDIAL_RELAY_ENSEIGNE);
  const culture = asString(process.env.MONDIAL_RELAY_CULTURE || "fr-FR");

  if (!login || !password || !customerId) {
    throw new Error("Missing MONDIAL_RELAY_LOGIN / MONDIAL_RELAY_PASSWORD / MONDIAL_RELAY_CUSTOMER_ID.");
  }

  const relayLocation = getRelayLocationFromFulfillment(fulfillment);
  if (!relayLocation) {
    logger.warn(`[mondial-relay] fulfillment ${fulfillment.id}: parcel shop id missing, fallback to home delivery.`);
  }

  const weightGrams = getOrderWeightGrams(fulfillment);

  const sendShipment = async (weight: number) =>
    createMondialRelayShipment({
      context: {
        Login: login,
        Password: password,
        CustomerId: customerId,
        Culture: culture,
        VersionAPI: "1.0",
      },
      outputOptions: {
        OutputFormat: "A4",
        OutputType: "PdfUrl",
      },
      shipment: {
        OrderNo: compact(getOrderDisplayNumber(order, fulfillment.id), 15),
        CustomerNo: "",
        ParcelCount: "1",
        CollectionMode: {
          Mode: "REL",
        },
        DeliveryMode: relayLocation
          ? {
              Mode: "24R",
              Location: relayLocation,
            }
          : {
              Mode: "HOM",
            },
        DeliveryInstruction: "",
        Sender: buildSenderFromEnv(),
        Recipient: buildRecipient(fulfillment, order),
        Parcels: {
          Parcel: {
            Content: compact(`FUL-${fulfillment.id}`, 40),
            Weight: {
              Value: weight,
              Unit: "gr",
            },
          },
        },
      },
    });

  let response;
  try {
    response = await sendShipment(weightGrams);
  } catch (error: any) {
    const message = asString(error?.message).toLowerCase();
    const isXlWeightError =
      message.includes("point relais xl") && (message.includes("poids") || message.includes("5,00"));

    if (!isXlWeightError) {
      throw error;
    }

    const fallbackWeight = Math.max(weightGrams, MIN_XL_RELAY_WEIGHT_GRAMS);
    logger.warn(
      `[mondial-relay] fulfillment ${fulfillment.id}: XL relay weight minimum detected, retry with ${fallbackWeight}g.`,
    );
    response = await sendShipment(fallbackWeight);
  }

  return {
    trackingNumber: response.sendingNumber,
    labelUrl: response.etiquetteLink,
  };
}

export default async function orderMondialRelayShipmentSubscriber({
  event,
  container,
}: SubscriberArgs<EventData>) {
  const logger = container.resolve(ContainerRegistrationKeys.LOGGER);
  const query = container.resolve(ContainerRegistrationKeys.QUERY);

  const fulfillmentId = asString(event.data?.fulfillment_id);
  const orderId = asString(event.data?.id || event.data?.order_id);

  if (!fulfillmentId) {
    return;
  }

  try {
    const fulfillment = await loadFulfillment(query, fulfillmentId);
    if (!fulfillment) {
      logger.warn(`[mondial-relay] fulfillment ${fulfillmentId}: not found.`);
      return;
    }

    if (!isMondialRelayFulfillment(fulfillment)) {
      logger.info(`[mondial-relay] fulfillment ${fulfillment.id}: not Mondial Relay, skip.`);
      return;
    }

    if (hasAlreadyShipped(fulfillment)) {
      logger.info(`[mondial-relay] fulfillment ${fulfillment.id}: already has shipment, skip.`);
      return;
    }

    const order = await loadOrder(query, orderId);
    const label = await createShipmentLabel(fulfillment, order, logger);

    await createShipmentWorkflow(container).run({
      input: {
        id: fulfillment.id,
        labels: [
          {
            tracking_number: label.trackingNumber,
            label_url: label.labelUrl,
            tracking_url: `https://www.mondialrelay.fr/suivi-de-colis/?NumeroExpedition=${encodeURIComponent(
              label.trackingNumber,
            )}`,
          },
        ],
      },
    });

    logger.info(`[mondial-relay] fulfillment ${fulfillment.id}: shipment created (${label.trackingNumber}).`);
  } catch (error: any) {
    const message = error?.message ? String(error.message) : "unknown error";
    logger.error(`[mondial-relay] fulfillment ${fulfillmentId}: automatic shipment failed - ${message}`);
  }
}

export const config: SubscriberConfig = {
  event: ["order.fulfillment_created"],
  context: {
    subscriberId: "order-mondialrelay-shipment",
  },
};
