/// <reference types="jest" />
import { BadRequestException } from "@nestjs/common";
import type { Logger } from "nestjs-pino";
import { CheckoutService } from "./checkout.service";
import { ORDER_TYPE } from "@customer-portal/domain/orders";
import type { InternetCatalogService } from "@bff/modules/catalog/services/internet-catalog.service";
import type { SimCatalogService } from "@bff/modules/catalog/services/sim-catalog.service";
import type { VpnCatalogService } from "@bff/modules/catalog/services/vpn-catalog.service";

const createLogger = (): Logger =>
  ({
    log: jest.fn(),
    warn: jest.fn(),
    error: jest.fn(),
  }) as unknown as Logger;

const internetPlan = {
  id: "prod-1",
  sku: "PLAN-1",
  name: "Plan 1",
  description: "Plan 1",
  monthlyPrice: 1000,
  oneTimePrice: 0,
} as unknown;

const simPlan = {
  id: "sim-1",
  sku: "SIM-PLAN-1",
  name: "SIM Plan",
  description: "SIM Plan",
  monthlyPrice: 4500,
  oneTimePrice: 0,
} as unknown;

const defaultActivationFee = {
  id: "act-1",
  sku: "SIM-ACTIVATION-FEE",
  name: "SIM Activation Fee",
  description: "One-time fee",
  monthlyPrice: 0,
  oneTimePrice: 3000,
  catalogMetadata: {
    autoAdd: true,
    isDefault: true,
  },
} as unknown;

const alternateActivationFee = {
  id: "act-2",
  sku: "SIM-ACTIVATION-PREMIUM",
  name: "SIM Premium Activation",
  description: "Premium activation",
  monthlyPrice: 0,
  oneTimePrice: 5000,
  catalogMetadata: {
    autoAdd: false,
    isDefault: false,
  },
} as unknown;

const createService = ({
  internet,
  sim,
  vpn,
}: {
  internet: Partial<InternetCatalogService>;
  sim: Partial<SimCatalogService>;
  vpn: Partial<VpnCatalogService>;
}) =>
  new CheckoutService(
    createLogger(),
    internet as InternetCatalogService,
    sim as SimCatalogService,
    vpn as VpnCatalogService
  );

describe("CheckoutService - personalized carts", () => {
  it("uses personalized internet plans when userId is provided", async () => {
    const internetCatalogService = {
      getPlansForUser: jest.fn().mockResolvedValue([internetPlan]),
      getPlans: jest.fn(),
      getInstallations: jest.fn().mockResolvedValue([]),
      getAddons: jest.fn().mockResolvedValue([]),
    };

    const service = createService({
      internet: internetCatalogService,
      sim: {
        getPlans: jest.fn(),
        getPlansForUser: jest.fn(),
        getAddons: jest.fn(),
        getActivationFees: jest.fn(),
      },
      vpn: {
        getPlans: jest.fn(),
        getActivationFees: jest.fn(),
      },
    });

    await service.buildCart(ORDER_TYPE.INTERNET, { planSku: "PLAN-1" }, undefined, "user-123");

    expect(internetCatalogService.getPlansForUser).toHaveBeenCalledWith("user-123");
    expect(internetCatalogService.getPlans).not.toHaveBeenCalled();
  });

  it("rejects plans that are not available to the user", async () => {
    const internetCatalogService = {
      getPlansForUser: jest.fn().mockResolvedValue([]),
      getPlans: jest.fn(),
      getInstallations: jest.fn().mockResolvedValue([]),
      getAddons: jest.fn().mockResolvedValue([]),
    };

    const service = createService({
      internet: internetCatalogService,
      sim: {
        getPlans: jest.fn(),
        getPlansForUser: jest.fn(),
        getAddons: jest.fn(),
        getActivationFees: jest.fn(),
      },
      vpn: {
        getPlans: jest.fn(),
        getActivationFees: jest.fn(),
      },
    });

    await expect(
      service.buildCart(ORDER_TYPE.INTERNET, { planSku: "UNKNOWN" }, undefined, "user-123")
    ).rejects.toThrow(BadRequestException);
  });

  it("falls back to shared catalog when userId is not provided", async () => {
    const internetCatalogService = {
      getPlansForUser: jest.fn(),
      getPlans: jest.fn().mockResolvedValue([internetPlan]),
      getInstallations: jest.fn().mockResolvedValue([]),
      getAddons: jest.fn().mockResolvedValue([]),
    };

    const service = createService({
      internet: internetCatalogService,
      sim: {
        getPlans: jest.fn(),
        getPlansForUser: jest.fn(),
        getAddons: jest.fn(),
        getActivationFees: jest.fn(),
      },
      vpn: {
        getPlans: jest.fn(),
        getActivationFees: jest.fn(),
      },
    });

    await service.buildCart(ORDER_TYPE.INTERNET, { planSku: "PLAN-1" });

    expect(internetCatalogService.getPlans).toHaveBeenCalledTimes(1);
    expect(internetCatalogService.getPlansForUser).not.toHaveBeenCalled();
  });
});

