import {
  liquidatorMaxRepayAmount,
  calculateDrop,
  calculateLiquidationPoint,
  calculateCollateralToTransfer,
  Collateral,
  liquidationRisk,
} from "../../src";
import { createCollateral } from "../utils";

describe("Liquidation Module", () => {
  const defaultIrParams = {
    slope1: 0.03,
    slope2: 0.5,
    baseIR: 0.01,
    urKink: 0.8,
  };

  describe("Liquidation Trigger Calculations", () => {
    it("calculates drop correctly with single collateral", () => {
      const collaterals = [createCollateral(100, 10, 0.8, 0.8)];
      const currentDebt = 500;

      const drop = calculateDrop(collaterals, currentDebt);
      expect(drop).toBeCloseTo(0.375);
    });

    it("returns 0 when total collateral value is 0", () => {
      const collaterals: Collateral[] = [];
      const currentDebt = 1000;

      const drop = calculateDrop(collaterals, currentDebt);
      expect(drop).toBe(0);
    });

    it("handles collaterals with undefined liquidationLTV", () => {
      const collaterals = [
        {
          amount: 100,
          price: 10,
          maxLTV: 0.8,
        } as Collateral,
      ];
      const currentDebt = 500;

      expect(() => calculateDrop(collaterals, currentDebt)).toThrow(
        "LiquidationLTV is not defined",
      );
    });
  });

  describe("Liquidation Point Calculations", () => {
    it("calculates liquidation point correctly", () => {
      const lp = calculateLiquidationPoint(
        0.8, // accountLiqLTV
        100, // debt shares
        1000, // open interest
        1000, // total debt shares
        10000, // total assets (10% utilization rate)
        defaultIrParams,
        1, // second
      );
      expect(lp).toBeCloseTo(125);
    });

    it("returns 0 when accountLiqLTV is 0", () => {
      const lp = calculateLiquidationPoint(
        0,
        100,
        1000,
        1000,
        10000,
        defaultIrParams,
        1,
      );
      expect(lp).toBe(0);
    });
  });

  describe("Liquidator Max Repay Calculations", () => {
    it("prevents repayment higher than actual debt (regression test for original issue)", () => {
      const collateral: Collateral = {
        liquidationPremium: 0.01, // 1% premium
        maxLTV: 0.6,
        liquidationLTV: 0.7,
        amount: 1000,
        price: 1,
      };

      // This was the original problematic test case
      const amt = liquidatorMaxRepayAmount(
        10000, // debtShares
        1000, // openInterest
        10000, // totalDebtShares
        20000, // totalAssets
        defaultIrParams,
        3600, // 1 hour
        collateral,
        [collateral], // Pass the same collateral as allCollaterals
      );

      // Should never exceed openInterest (1000)
      expect(amt).toBeLessThanOrEqual(1000);

      // Should be capped by collateralValue / (1 + premium) ≈ 990.099
      expect(amt).toBeCloseTo(990.099, 3);

      // Verify it's using the collateral cap rather than maxRepayCalc
      const collateralCap =
        collateral.amount / (1 + collateral.liquidationPremium!);
      expect(amt).toBeCloseTo(collateralCap, 3);
    });

    it("calculates max repay with proper secured value consideration", () => {
      const collateral: Collateral = {
        liquidationPremium: 0.1, // 10% premium
        maxLTV: 0.6,
        liquidationLTV: 0.7,
        amount: 1000,
        price: 1,
      };

      const amt = liquidatorMaxRepayAmount(
        1000, // debtShares (all debt)
        1000, // openInterest
        1000, // totalDebtShares
        2000, // totalAssets
        defaultIrParams,
        3600,
        collateral,
        [collateral],
      );

      // Calculations:
      // collateralValue = 1000
      // securedValue = 1000 * 0.7 = 700
      // debtAssets ≈ 1000 (plus small interest)
      // denominator = 1 - (1 + 0.1) * 0.7 = 0.23
      // maxRepayCalc = (1000 - 700) / 0.23 ≈ 1304.35
      // collateralCap = 1000 / 1.1 ≈ 909.09
      // Should return min(909.09, 1000) = 909.09
      expect(amt).toBeCloseTo(909.09, 2);
    });

    it("handles multi-collateral case correctly", () => {
      const collateralA: Collateral = {
        liquidationPremium: 0.1, // 10% premium
        maxLTV: 0.7,
        liquidationLTV: 0.78,
        amount: 100,
        price: 10, // Value = 1000
      };

      const collateralB: Collateral = {
        liquidationPremium: 0.12, // 12% premium
        maxLTV: 0.6,
        liquidationLTV: 0.7,
        amount: 664,
        price: 1, // Value = 664
      };

      const collateralC: Collateral = {
        liquidationPremium: 0.15, // 15% premium
        maxLTV: 0.5,
        liquidationLTV: 0.6,
        amount: 350,
        price: 1, // Value = 350
      };

      const allCollaterals = [collateralA, collateralB, collateralC];

      // Test liquidating collateral B
      const amtB = liquidatorMaxRepayAmount(
        1000, // debtShares
        1014, // openInterest (matches image)
        1000, // totalDebtShares
        2000, // totalAssets
        defaultIrParams,
        3600,
        collateralB,
        allCollaterals,
      );

      // Calculations for collateral B:
      // totalSecuredValue = (1000 x 0.78) + (664 x 0.70) + (350 x 0.60) = 1247.8
      // denominator = 1 - (1 + 0.12) x 0.70 = 0.156
      // maxRepayCalc = (1014 - 1247.8) / 0.156 = -1497.44
      // collateralCap = 664 / (1 + 0.12) = 592.86
      // maxRepayAllowed = max(min(-1497.44, 592.86), 0) = 0
      expect(amtB).toBe(0);

      // Test liquidating collateral B with higher debt
      const amtBHighDebt = liquidatorMaxRepayAmount(
        2000, // Higher debt shares
        2014, // Higher openInterest
        1000,
        2000,
        defaultIrParams,
        3600,
        collateralB,
        allCollaterals,
      );

      // For higher debt:
      // debtAssets ≈ 2014
      // totalSecuredValue = 1247.8 (same as before)
      // maxRepayCalc = (2014 - 1247.8) / 0.156 = 4910.38
      // collateralCap = 664 / 1.12 = 592.86
      // maxRepayAllowed = max(min(4910.38, 592.86), 0) = 592.86
      expect(amtBHighDebt).toBeCloseTo(592.86, 2);
    });

    it("returns 0 when debt is fully secured by collateral", () => {
      const collateral: Collateral = {
        liquidationPremium: 0.1,
        maxLTV: 0.6,
        liquidationLTV: 0.7,
        amount: 2000, // High collateral amount
        price: 1,
      };

      const amt = liquidatorMaxRepayAmount(
        100, // Small debt
        1000,
        1000,
        2000,
        defaultIrParams,
        3600,
        collateral,
        [collateral],
      );

      // Debt is fully secured by collateral, so no liquidation needed
      expect(amt).toBe(0);
    });

    it("respects collateral cap when debt is high", () => {
      const collateral: Collateral = {
        liquidationPremium: 0.1,
        maxLTV: 0.6,
        liquidationLTV: 0.7,
        amount: 100, // Low collateral amount
        price: 1,
      };

      const amt = liquidatorMaxRepayAmount(
        1000, // High debt
        1000,
        1000,
        2000,
        defaultIrParams,
        3600,
        collateral,
        [collateral],
      );

      // Collateral cap = 100 / 1.1 ≈ 90.91
      expect(amt).toBeCloseTo(90.91, 2);
    });

    it("handles zero collateral amount", () => {
      const collateral: Collateral = {
        liquidationPremium: 0.1,
        maxLTV: 0.6,
        liquidationLTV: 0.7,
        amount: 0,
        price: 1,
      };

      const amt = liquidatorMaxRepayAmount(
        1000,
        1000,
        1000,
        2000,
        defaultIrParams,
        3600,
        collateral,
        [collateral],
      );

      expect(amt).toBe(0);
    });

    it("throws error when liquidation parameters are undefined", () => {
      const collateral = {
        maxLTV: 0.6,
        amount: 1000,
        price: 1,
      } as Collateral;

      expect(() =>
        liquidatorMaxRepayAmount(
          1000,
          1000,
          1000,
          2000,
          defaultIrParams,
          3600,
          collateral,
          [collateral],
        ),
      ).toThrow("Liquidation LTV or liquidation premium are not defined");
    });

    it("handles denominator near zero case", () => {
      const collateral: Collateral = {
        liquidationPremium: 0.25, // 25% premium
        maxLTV: 0.7,
        liquidationLTV: 0.79,
        amount: 1000,
        price: 1,
      };

      const amt = liquidatorMaxRepayAmount(
        2000, // High debt to ensure positive maxRepayCalc
        2000,
        1000,
        2000,
        defaultIrParams,
        3600,
        collateral,
        [collateral],
      );

      // Should still be capped by collateralCap = 1000/1.25 = 800
      expect(amt).toBeCloseTo(800, 2);
    });

    it("handles multiple collaterals of same type", () => {
      const collateral1: Collateral = {
        liquidationPremium: 0.1,
        maxLTV: 0.6,
        liquidationLTV: 0.7,
        amount: 500,
        price: 1,
      };

      const collateral2: Collateral = {
        liquidationPremium: 0.1,
        maxLTV: 0.6,
        liquidationLTV: 0.7,
        amount: 500,
        price: 1,
      };

      const amt = liquidatorMaxRepayAmount(
        1500,
        1500,
        1000,
        2000,
        defaultIrParams,
        3600,
        collateral1,
        [collateral1, collateral2],
      );

      // Total secured value = (500 + 500) * 0.7 = 700
      // maxRepayCalc = (1500 - 700) / (1 - 1.1 * 0.7) = 2666.67
      // collateralCap = 500 / 1.1 = 454.55
      expect(amt).toBeCloseTo(454.55, 2);
    });

    it("handles different price scales correctly", () => {
      const collateral: Collateral = {
        liquidationPremium: 0.1,
        maxLTV: 0.6,
        liquidationLTV: 0.7,
        amount: 1, // 1 BTC
        price: 50000, // $50,000 per BTC
      };

      const amt = liquidatorMaxRepayAmount(
        60000,
        60000,
        1000,
        100000,
        defaultIrParams,
        3600,
        collateral,
        [collateral],
      );

      // collateralCap = (1 * 50000) / 1.1 = 45454.55
      expect(amt).toBeCloseTo(45454.55, 2);
    });

    it("handles zero and negative prices", () => {
      const zeroPrice: Collateral = {
        liquidationPremium: 0.1,
        maxLTV: 0.6,
        liquidationLTV: 0.7,
        amount: 1000,
        price: 0,
      };

      const negativePrice: Collateral = {
        liquidationPremium: 0.1,
        maxLTV: 0.6,
        liquidationLTV: 0.7,
        amount: 1000,
        price: -1,
      };

      const amtZero = liquidatorMaxRepayAmount(
        1000,
        1000,
        1000,
        2000,
        defaultIrParams,
        3600,
        zeroPrice,
        [zeroPrice],
      );

      const amtNegative = liquidatorMaxRepayAmount(
        1000,
        1000,
        1000,
        2000,
        defaultIrParams,
        3600,
        negativePrice,
        [negativePrice],
      );

      expect(amtZero).toBe(0);
      expect(amtNegative).toBe(0);
    });

    it("throws error when any collateral in allCollaterals has undefined liquidationLTV", () => {
      const validCollateral: Collateral = {
        liquidationPremium: 0.1,
        maxLTV: 0.6,
        liquidationLTV: 0.7,
        amount: 1000,
        price: 1,
      };

      const invalidCollateral = {
        liquidationPremium: 0.1,
        maxLTV: 0.6,
        // liquidationLTV is undefined
        amount: 500,
        price: 1,
      } as Collateral;

      expect(() =>
        liquidatorMaxRepayAmount(
          1000,
          1000,
          1000,
          2000,
          defaultIrParams,
          3600,
          validCollateral, // Main collateral is valid
          [validCollateral, invalidCollateral], // But one of allCollaterals is invalid
        ),
      ).toThrow("LiquidationLTV is not defined for one or more collaterals");
    });
  });

  describe("Collateral Transfer Calculations", () => {
    it("calculates collateral to transfer correctly", () => {
      const collateral: Collateral = {
        liquidationPremium: 0.1,
        maxLTV: 0.6,
        liquidationLTV: 0.7,
        amount: 1000,
        price: 2,
      };

      const collateralToTransfer = calculateCollateralToTransfer(
        100,
        collateral,
      );

      // For repayAmount = 100:
      // liquidationReward = 100 * 0.1 = 10
      // collateralToTransfer = (100 + 10) / 2 = 55
      expect(collateralToTransfer).toBe(55);
    });

    it("handles different price scales in collateral transfer", () => {
      const collateral: Collateral = {
        liquidationPremium: 0.1,
        maxLTV: 0.6,
        liquidationLTV: 0.7,
        amount: 1,
        price: 50000, // $50,000 per unit
      };

      const collateralToTransfer = calculateCollateralToTransfer(
        5500,
        collateral,
      );

      // For repayAmount = 5500:
      // liquidationReward = 5500 * 0.1 = 550
      // collateralToTransfer = (5500 + 550) / 50000 = 0.1210
      expect(collateralToTransfer).toBeCloseTo(0.121, 4);
    });

    it("handles precision edge cases in collateral transfer", () => {
      const collateral: Collateral = {
        liquidationPremium: 0.1,
        maxLTV: 0.6,
        liquidationLTV: 0.7,
        amount: 1,
        price: 0.0001, // Very small price
      };

      const collateralToTransfer = calculateCollateralToTransfer(
        0.00001,
        collateral,
      );
      expect(collateralToTransfer).toBeGreaterThan(0);
      expect(Number.isFinite(collateralToTransfer)).toBe(true);
    });

    it("throws error when liquidation premium is undefined", () => {
      const collateral = {
        maxLTV: 0.6,
        liquidationLTV: 0.7,
        amount: 1000,
        price: 1,
      } as Collateral;

      expect(() => calculateCollateralToTransfer(100, collateral)).toThrow(
        "Liquidation premium is not defined",
      );
    });
  });

  describe("calculateDrop extra cases", () => {
    it("handles multiple collaterals", () => {
      const collaterals: Collateral[] = [
        createCollateral(100, 10, 0.8, 0.8), // secured = 800
        { amount: 50, price: 20, maxLTV: 0.6, liquidationLTV: 0.6 }, // secured = 600
      ];
      const currentDebt = 700;
      // total secured = 1400, drop = 1 - 700/1400 = 0.5
      expect(calculateDrop(collaterals, currentDebt)).toBeCloseTo(0.5, 6);
    });

    it("returns a negative value when debt exceeds secured collateral", () => {
      const collaterals: Collateral[] = [
        createCollateral(100, 10, 0.8, 0.8), // secured = 800
        { amount: 50, price: 20, maxLTV: 0.6, liquidationLTV: 0.6 }, // secured = 600
      ];
      const currentDebt = 2000;
      // 1 - 2000/1400 = -0.428571...
      expect(calculateDrop(collaterals, currentDebt)).toBeCloseTo(
        -0.4285714286,
        6,
      );
    });

    it("ignores zero priced collateral contributions in the sum", () => {
      const collaterals: Collateral[] = [
        createCollateral(100, 10, 0.8, 0.8), // secured = 800
        { amount: 100, price: 0, maxLTV: 0.7, liquidationLTV: 0.7 }, // secured = 0
      ];
      const currentDebt = 400;
      // total secured = 800, drop = 1 - 400/800 = 0.5
      expect(calculateDrop(collaterals, currentDebt)).toBeCloseTo(0.5, 6);
    });
  });

  describe("calculateLiquidationPoint properties", () => {
    it("scales inversely with accountLiqLTV", () => {
      const args = {
        debtShares: 100,
        openInterest: 1000,
        totalDebtShares: 1000,
        totalAssets: 10000,
        timeDelta: 1,
      };
      const lpAt08 = calculateLiquidationPoint(
        0.8,
        args.debtShares,
        args.openInterest,
        args.totalDebtShares,
        args.totalAssets,
        defaultIrParams,
        args.timeDelta,
      );
      const lpAt05 = calculateLiquidationPoint(
        0.5,
        args.debtShares,
        args.openInterest,
        args.totalDebtShares,
        args.totalAssets,
        defaultIrParams,
        args.timeDelta,
      );
      // LP(0.5) should be 1.6x LP(0.8)
      expect(lpAt05).toBeCloseTo(lpAt08 * (0.8 / 0.5), 6);
    });
  });

  describe("liquidationRisk coverage", () => {
    it("matches baseline liquidation point when debtAssets is zero", () => {
      const accountLiqLTV = 0.8;
      const initialDebtShares = 100;
      const initialOpenInterest = 1000;
      const initialTotalDebtShares = 1000;
      const initialTotalAssets = 10000;
      const timeDelta = 1;

      const baseline = calculateLiquidationPoint(
        accountLiqLTV,
        initialDebtShares,
        initialOpenInterest,
        initialTotalDebtShares,
        initialTotalAssets,
        defaultIrParams,
        timeDelta,
      );

      const risk = liquidationRisk(
        0, // debtAssets
        initialOpenInterest,
        initialTotalDebtShares,
        initialTotalAssets,
        0, // protocolReservePercentage
        defaultIrParams,
        timeDelta,
        initialDebtShares,
        initialOpenInterest,
        initialTotalDebtShares,
        initialTotalAssets,
        accountLiqLTV,
      );

      expect(risk).toBeCloseTo(baseline, 6);
    });

    it("increases as additional debtAssets increases", () => {
      const accountLiqLTV = 0.7;
      const initialDebtShares = 200;
      const initialOpenInterest = 2000;
      const initialTotalDebtShares = 2000;
      const initialTotalAssets = 15000;
      const timeDelta = 3600;

      const riskSmall = liquidationRisk(
        100, // smaller added debt
        initialOpenInterest,
        initialTotalDebtShares,
        initialTotalAssets,
        0,
        defaultIrParams,
        timeDelta,
        initialDebtShares,
        initialOpenInterest,
        initialTotalDebtShares,
        initialTotalAssets,
        accountLiqLTV,
      );

      const riskLarge = liquidationRisk(
        500, // larger added debt
        initialOpenInterest,
        initialTotalDebtShares,
        initialTotalAssets,
        0,
        defaultIrParams,
        timeDelta,
        initialDebtShares,
        initialOpenInterest,
        initialTotalDebtShares,
        initialTotalAssets,
        accountLiqLTV,
      );

      expect(riskLarge).toBeGreaterThan(riskSmall);
    });
  });

  describe("liquidatorMaxRepayAmount extra edges", () => {
    it("returns 0 when denominator is negative", () => {
      const collateral: Collateral = {
        liquidationPremium: 0.5, // makes denominator negative with liqLTV 0.8
        maxLTV: 0.6,
        liquidationLTV: 0.8,
        amount: 1000,
        price: 1,
      };

      const amt = liquidatorMaxRepayAmount(
        1000,
        1000,
        1000,
        2000,
        defaultIrParams,
        1,
        collateral,
        [collateral],
      );

      expect(amt).toBe(0);
    });
  });

  describe("calculateCollateralToTransfer extra edges", () => {
    it("returns 0 when repayAmount is 0", () => {
      const collateral: Collateral = {
        liquidationPremium: 0.1,
        maxLTV: 0.6,
        liquidationLTV: 0.7,
        amount: 1000,
        price: 2,
      };
      expect(calculateCollateralToTransfer(0, collateral)).toBe(0);
    });

    it("returns Infinity when price is 0", () => {
      const collateral: Collateral = {
        liquidationPremium: 0.1,
        maxLTV: 0.6,
        liquidationLTV: 0.7,
        amount: 1000,
        price: 0,
      };
      const v = calculateCollateralToTransfer(100, collateral);
      expect(v).toBe(Infinity);
    });

    it("supports negative liquidation premiums arithmetically", () => {
      const collateral: Collateral = {
        liquidationPremium: -0.05,
        maxLTV: 0.6,
        liquidationLTV: 0.7,
        amount: 1000,
        price: 10,
      };
      // (100 + 100 * -0.05) / 10 = 9.5
      expect(calculateCollateralToTransfer(100, collateral)).toBeCloseTo(
        9.5,
        6,
      );
    });
  });
});
