import { expect } from 'chai';

import {
  HyperbolaPayoutCurvePiece,
  PayoutCurvePiece,
  PolynomialPayoutCurvePiece,
} from '../../lib/messages/PayoutCurvePiece';
import { F64 } from '../../lib/serialize/F64';

describe('PayoutCurvePiece', () => {
  describe('PolynomialPayoutCurvePiece', () => {
    it('serializes and deserializes correctly', () => {
      const instance = new PolynomialPayoutCurvePiece();

      instance.points = [
        {
          eventOutcome: BigInt(0),
          outcomePayout: BigInt(0),
          extraPrecision: 0,
        },
        {
          eventOutcome: BigInt(1),
          outcomePayout: BigInt(1),
          extraPrecision: 0,
        },
      ];

      // Test that it serializes without errors (new dlcspecs PR #163 format)
      const serialized = instance.serialize();
      expect(serialized).to.be.instanceof(Buffer);
      expect(serialized.length).to.be.greaterThan(0);

      // Test round-trip serialization
      const deserialized = PayoutCurvePiece.deserialize(
        serialized,
      ) as PolynomialPayoutCurvePiece;
      expect(deserialized).to.be.instanceof(PolynomialPayoutCurvePiece);
      expect(deserialized.points.length).to.equal(2);
      expect(deserialized.points[0].eventOutcome).to.equal(BigInt(0));
      expect(deserialized.points[0].outcomePayout).to.equal(BigInt(0));
      expect(deserialized.points[1].eventOutcome).to.equal(BigInt(1));
      expect(deserialized.points[1].outcomePayout).to.equal(BigInt(1));
    });

    it('handles large BigInt values correctly', () => {
      const instance = new PolynomialPayoutCurvePiece();
      const largeBigInt = BigInt('18446744073709551615'); // Max uint64

      instance.points = [
        {
          eventOutcome: largeBigInt,
          outcomePayout: largeBigInt,
          extraPrecision: 0,
        },
      ];

      const serialized = instance.serialize();
      const deserialized = PayoutCurvePiece.deserialize(
        serialized,
      ) as PolynomialPayoutCurvePiece;

      expect(deserialized.points[0].eventOutcome).to.equal(largeBigInt);
      expect(deserialized.points[0].outcomePayout).to.equal(largeBigInt);
    });
  });

  describe('HyperbolaPayoutCurvePiece', () => {
    it('serializes and deserializes correctly with normal values', () => {
      const instance = new HyperbolaPayoutCurvePiece();
      instance.usePositivePiece = true;
      instance.translateOutcome = F64.fromNumber(100.5);
      instance.translatePayout = F64.fromNumber(50.25);
      instance.a = F64.fromNumber(1.0);
      instance.b = F64.fromNumber(0.0);
      instance.c = F64.fromNumber(0.0);
      instance.d = F64.fromNumber(200.0);

      const serialized = instance.serialize();
      expect(serialized).to.be.instanceof(Buffer);
      expect(serialized.length).to.be.greaterThan(0);

      // Test round-trip
      const deserialized = PayoutCurvePiece.deserialize(
        serialized,
      ) as HyperbolaPayoutCurvePiece;
      expect(deserialized.usePositivePiece).to.equal(true);
      expect(deserialized.translateOutcome.toNumber()).to.equal(100.5);
    });

    it('handles very large f64 values without precision loss', () => {
      // Test values that exceed JavaScript MAX_SAFE_INTEGER
      const testCases = [
        {
          name: 'Large positive number',
          value: 1.7976931348623157e100, // Very large but valid f64
        },
        {
          name: 'Very small number',
          value: 5e-324, // Smallest positive denormalized f64
        },
        {
          name: 'Beyond safe integer',
          value: 9007199254740992, // MAX_SAFE_INTEGER + 1
        },
        {
          name: 'Scientific notation',
          value: 1.23456789e50,
        },
      ];

      testCases.forEach(({ value }) => {
        const instance = new HyperbolaPayoutCurvePiece();
        instance.usePositivePiece = false;
        instance.translateOutcome = F64.fromNumber(value);
        instance.translatePayout = F64.fromNumber(123.456);
        instance.a = F64.fromNumber(1.0);
        instance.b = F64.fromNumber(0.0);
        instance.c = F64.fromNumber(0.0);
        instance.d = F64.fromNumber(value * 2);

        // Test round-trip serialization preserves exact binary representation
        const serialized = instance.serialize();
        const deserialized = PayoutCurvePiece.deserialize(
          serialized,
        ) as HyperbolaPayoutCurvePiece;

        // The F64 should maintain exact binary representation
        expect(deserialized.translateOutcome.equals(instance.translateOutcome))
          .to.be.true;
        expect(deserialized.d.equals(instance.d)).to.be.true;
      });
    });

    it('handles JSON with string values (for very large numbers)', () => {
      // Test JSON with string values for large numbers
      const jsonWithStrings = {
        usePositivePiece: true,
        translateOutcome: '1.7976931348623157e+100', // String representation
        translatePayout: 50000.0, // Regular number
        a: '9007199254740992', // String for large integer
        b: 0.0,
        c: 0.0,
        d: 1.5,
      };

      const instance = HyperbolaPayoutCurvePiece.fromJSON(jsonWithStrings);

      // Should handle both string and number inputs correctly
      expect(instance.usePositivePiece).to.be.true;
      expect(instance.translateOutcome.toNumber()).to.equal(
        1.7976931348623157e100,
      );
      expect(instance.translatePayout.toNumber()).to.equal(50000.0);
      expect(instance.a.toNumber()).to.equal(9007199254740992);
    });

    it('JSON round-trip maintains values within JavaScript precision limits', () => {
      const instance = new HyperbolaPayoutCurvePiece();
      instance.usePositivePiece = true;
      instance.translateOutcome = F64.fromNumber(50000.5);
      instance.translatePayout = F64.fromNumber(25000.25);
      instance.a = F64.fromNumber(1.5);
      instance.b = F64.fromNumber(0.0);
      instance.c = F64.fromNumber(0.0);
      instance.d = F64.fromNumber(10000.0);

      // Test JSON round-trip
      const json = instance.toJSON();
      const restored = HyperbolaPayoutCurvePiece.fromJSON(
        json.hyperbolaPayoutCurvePiece,
      );

      expect(restored.usePositivePiece).to.equal(instance.usePositivePiece);
      expect(restored.translateOutcome.toNumber()).to.equal(
        instance.translateOutcome.toNumber(),
      );
      expect(restored.translatePayout.toNumber()).to.equal(
        instance.translatePayout.toNumber(),
      );
      expect(restored.a.toNumber()).to.equal(instance.a.toNumber());
      expect(restored.d.toNumber()).to.equal(instance.d.toNumber());
    });

    it('demonstrates precision limits with very large numbers in JSON', () => {
      // This test demonstrates the precision tradeoff we're making
      const veryLargeNumber = 1.2345678901234567e100;

      const instance = new HyperbolaPayoutCurvePiece();
      instance.usePositivePiece = false;
      instance.translateOutcome = F64.fromNumber(veryLargeNumber);
      instance.translatePayout = F64.fromNumber(0);
      instance.a = F64.fromNumber(0);
      instance.b = F64.fromNumber(0);
      instance.c = F64.fromNumber(0);
      instance.d = F64.fromNumber(0);

      // Binary serialization preserves exact representation
      const binarySerialized = instance.serialize();
      const binaryDeserialized = PayoutCurvePiece.deserialize(
        binarySerialized,
      ) as HyperbolaPayoutCurvePiece;

      expect(
        binaryDeserialized.translateOutcome.equals(instance.translateOutcome),
      ).to.be.true;

      // JSON serialization may lose precision for very large numbers
      const json = instance.toJSON();
      const jsonRestored = HyperbolaPayoutCurvePiece.fromJSON(
        json.hyperbolaPayoutCurvePiece,
      );

      // This may not be exactly equal due to JSON number precision limits
      const jsonValue = jsonRestored.translateOutcome.toNumber();

      // The important thing is that both are very large numbers in the same ballpark
      expect(jsonValue).to.be.greaterThan(1e99);
    });
  });
});