describe("CheckoutService - SIM activation fees", () => {
  const internetCatalogService = {
    getPlansForUser: jest.fn(),
    getPlans: jest.fn(),
    getInstallations: jest.fn(),
    getAddons: jest.fn(),
  };

  const vpnCatalogService = {
    getPlans: jest.fn(),
    getActivationFees: jest.fn(),
  };

  it("auto-adds default activation fee when none specified", async () => {
    const simCatalogService = {
      getPlans: jest.fn().mockResolvedValue([simPlan]),
      getPlansForUser: jest.fn(),
      getAddons: jest.fn().mockResolvedValue([]),
      getActivationFees: jest.fn().mockResolvedValue([defaultActivationFee]),
    };

    const service = createService({
      internet: internetCatalogService,
      sim: simCatalogService,
      vpn: vpnCatalogService,
    });

    const cart = await service.buildCart(ORDER_TYPE.SIM, { planSku: "SIM-PLAN-1" });

    expect(cart.items).toEqual(
      expect.arrayContaining([
        expect.objectContaining({ sku: "SIM-PLAN-1" }),
        expect.objectContaining({
          sku: "SIM-ACTIVATION-FEE",
          itemType: "activation",
          autoAdded: true,
        }),
      ])
    );
    expect(cart.totals.oneTimeTotal).toBe(3000);
  });

  it("respects explicit activation fee selection", async () => {
    const simCatalogService = {
      getPlans: jest.fn().mockResolvedValue([simPlan]),
      getPlansForUser: jest.fn(),
      getAddons: jest.fn().mockResolvedValue([]),
      getActivationFees: jest
        .fn()
        .mockResolvedValue([defaultActivationFee, alternateActivationFee]),
    };

    const service = createService({
      internet: internetCatalogService,
      sim: simCatalogService,
      vpn: vpnCatalogService,
    });

    const cart = await service.buildCart(ORDER_TYPE.SIM, {
      planSku: "SIM-PLAN-1",
      activationFeeSku: "SIM-ACTIVATION-PREMIUM",
    });

    expect(cart.items).toEqual(
      expect.arrayContaining([
        expect.objectContaining({
          sku: "SIM-ACTIVATION-PREMIUM",
          autoAdded: false,
        }),
      ])
    );
    expect(cart.totals.oneTimeTotal).toBe(5000);
  });

  it("throws when no activation fee is available", async () => {
    const simCatalogService = {
      getPlans: jest.fn().mockResolvedValue([simPlan]),
      getPlansForUser: jest.fn(),
      getAddons: jest.fn().mockResolvedValue([]),
      getActivationFees: jest.fn().mockResolvedValue([]),
    };

    const service = createService({
      internet: internetCatalogService,
      sim: simCatalogService,
      vpn: vpnCatalogService,
    });

    await expect(
      service.buildCart(ORDER_TYPE.SIM, { planSku: "SIM-PLAN-1" })
    ).rejects.toThrow("SIM activation fee is not available");
  });

  it("skips activation fees without SKUs and falls back to the next valid option", async () => {
    const simCatalogService = {
      getPlans: jest.fn().mockResolvedValue([simPlan]),
      getPlansForUser: jest.fn(),
      getAddons: jest.fn().mockResolvedValue([]),
      getActivationFees: jest.fn().mockResolvedValue([
        { ...defaultActivationFee, sku: "" },
        alternateActivationFee,
      ]),
    };

    const service = createService({
      internet: internetCatalogService,
      sim: simCatalogService,
      vpn: vpnCatalogService,
    });

    const cart = await service.buildCart(ORDER_TYPE.SIM, { planSku: "SIM-PLAN-1" });

    expect(cart.items).toEqual(
      expect.arrayContaining([
        expect.objectContaining({
          sku: "SIM-ACTIVATION-PREMIUM",
          itemType: "activation",
          autoAdded: true,
        }),
      ])
    );
    expect(cart.totals.oneTimeTotal).toBe(5000);
  });
});
