const RentalDurationCalculator = require("../../../utils/rentalDurationCalculator");

describe("RentalDurationCalculator - Hybrid Pricing", () => {
  // Helper to create ISO date strings
  const date = (dateStr, time = "10:00") => {
    return `2024-01-${dateStr.padStart(2, "0")}T${time}:00.000Z`;
  };

  describe("calculateRentalCost", () => {
    describe("Basic single-tier pricing", () => {
      test("should calculate hourly rate for short rentals", () => {
        const item = { pricePerHour: 10 };
        const result = RentalDurationCalculator.calculateRentalCost(
          date("01", "10:00"),
          date("01", "13:00"),
          item
        );
        expect(result).toBe(30); // 3 hours * $10
      });

      test("should calculate daily rate for exact day rentals", () => {
        const item = { pricePerDay: 50 };
        const result = RentalDurationCalculator.calculateRentalCost(
          date("01", "10:00"),
          date("03", "10:00"),
          item
        );
        expect(result).toBe(100); // 2 days * $50
      });

      test("should calculate weekly rate for exact week rentals", () => {
        const item = { pricePerWeek: 200 };
        const result = RentalDurationCalculator.calculateRentalCost(
          date("01", "10:00"),
          date("15", "10:00"),
          item
        );
        expect(result).toBe(400); // 2 weeks * $200
      });

      test("should calculate monthly rate for exact month rentals", () => {
        const item = { pricePerMonth: 600 };
        const result = RentalDurationCalculator.calculateRentalCost(
          "2024-01-01T10:00:00.000Z",
          "2024-03-01T10:00:00.000Z", // 60 days = exactly 2 months (using 30-day months)
          item
        );
        expect(result).toBe(1200); // 2 months * $600
      });
    });

    describe("Hybrid day + hours pricing", () => {
      test("should combine day and hours for 26-hour rental", () => {
        const item = { pricePerDay: 50, pricePerHour: 10 };
        const result = RentalDurationCalculator.calculateRentalCost(
          date("01", "15:00"),
          date("02", "17:00"), // 26 hours
          item
        );
        expect(result).toBe(70); // 1 day ($50) + 2 hours ($20)
      });

      test("should combine day and hours for 27-hour rental", () => {
        const item = { pricePerDay: 50, pricePerHour: 10 };
        const result = RentalDurationCalculator.calculateRentalCost(
          date("01", "10:00"),
          date("02", "13:00"), // 27 hours
          item
        );
        expect(result).toBe(80); // 1 day ($50) + 3 hours ($30)
      });

      test("should choose cheaper option - round up when hourly is expensive", () => {
        // 25 hours: daily = $40, hourly = $50
        // Hybrid: 1 day ($40) + 1 hour ($50) = $90
        // Round-up: 2 days ($80)
        // Expected: $80 (round-up wins)
        const item = { pricePerDay: 40, pricePerHour: 50 };
        const result = RentalDurationCalculator.calculateRentalCost(
          date("01", "10:00"),
          date("02", "11:00"), // 25 hours
          item
        );
        expect(result).toBe(80); // 2 days (cheaper than 1d + 1h)
      });

      test("should choose cheaper option - hybrid when hourly is cheap", () => {
        // 25 hours: daily = $50, hourly = $5
        // Hybrid: 1 day ($50) + 1 hour ($5) = $55
        // Round-up: 2 days ($100)
        // Expected: $55 (hybrid wins)
        const item = { pricePerDay: 50, pricePerHour: 5 };
        const result = RentalDurationCalculator.calculateRentalCost(
          date("01", "10:00"),
          date("02", "11:00"), // 25 hours
          item
        );
        expect(result).toBe(55); // 1 day + 1 hour (cheaper)
      });
    });

    describe("Hybrid week + days pricing", () => {
      test("should combine week and days for 10-day rental", () => {
        const item = { pricePerWeek: 200, pricePerDay: 50 };
        const result = RentalDurationCalculator.calculateRentalCost(
          date("01", "10:00"),
          date("11", "10:00"), // 10 days
          item
        );
        expect(result).toBe(350); // 1 week ($200) + 3 days ($150)
      });

      test("should combine week and days for 9-day rental", () => {
        const item = { pricePerWeek: 200, pricePerDay: 50 };
        const result = RentalDurationCalculator.calculateRentalCost(
          date("01", "10:00"),
          date("10", "10:00"), // 9 days
          item
        );
        expect(result).toBe(300); // 1 week ($200) + 2 days ($100)
      });

      test("should round up to 2 weeks when daily cost exceeds week", () => {
        // 10 days: weekly = $100, daily = $50
        // Hybrid: 1 week ($100) + 3 days ($150) = $250
        // Round-up: 2 weeks ($200)
        // Expected: $200 (round-up wins)
        const item = { pricePerWeek: 100, pricePerDay: 50 };
        const result = RentalDurationCalculator.calculateRentalCost(
          date("01", "10:00"),
          date("11", "10:00"), // 10 days
          item
        );
        expect(result).toBe(200); // 2 weeks (cheaper)
      });
    });

    describe("Hybrid month + weeks + days pricing", () => {
      test("should combine month, week, and days for 38-day rental", () => {
        const item = { pricePerMonth: 600, pricePerWeek: 200, pricePerDay: 50 };
        const result = RentalDurationCalculator.calculateRentalCost(
          "2024-01-01T10:00:00.000Z",
          "2024-02-08T10:00:00.000Z", // 38 days
          item
        );
        expect(result).toBe(850); // 1 month ($600) + 1 week ($200) + 1 day ($50)
      });

      test("should combine month and days for 35-day rental without weekly", () => {
        const item = { pricePerMonth: 600, pricePerDay: 50 };
        const result = RentalDurationCalculator.calculateRentalCost(
          "2024-01-01T10:00:00.000Z",
          "2024-02-05T10:00:00.000Z", // 35 days
          item
        );
        expect(result).toBe(850); // 1 month ($600) + 5 days ($250)
      });
    });

    describe("Full hybrid: month + week + day + hour", () => {
      test("should combine all tiers for complex duration", () => {
        const item = {
          pricePerMonth: 600,
          pricePerWeek: 200,
          pricePerDay: 50,
          pricePerHour: 10,
        };
        // 38 days + 5 hours
        const result = RentalDurationCalculator.calculateRentalCost(
          "2024-01-01T10:00:00.000Z",
          "2024-02-08T15:00:00.000Z",
          item
        );
        // 1 month + 1 week + 1 day + 5 hours = $600 + $200 + $50 + $50 = $900
        expect(result).toBe(900);
      });
    });

    describe("Missing tier fallback", () => {
      test("should round up to days when no hourly rate", () => {
        const item = { pricePerDay: 50 }; // No hourly
        const result = RentalDurationCalculator.calculateRentalCost(
          date("01", "15:00"),
          date("02", "17:00"), // 26 hours
          item
        );
        expect(result).toBe(100); // 2 days (rounded up)
      });

      test("should round up to weeks when no daily rate", () => {
        const item = { pricePerWeek: 200 }; // No daily
        const result = RentalDurationCalculator.calculateRentalCost(
          date("01", "10:00"),
          date("10", "10:00"), // 9 days
          item
        );
        expect(result).toBe(400); // 2 weeks (rounded up)
      });

      test("should round up to months when no weekly or daily rate", () => {
        const item = { pricePerMonth: 600 }; // Only monthly
        const result = RentalDurationCalculator.calculateRentalCost(
          "2024-01-01T10:00:00.000Z",
          "2024-02-15T10:00:00.000Z", // 45 days
          item
        );
        expect(result).toBe(1200); // 2 months (rounded up)
      });

      test("should use hourly only when no larger tiers", () => {
        const item = { pricePerHour: 10 }; // Only hourly
        const result = RentalDurationCalculator.calculateRentalCost(
          date("01", "10:00"),
          date("03", "10:00"), // 48 hours
          item
        );
        expect(result).toBe(480); // 48 hours * $10
      });
    });

    describe("Edge cases", () => {
      test("should return 0 for zero duration", () => {
        const item = { pricePerDay: 50 };
        const result = RentalDurationCalculator.calculateRentalCost(
          date("01", "10:00"),
          date("01", "10:00"),
          item
        );
        expect(result).toBe(0);
      });

      test("should return 0 for negative duration", () => {
        const item = { pricePerDay: 50 };
        const result = RentalDurationCalculator.calculateRentalCost(
          date("02", "10:00"),
          date("01", "10:00"), // End before start
          item
        );
        expect(result).toBe(0);
      });

      test("should return 0 for free items (no pricing)", () => {
        const item = {}; // No pricing
        const result = RentalDurationCalculator.calculateRentalCost(
          date("01", "10:00"),
          date("05", "10:00"),
          item
        );
        expect(result).toBe(0);
      });

      test("should return 0 when all prices are 0", () => {
        const item = {
          pricePerHour: 0,
          pricePerDay: 0,
          pricePerWeek: 0,
          pricePerMonth: 0,
        };
        const result = RentalDurationCalculator.calculateRentalCost(
          date("01", "10:00"),
          date("05", "10:00"),
          item
        );
        expect(result).toBe(0);
      });

      test("should handle decimal prices", () => {
        const item = { pricePerHour: 5.5 };
        const result = RentalDurationCalculator.calculateRentalCost(
          date("01", "10:00"),
          date("01", "13:00"), // 3 hours
          item
        );
        expect(result).toBe(16.5); // 3 * $5.50
      });

      test("should round result to 2 decimal places", () => {
        const item = { pricePerHour: 3.33 };
        const result = RentalDurationCalculator.calculateRentalCost(
          date("01", "10:00"),
          date("01", "13:00"), // 3 hours
          item
        );
        expect(result).toBe(9.99); // 3 * $3.33
      });

      test("should handle string prices in item", () => {
        const item = { pricePerDay: "50", pricePerHour: "10" };
        const result = RentalDurationCalculator.calculateRentalCost(
          date("01", "15:00"),
          date("02", "17:00"), // 26 hours
          item
        );
        expect(result).toBe(70); // 1 day + 2 hours
      });

      test("should handle very short rentals (less than 1 hour)", () => {
        const item = { pricePerHour: 10 };
        const result = RentalDurationCalculator.calculateRentalCost(
          "2024-01-01T10:00:00.000Z",
          "2024-01-01T10:30:00.000Z", // 30 minutes
          item
        );
        expect(result).toBe(10); // Rounds up to 1 hour
      });
    });

    describe("Real-world scenarios", () => {
      test("Scenario: Tool rental 3 days 4 hours", () => {
        const item = { pricePerDay: 25, pricePerHour: 5 };
        const result = RentalDurationCalculator.calculateRentalCost(
          "2024-01-01T08:00:00.000Z",
          "2024-01-04T12:00:00.000Z", // 3 days + 4 hours
          item
        );
        expect(result).toBe(95); // 3 days ($75) + 4 hours ($20)
      });

      test("Scenario: Equipment rental 2 weeks 3 days", () => {
        const item = { pricePerWeek: 100, pricePerDay: 20 };
        const result = RentalDurationCalculator.calculateRentalCost(
          "2024-01-01T10:00:00.000Z",
          "2024-01-18T10:00:00.000Z", // 17 days = 2 weeks + 3 days
          item
        );
        expect(result).toBe(260); // 2 weeks ($200) + 3 days ($60)
      });

      test("Scenario: Long-term rental 2 months 1 week", () => {
        const item = { pricePerMonth: 500, pricePerWeek: 150 };
        const result = RentalDurationCalculator.calculateRentalCost(
          "2024-01-01T10:00:00.000Z",
          "2024-03-08T10:00:00.000Z", // ~67 days = 2 months + 1 week
          item
        );
        expect(result).toBe(1150); // 2 months ($1000) + 1 week ($150)
      });
    });
  });

  describe("buildPricingTiers", () => {
    test("should return empty array for items with no pricing", () => {
      const item = {};
      const tiers = RentalDurationCalculator.buildPricingTiers(item);
      expect(tiers).toHaveLength(0);
    });

    test("should build tiers from largest to smallest", () => {
      const item = {
        pricePerMonth: 600,
        pricePerWeek: 200,
        pricePerDay: 50,
        pricePerHour: 10,
      };
      const tiers = RentalDurationCalculator.buildPricingTiers(item);
      expect(tiers).toHaveLength(4);
      expect(tiers[0].name).toBe("month");
      expect(tiers[1].name).toBe("week");
      expect(tiers[2].name).toBe("day");
      expect(tiers[3].name).toBe("hour");
    });

    test("should skip tiers with 0 or null pricing", () => {
      const item = {
        pricePerMonth: 0,
        pricePerWeek: null,
        pricePerDay: 50,
        pricePerHour: 10,
      };
      const tiers = RentalDurationCalculator.buildPricingTiers(item);
      expect(tiers).toHaveLength(2);
      expect(tiers[0].name).toBe("day");
      expect(tiers[1].name).toBe("hour");
    });
  });
});
