import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
import DiscountService from "../discount"

describe("DiscountService", () => {
  describe("create", () => {
    const discountRepository = MockRepository({})

    const discountRuleRepository = MockRepository({})

    const regionService = {
      retrieve: () => {
        return {
          id: IdMap.getId("france"),
        }
      },
      withTransaction: function() {
        return this
      },
    }

    const discountService = new DiscountService({
      manager: MockManager,
      discountRepository,
      discountRuleRepository,
      regionService,
    })

    beforeEach(() => {
      jest.clearAllMocks()
    })

    it("fails to create a fixed discount with multiple regions", async () => {
      expect.assertions(3)
      try {
        await discountService.create({
          code: "test",
          rule: {
            type: "fixed",
            allocation: "total",
            value: 20,
          },
          regions: [IdMap.getId("france"), IdMap.getId("Italy")],
        })
      } catch (err) {
        expect(err.type).toEqual("invalid_data")
        expect(err.message).toEqual("Fixed discounts can have one region")
        expect(discountRepository.create).toHaveBeenCalledTimes(0)
      }
    })

    it("successfully creates discount", async () => {
      await discountService.create({
        code: "test",
        rule: {
          type: "percentage",
          allocation: "total",
          value: 20,
        },
        regions: [IdMap.getId("france")],
      })

      expect(discountRuleRepository.create).toHaveBeenCalledTimes(1)
      expect(discountRuleRepository.create).toHaveBeenCalledWith({
        type: "percentage",
        allocation: "total",
        value: 20,
      })

      expect(discountRuleRepository.save).toHaveBeenCalledTimes(1)

      expect(discountRepository.create).toHaveBeenCalledTimes(1)
      expect(discountRepository.create).toHaveBeenCalledWith({
        code: "TEST",
        rule: expect.anything(),
        regions: [{ id: IdMap.getId("france") }],
      })

      expect(discountRepository.save).toHaveBeenCalledTimes(1)
    })

    it("successfully creates discount with start and end dates", async () => {
      await discountService.create({
        code: "test",
        rule: {
          type: "percentage",
          allocation: "total",
          value: 20,
        },
        starts_at: new Date("03/14/2021"),
        ends_at: new Date("03/15/2021"),
        regions: [IdMap.getId("france")],
      })

      expect(discountRuleRepository.create).toHaveBeenCalledTimes(1)
      expect(discountRuleRepository.create).toHaveBeenCalledWith({
        type: "percentage",
        allocation: "total",
        value: 20,
      })

      expect(discountRuleRepository.save).toHaveBeenCalledTimes(1)

      expect(discountRepository.create).toHaveBeenCalledTimes(1)
      expect(discountRepository.create).toHaveBeenCalledWith({
        code: "TEST",
        rule: expect.anything(),
        regions: [{ id: IdMap.getId("france") }],
        starts_at: new Date("03/14/2021"),
        ends_at: new Date("03/15/2021"),
      })

      expect(discountRepository.save).toHaveBeenCalledTimes(1)
    })

    it("successfully creates discount with start date and a valid duration", async () => {
      await discountService.create({
        code: "test",
        rule: {
          type: "percentage",
          allocation: "total",
          value: 20,
        },
        starts_at: new Date("03/14/2021"),
        valid_duration: "P0Y0M1D",
        regions: [IdMap.getId("france")],
      })

      expect(discountRuleRepository.create).toHaveBeenCalledTimes(1)
      expect(discountRuleRepository.create).toHaveBeenCalledWith({
        type: "percentage",
        allocation: "total",
        value: 20,
      })

      expect(discountRuleRepository.save).toHaveBeenCalledTimes(1)

      expect(discountRepository.create).toHaveBeenCalledTimes(1)
      expect(discountRepository.create).toHaveBeenCalledWith({
        code: "TEST",
        rule: expect.anything(),
        regions: [{ id: IdMap.getId("france") }],
        starts_at: new Date("03/14/2021"),
        valid_duration: "P0Y0M1D",
      })

      expect(discountRepository.save).toHaveBeenCalledTimes(1)
    })
  })

  describe("retrieve", () => {
    const discountRepository = MockRepository({
      findOne: (query) => {
        if (query.where.id) {
          return Promise.resolve({ id: IdMap.getId("total10") })
        }
        return Promise.resolve(undefined)
      },
    })

    const discountService = new DiscountService({
      manager: MockManager,
      discountRepository,
    })

    beforeEach(() => {
      jest.clearAllMocks()
    })

    it("successfully retrieves discount", async () => {
      await discountService.retrieve(IdMap.getId("total10"))
      expect(discountRepository.findOne).toHaveBeenCalledTimes(1)
      expect(discountRepository.findOne).toHaveBeenCalledWith({
        where: {
          id: IdMap.getId("total10"),
        },
      })
    })

    it("throws on invalid discount id", async () => {
      try {
        await discountService.retrieve(IdMap.getId("invalid"))
      } catch (error) {
        expect(error.message).toBe(
          `Discount with ${IdMap.getId("invalid")} was not found`
        )
      }
    })
  })

  describe("retrieveByCode", () => {
    const discountRepository = MockRepository({
      findOne: (query) => {
        if (query.where.code === "10%OFF") {
          return Promise.resolve({ id: IdMap.getId("total10"), code: "10%OFF" })
        }
        if (query.where.code === "DYNAMIC") {
          return Promise.resolve({ id: IdMap.getId("total10"), code: "10%OFF" })
        }
        return Promise.resolve(undefined)
      },
    })

    const discountService = new DiscountService({
      manager: MockManager,
      discountRepository,
    })

    beforeEach(() => {
      jest.clearAllMocks()
    })

    it("successfully finds discount by code", async () => {
      await discountService.retrieveByCode("10%OFF")
      expect(discountRepository.findOne).toHaveBeenCalledTimes(1)
      expect(discountRepository.findOne).toHaveBeenCalledWith({
        where: {
          code: "10%OFF",
          is_dynamic: false,
        },
      })
    })
  })

  describe("update", () => {
    const discountRepository = MockRepository({
      findOne: () =>
        Promise.resolve({
          id: IdMap.getId("total10"),
          code: "10%OFF",
          rule: { type: "fixed" },
        }),
    })

    const discountRuleRepository = MockRepository({
      create: (values) => values,
    })

    const regionService = {
      retrieve: () => {
        return {
          id: IdMap.getId("france"),
        }
      },
    }

    const discountService = new DiscountService({
      manager: MockManager,
      discountRepository,
      discountRuleRepository,
      regionService,
    })

    beforeEach(() => {
      jest.clearAllMocks()
    })

    it("fails to update a fixed discount with multiple regions", async () => {
      expect.assertions(3)
      try {
        await discountService.update(IdMap.getId("total10"), {
          code: "test",
          regions: [IdMap.getId("france"), IdMap.getId("Italy")],
        })
      } catch (err) {
        expect(err.type).toEqual("invalid_data")
        expect(err.message).toEqual("Fixed discounts can have one region")
        expect(discountRepository.create).toHaveBeenCalledTimes(0)
      }
    })

    it("successfully updates discount", async () => {
      await discountService.update(IdMap.getId("total10"), {
        code: "test",
        regions: [IdMap.getId("france")],
      })
      expect(discountRepository.save).toHaveBeenCalledTimes(1)
      expect(discountRepository.save).toHaveBeenCalledWith({
        id: IdMap.getId("total10"),
        code: "TEST",
        rule: { type: "fixed" },
        regions: [{ id: IdMap.getId("france") }],
      })
    })

    it("successfully updates discount rule", async () => {
      await discountService.update(IdMap.getId("total10"), {
        rule: { type: "fixed", value: 10, allocation: "total" },
      })
      expect(discountRepository.save).toHaveBeenCalledTimes(1)
      expect(discountRepository.save).toHaveBeenCalledWith({
        id: IdMap.getId("total10"),
        code: "10%OFF",
        rule: { type: "fixed", value: 10, allocation: "total" },
      })
    })

    it("successfully updates metadata", async () => {
      await discountService.update(IdMap.getId("total10"), {
        metadata: { testKey: "testValue" },
      })
      expect(discountRepository.save).toHaveBeenCalledTimes(1)
      expect(discountRepository.save).toHaveBeenCalledWith({
        id: IdMap.getId("total10"),
        rule: { type: "fixed" },
        code: "10%OFF",
        metadata: { testKey: "testValue" },
      })
    })
  })

  describe("addRegion", () => {
    const discountRepository = MockRepository({
      findOne: (q) => {
        if (q.where.id === "fixed") {
          return Promise.resolve({
            id: IdMap.getId("total10"),
            regions: [{ id: IdMap.getId("test-region") }],
            rule: {
              type: "fixed",
            },
          })
        }
        return Promise.resolve({
          id: IdMap.getId("total10"),
          regions: [{ id: IdMap.getId("test-region") }],
          rule: {
            type: "percentage",
          },
        })
      },
    })

    const discountRuleRepository = MockRepository({})

    const regionService = {
      retrieve: () => {
        return {
          id: IdMap.getId("test-region-2"),
        }
      },
    }

    const discountService = new DiscountService({
      manager: MockManager,
      discountRepository,
      discountRuleRepository,
      regionService,
    })

    beforeEach(() => {
      jest.clearAllMocks()
    })

    it("fails to add a region to a fixed discount with an existing region", async () => {
      expect.assertions(3)
      try {
        await discountService.addRegion("fixed", IdMap.getId("test-region-2"))
      } catch (err) {
        expect(err.type).toEqual("invalid_data")
        expect(err.message).toEqual("Fixed discounts can have one region")
        expect(discountRepository.save).toHaveBeenCalledTimes(0)
      }
    })

    it("successfully adds a region", async () => {
      await discountService.addRegion(
        IdMap.getId("total10"),
        IdMap.getId("test-region-2")
      )

      expect(discountRepository.save).toHaveBeenCalledTimes(1)
      expect(discountRepository.save).toHaveBeenCalledWith({
        id: IdMap.getId("total10"),
        regions: [
          { id: IdMap.getId("test-region") },
          { id: IdMap.getId("test-region-2") },
        ],
        rule: {
          type: "percentage",
        },
      })
    })

    it("successfully resolves if region already exists", async () => {
      await discountService.addRegion(
        IdMap.getId("total10"),
        IdMap.getId("test-region")
      )

      expect(discountRepository.save).toHaveBeenCalledTimes(0)
    })
  })

  describe("createDynamicDiscount", () => {
    const discountRepository = MockRepository({
      create: (d) => d,
      findOne: () =>
        Promise.resolve({
          id: "parent",
          is_dynamic: true,
          rule_id: "parent_rule",
          valid_duration: "P1Y",
        }),
    })

    const discountRuleRepository = MockRepository({})

    const regionService = {
      retrieve: () => {
        return {
          id: IdMap.getId("test-region"),
        }
      },
    }

    const discountService = new DiscountService({
      manager: MockManager,
      discountRepository,
      discountRuleRepository,
      regionService,
    })

    beforeEach(() => {
      jest.clearAllMocks()
    })

    it("successfully removes a region", async () => {
      await discountService.createDynamicCode("former", {
        code: "hi",
      })

      expect(discountRepository.save).toHaveBeenCalledTimes(1)
      expect(discountRepository.save).toHaveBeenCalledWith({
        is_dynamic: true,
        is_disabled: false,
        rule_id: "parent_rule",
        parent_discount_id: "parent",
        code: "HI",
        usage_limit: undefined,
        ends_at: expect.any(Date),
      })
    })
  })

  describe("removeRegion", () => {
    const discountRepository = MockRepository({
      findOne: () =>
        Promise.resolve({
          id: IdMap.getId("total10"),
          regions: [{ id: IdMap.getId("test-region") }],
        }),
    })

    const discountRuleRepository = MockRepository({})

    const regionService = {
      retrieve: () => {
        return {
          id: IdMap.getId("test-region"),
        }
      },
    }

    const discountService = new DiscountService({
      manager: MockManager,
      discountRepository,
      discountRuleRepository,
      regionService,
    })

    beforeEach(() => {
      jest.clearAllMocks()
    })

    it("successfully removes a region", async () => {
      await discountService.removeRegion(
        IdMap.getId("total10"),
        IdMap.getId("test-region")
      )

      expect(discountRepository.save).toHaveBeenCalledTimes(1)
      expect(discountRepository.save).toHaveBeenCalledWith({
        id: IdMap.getId("total10"),
        regions: [],
      })
    })

    it("successfully resolve if region does not exist", async () => {
      await discountService.removeRegion(
        IdMap.getId("total10"),
        IdMap.getId("test-region-2")
      )

      expect(discountRepository.save).toHaveBeenCalledTimes(0)
    })
  })

  describe("listAndCount", () => {
    const discountRepository = MockRepository({
      findAndCount: () =>
        Promise.resolve([
          {
            id: IdMap.getId("total10"),
            code: "OLITEST",
          },
        ]),
    })

    const discountService = new DiscountService({
      manager: MockManager,
      discountRepository,
    })

    beforeEach(() => {
      jest.clearAllMocks()
    })

    it("calls repository function with query and default config", async () => {
      await discountService.listAndCount({ q: "OLI" })

      expect(discountRepository.findAndCount).toHaveBeenCalledTimes(1)
      expect(discountRepository.findAndCount).toHaveBeenCalledWith({
        where: expect.anything(),
        skip: 0,
        take: 20,
        order: { created_at: "DESC" },
      })
    })

    it("calls repository function specified query", async () => {
      await discountService.listAndCount(
        {},
        { skip: 50, take: 50, order: { created_at: "ASC" } }
      )

      expect(discountRepository.findAndCount).toHaveBeenCalledTimes(1)
      expect(discountRepository.findAndCount).toHaveBeenCalledWith({
        where: {},
        skip: 50,
        take: 50,
        order: { created_at: "ASC" },
      })
    })
  })

  describe("calculateDiscountForLineItem", () => {
    const discountRepository = MockRepository({
      findOne: ({ where }) => {
        if (where.id === "disc_percentage") {
          return Promise.resolve({
            code: "MEDUSA",
            rule: {
              type: "percentage",
              allocation: "total",
              value: 15,
            },
          })
        }
        if (where.id === "disc_fixed_total") {
          return Promise.resolve({
            code: "MEDUSA",
            rule: {
              type: "fixed",
              allocation: "total",
              value: 400,
            },
          })
        }
        return Promise.resolve({
          id: "disc_fixed",
          code: "MEDUSA",
          rule: {
            type: "fixed",
            allocation: "item",
            value: 200,
          },
        })
      },
    })

    const totalsService = {
      getSubtotal: () => {
        return 1100
      },
    }

    const discountService = new DiscountService({
      manager: MockManager,
      discountRepository,
      totalsService,
    })

    beforeEach(() => {
      jest.clearAllMocks()
    })

    it("correctly calculates fixed + item discount", async () => {
      const adjustment = await discountService.calculateDiscountForLineItem(
        "disc_fixed",
        {
          unit_price: 300,
          quantity: 2,
          allow_discounts: true,
        }
      )

      expect(adjustment).toBe(400)
    })

    it("correctly calculates fixed + total discount", async () => {
      const adjustment1 = await discountService.calculateDiscountForLineItem(
        "disc_fixed_total",
        {
          unit_price: 400,
          quantity: 2,
          allow_discounts: true,
        }
      )

      const adjustment2 = await discountService.calculateDiscountForLineItem(
        "disc_fixed_total",
        {
          unit_price: 300,
          quantity: 1,
          allow_discounts: true,
        }
      )

      expect(adjustment1).toBe(291)
      expect(adjustment2).toBe(109)
    })

    it("returns line item amount if discount exceeds lime item price", async () => {
      const adjustment = await discountService.calculateDiscountForLineItem(
        "disc_fixed",
        {
          unit_price: 100,
          quantity: 1,
          allow_discounts: true,
        }
      )

      expect(adjustment).toBe(100)
    })

    it("correctly calculates percentage discount", async () => {
      const adjustment = await discountService.calculateDiscountForLineItem(
        "disc_percentage",
        {
          unit_price: 400,
          quantity: 2,
          allow_discounts: true,
        }
      )

      expect(adjustment).toBe(120)
    })

    it("returns full amount if exceeds total line item amount", async () => {
      const adjustment = await discountService.calculateDiscountForLineItem(
        "disc_fixed",
        {
          unit_price: 50,
          quantity: 2,
          allow_discounts: true,
        }
      )

      expect(adjustment).toBe(100)
    })

    it("returns early if discounts are not allowed", async () => {
      const adjustment = await discountService.calculateDiscountForLineItem(
        "disc_percentage",
        {
          unit_price: 400,
          quantity: 2,
          allow_discounts: false,
        }
      )

      expect(adjustment).toBe(0)
    })
  })

  describe("validateDiscountForCartOrThrow", () => {
    const getCart = (id) => {
      if (id === "with-d") {
        return {
          id: "cart",
          discounts: [
            {
              code: "1234",
              rule: {
                type: "fixed",
              },
            },
            {
              code: "FS1234",
              rule: {
                type: "free_shipping",
              },
            },
          ],
          region_id: "good",
          items: [
            {
              id: "li1",
              quantity: 2,
              unit_price: 1000,
            },
            {
              id: "li2",
              quantity: 1,
              unit_price: 500,
            },
          ],
        }
      }
      if (id === "with-d-and-customer") {
        return {
          id: "with-d-and-customer",
          discounts: [
            {
              code: "ApplicableForCustomer",
              rule: {
                type: "fixed",
              },
            },
          ],
          region_id: "good",
          customer_id: "test-customer",
        }
      }
      return {
        id: "cart",
        discounts: [
          {
            code: "CODE",
            rule: {
              type: "percentage",
            },
          },
        ],
        region_id: "good",
        items: [
          {
            id: "li1",
            quantity: 2,
            unit_price: 1000,
          },
          {
            id: "li2",
            quantity: 1,
            unit_price: 500,
          },
        ],
      }
    }

    let discountService

    beforeEach(async () => {
      discountService = new DiscountService({})
      const hasReachedLimitMock = jest.fn().mockImplementation(() => false)
      const isDisabledMock = jest.fn().mockImplementation(() => false)
      const isValidForRegionMock = jest
        .fn()
        .mockImplementation(() => Promise.resolve(true))
      const canApplyForCustomerMock = jest.fn().mockImplementation(() => {
        return Promise.resolve(true)
      })
      discountService.hasReachedLimit = hasReachedLimitMock
      discountService.isDisabled = isDisabledMock
      discountService.canApplyForCustomer = canApplyForCustomerMock
      discountService.isValidForRegion = isValidForRegionMock
    })

    it("calls all validation methods when cart includes a customer_id and doesn't throw", async () => {
      const discount = {
        id: "10off",
        code: "10%OFF",
        regions: [{ id: "good" }],
        rule: {
          id: "10off-rule",
          type: "percentage",
        },
      }
      const cart = getCart("with-d-and-customer")
      const result = await discountService.validateDiscountForCartOrThrow(
        cart,
        discount
      )
      expect(result).toBeUndefined()

      expect(discountService.hasReachedLimit).toHaveBeenCalledTimes(1)
      expect(discountService.hasReachedLimit).toHaveBeenCalledWith(discount)

      expect(discountService.isDisabled).toHaveBeenCalledTimes(1)
      expect(discountService.isDisabled).toHaveBeenCalledWith(discount)

      expect(discountService.isValidForRegion).toHaveBeenCalledTimes(1)
      expect(discountService.isValidForRegion).toHaveBeenCalledWith(
        discount,
        cart.region_id
      )

      expect(discountService.canApplyForCustomer).toHaveBeenCalledTimes(1)
      expect(discountService.canApplyForCustomer).toHaveBeenCalledWith(
        discount.rule.id,
        cart.customer_id
      )
    })

    it("throws when hasReachedLimit returns true", async () => {
      const discount = {}
      const cart = getCart("with-d-and-customer")
      discountService.hasReachedLimit = jest.fn().mockImplementation(() => true)

      expect(
        discountService.validateDiscountForCartOrThrow(cart, discount)
      ).rejects.toThrow({
        message: "Discount has been used maximum allowed times",
      })
    })

    it("throws when hasNotStarted returns true", async () => {
      const discount = {}
      const cart = getCart("with-d-and-customer")
      discountService.hasNotStarted = jest.fn().mockImplementation(() => true)

      expect(
        discountService.validateDiscountForCartOrThrow(cart, discount)
      ).rejects.toThrow({
        message: "Discount is not valid yet",
      })
    })

    it("throws when hasExpired returns true", async () => {
      const discount = {}
      const cart = getCart("with-d-and-customer")
      discountService.hasExpired = jest.fn().mockImplementation(() => true)

      expect(
        discountService.validateDiscountForCartOrThrow(cart, discount)
      ).rejects.toThrow({
        message: "Discount is expired",
      })
    })

    it("throws when isDisabled returns true", async () => {
      const discount = {}
      const cart = getCart("with-d-and-customer")
      discountService.isDisabled = jest.fn().mockImplementation(() => true)

      expect(
        discountService.validateDiscountForCartOrThrow(cart, discount)
      ).rejects.toThrow({
        message: "The discount code is disabled",
      })
    })

    it("throws when isValidForRegion returns false", async () => {
      const discount = {}
      const cart = getCart("with-d-and-customer")
      discountService.isValidForRegion = jest
        .fn()
        .mockImplementation(() => Promise.resolve(false))

      expect(
        discountService.validateDiscountForCartOrThrow(cart, discount)
      ).rejects.toThrow({
        message: "The discount is not available in current region",
      })
    })

    it("throws when canApplyForCustomer returns false", async () => {
      const discount = { rule: { id: "" } }
      const cart = getCart("with-d-and-customer")
      discountService.canApplyForCustomer = jest
        .fn()
        .mockImplementation(() => Promise.resolve(false))

      expect(
        discountService.validateDiscountForCartOrThrow(cart, discount)
      ).rejects.toThrow({
        message: "Discount is not valid for customer",
      })
    })
  })

  describe("hasReachedLimit", () => {
    const discountService = new DiscountService({})

    it("returns true if discount limit is reached", () => {
      const discount = {
        id: "limit-reached",
        code: "limit-reached",
        regions: [{ id: "good" }],
        rule: {},
        usage_count: 2,
        usage_limit: 2,
      }
      const hasReachedLimit = discountService.hasReachedLimit(discount)
      expect(hasReachedLimit).toBe(true)
    })

    it("returns false if discount limit is not reached", () => {
      const discount = {
        id: "limit-reached",
        code: "limit-reached",
        regions: [{ id: "good" }],
        rule: {},
        usage_count: 1,
        usage_limit: 100,
      }

      const hasReachedLimit = discountService.hasReachedLimit(discount)
      expect(hasReachedLimit).toBe(false)
    })

    it("returns false if discount limit is not set", () => {
      const discount = {
        id: "limit-reached",
        code: "limit-reached",
        regions: [{ id: "good" }],
        rule: {},
        usage_count: 1,
      }

      const hasReachedLimit = discountService.hasReachedLimit(discount)
      expect(hasReachedLimit).toBe(false)
    })
  })

  describe("isDisabled", () => {
    const discountService = new DiscountService({})

    it("returns false if discount not disabled", async () => {
      const discount = {
        id: "10off",
        code: "10%OFF",
        regions: [{ id: "good" }],
        rule: {
          id: "10off-rule",
          type: "percentage",
        },
        is_disabled: false,
      }

      const isDisabled = discountService.isDisabled(discount)
      expect(isDisabled).toBe(false)
    })

    it("returns true if discount is disabled", async () => {
      const discount = {
        id: "10off",
        code: "10%OFF",
        regions: [{ id: "good" }],
        rule: {
          id: "10off-rule",
          type: "percentage",
        },
        is_disabled: true,
      }

      const isDisabled = discountService.isDisabled(discount)
      expect(isDisabled).toBe(true)
    })
  })

  describe("hasNotStarted", () => {
    const discountService = new DiscountService({})

    it("returns true if discount has a future starts_at date", async () => {
      const discount = {
        id: "10off",
        code: "10%OFF",
        regions: [{ id: "good" }],
        rule: {
          type: "percentage",
        },
        starts_at: getOffsetDate(1),
        ends_at: getOffsetDate(10),
      }

      const hasNotStarted = discountService.hasNotStarted(discount)
      expect(hasNotStarted).toBe(true)
    })

    it("returns false if discount has a past starts_at date", async () => {
      const discount = {
        id: "10off",
        code: "10%OFF",
        regions: [{ id: "good" }],
        rule: {
          type: "percentage",
        },
        starts_at: getOffsetDate(-2),
        ends_at: getOffsetDate(10),
      }

      const hasNotStarted = discountService.hasNotStarted(discount)
      expect(hasNotStarted).toBe(false)
    })
  })

  describe("hasExpired", () => {
    const discountService = new DiscountService({})

    it("returns false if discount has a future ends_at date", async () => {
      const discount = {
        id: "10off",
        code: "10%OFF",
        regions: [{ id: "good" }],
        rule: {
          type: "percentage",
        },
        ends_at: getOffsetDate(10),
        starts_at: getOffsetDate(-1),
      }

      const hasExpired = discountService.hasExpired(discount)
      expect(hasExpired).toBe(false)
    })

    it("returns true if discount has a past ends_at date", async () => {
      const discount = {
        id: "10off",
        code: "10%OFF",
        regions: [{ id: "good" }],
        rule: {
          type: "percentage",
        },
        starts_at: getOffsetDate(-10),
        ends_at: getOffsetDate(-1),
      }

      const hasExpired = discountService.hasExpired(discount)
      expect(hasExpired).toBe(true)
    })
  })

  describe("isValidForRegion", () => {
    const retrieveMock = jest.fn().mockImplementation((id) => {
      if (id === "parent-discount-us") {
        return Promise.resolve({
          id,
          regions: [{ id: "us" }],
          rule: {
            id: "10off-rule",
            type: "percentage",
          },
        })
      } else if (id === "parent-discount-dk") {
        return Promise.resolve({
          id,
          regions: [{ id: "dk" }],
          rule: {
            id: "10off-rule",
            type: "percentage",
          },
        })
      }
    })

    const discountService = new DiscountService({})
    discountService.retrieve = retrieveMock

    beforeEach(() => {
      jest.clearAllMocks()
    })

    it("returns false if discount is not available in a given region", async () => {
      const discount = {
        id: "10off",
        code: "10%OFF",
        regions: [{ id: "us" }],
        rule: {
          id: "10off-rule",
          type: "percentage",
        },
      }

      const isValidForRegion = await discountService.isValidForRegion(
        discount,
        "dk"
      )

      expect(retrieveMock).toBeCalledTimes(0)
      expect(isValidForRegion).toBe(false)
    })

    it("returns true if discount is available in a given region", async () => {
      const discount = {
        id: "10off",
        code: "10%OFF",
        regions: [{ id: "us" }],
        rule: {
          id: "10off-rule",
          type: "percentage",
        },
      }

      const isValidForRegion = await discountService.isValidForRegion(
        discount,
        "us"
      )

      expect(retrieveMock).toBeCalledTimes(0)
      expect(isValidForRegion).toBe(true)
    })

    it("returns false if discount has a parent discount and is not available in region", async () => {
      const discount = {
        id: "10off",
        code: "10%OFF",
        parent_discount_id: "parent-discount-us",
      }

      const isValidForRegion = await discountService.isValidForRegion(
        discount,
        "dk"
      )

      expect(retrieveMock).toBeCalledTimes(1)
      expect(retrieveMock).toBeCalledWith(discount.parent_discount_id, {
        relations: ["rule", "regions"],
      })
      expect(isValidForRegion).toBe(false)
    })

    it("returns true if discount has a parent discount and is available in region", async () => {
      const discount = {
        id: "10off",
        code: "10%OFF",
        parent_discount_id: "parent-discount-dk",
      }

      const isValidForRegion = await discountService.isValidForRegion(
        discount,
        "dk"
      )
      expect(retrieveMock).toBeCalledTimes(1)
      expect(retrieveMock).toBeCalledWith(discount.parent_discount_id, {
        relations: ["rule", "regions"],
      })
      expect(isValidForRegion).toBe(true)
    })
  })

  describe("canApplyForCustomer", () => {
    const discountConditionRepository = {
      canApplyForCustomer: jest
        .fn()
        .mockImplementation(() => Promise.resolve(true)),
    }

    const customerService = {
      retrieve: jest.fn().mockImplementation((id) => {
        if (id === "customer-no-groups") {
          return Promise.resolve({ id: "customer-no-groups" })
        }
        if (id === "customer-with-groups") {
          return Promise.resolve({
            id: "customer-with-groups",
            groups: [{ id: "group-1" }],
          })
        }
      }),
    }

    const discountService = new DiscountService({
      manager: MockManager,
      discountConditionRepository,
      customerService,
    })

    it("returns false on undefined customer id", async () => {
      const res = await discountService.canApplyForCustomer("rule-1")

      expect(res).toBe(false)

      expect(
        discountConditionRepository.canApplyForCustomer
      ).toHaveBeenCalledTimes(0)
    })

    it("returns true on customer with groups", async () => {
      const res = await discountService.canApplyForCustomer(
        "rule-1",
        "customer-with-groups"
      )

      expect(res).toBe(true)

      expect(
        discountConditionRepository.canApplyForCustomer
      ).toHaveBeenCalledTimes(1)
      expect(
        discountConditionRepository.canApplyForCustomer
      ).toHaveBeenCalledWith("rule-1", "customer-with-groups")
    })
  })
})

const getOffsetDate = (offset) => {
  const date = new Date()
  date.setDate(date.getDate() + offset)
  return date
}
