import { expect } from 'chai';

import {
  LOCKTIME_THRESHOLD,
  MessageType,
  OrderOfferContainer,
  OrderPositionInfoV0,
} from '../../lib';
import { ContractInfo } from '../../lib/messages/ContractInfo';
import {
  IOrderIrcInfoJSON,
  OrderIrcInfoV0,
} from '../../lib/messages/OrderIrcInfo';
import {
  IOrderMetadataJSON,
  OrderMetadataV0,
} from '../../lib/messages/OrderMetadata';
import { OrderOfferV0 } from '../../lib/messages/OrderOffer';

describe('OrderOffer', () => {
  const chainHash = Buffer.from(
    '06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f',
    'hex',
  );

  const buf = Buffer.from(
    "f532" + // type
    "06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f" + // chain_hash
    "fdd82e" + // type contract_info
    "fd0131" + // length
    "000000000bebc200" + // total_collateral
    "fda710" + // type contract_descriptor
    "79" + // length
    "03" + // num_outcomes
    "c5a7affd51901bc7a51829b320d588dc7af0ad1f3d56f20a1d3c60c9ba7c6722" + // outcome_1
    "0000000000000000" + // payout_1
    "adf1c23fbeed6611efa5caa0e9ed4c440c450a18bc010a6c867e05873ac08ead" + // outcome_2
    "00000000092363a3" + // payout_2
    "6922250552ad6bb10ab3ddd6981b530aa9a6fd05725bf85b59e3e51163905288" + // outcome_3
    "000000000bebc200" + // payout_3
    "fda712" + // type oracle_info
    "a8" + // length
    "fdd824" + // type oracle_announcement
    "a4" + // length
    "fab22628f6e2602e1671c286a2f63a9246794008627a1749639217f4214cb4a9" + // announcement_signature_r
    "494c93d1a852221080f44f697adb4355df59eb339f6ba0f9b01ba661a8b108d4" + // announcement_signature_s
    "da078bbb1d34e7729e38e2ae34236e776da121af442626fa31e31ae55a279a0b" + // oracle_public_key
    "fdd822" + // type oracle_event
    "40" + // length
    "0001" + // nb_nonces
    "3cfba011378411b20a5ab773cb95daab93e9bcd1e4cce44986a7dda84e01841b" + // oracle_nonces
    "00000000" + // event_maturity_epoch
    "fdd806" + // type enum_event_descriptor
    "10" + // length
    "0002" + // num_outcomes
    "06" + // outcome_1_len
    "64756d6d7931" + // outcome_1
    "06" + // outcome_2_len
    "64756d6d7932" + // outcome_2
    "05" + // event_id_length
    "64756d6d79" + // event_id
    "0000000005f5e100" + // total_collateral_satoshis
    "0000000000000001" + // fee_rate_per_vb
    "00000064" + // cet_locktime
    "000000c8" // refund_locktime
    , "hex"
  ); // prettier-ignore

  describe('serialize', () => {
    const instance = new OrderOfferV0();

    instance.chainHash = chainHash;

    instance.contractInfo = ContractInfo.deserialize(
      Buffer.from(
        'fdd82e' + // type contract_info
          'fd0131' + // length
          '000000000bebc200' + // total_collateral
          'fda710' + // type contract_descriptor
          '79' + // length
          '03' + // num_outcomes
          'c5a7affd51901bc7a51829b320d588dc7af0ad1f3d56f20a1d3c60c9ba7c6722' + // outcome_1
          '0000000000000000' + // payout_1
          'adf1c23fbeed6611efa5caa0e9ed4c440c450a18bc010a6c867e05873ac08ead' + // outcome_2
          '00000000092363a3' + // payout_2
          '6922250552ad6bb10ab3ddd6981b530aa9a6fd05725bf85b59e3e51163905288' + // outcome_3
          '000000000bebc200' + // payout_3
          'fda712' + // type oracle_info
          'a8' + // length
          'fdd824' + // type oracle_announcement
          'a4' + // length
          'fab22628f6e2602e1671c286a2f63a9246794008627a1749639217f4214cb4a9' + // announcement_signature_r
          '494c93d1a852221080f44f697adb4355df59eb339f6ba0f9b01ba661a8b108d4' + // announcement_signature_s
          'da078bbb1d34e7729e38e2ae34236e776da121af442626fa31e31ae55a279a0b' + // oracle_public_key
          'fdd822' + // type oracle_event
          '40' + // length
          '0001' + // nb_nonces
          '3cfba011378411b20a5ab773cb95daab93e9bcd1e4cce44986a7dda84e01841b' + // oracle_nonces
          '00000000' + // event_maturity_epoch
          'fdd806' + // type enum_event_descriptor
          '10' + // length
          '0002' + // num_outcomes
          '06' + // outcome_1_len
          '64756d6d7931' + // outcome_1
          '06' + // outcome_2_len
          '64756d6d7932' + // outcome_2
          '05' + // event_id_length
          '64756d6d79', // event_id
        'hex',
      ),
    );

    instance.offerCollateralSatoshis = BigInt(100000000);
    instance.feeRatePerVb = BigInt(1);
    instance.cetLocktime = 100;
    instance.refundLocktime = 200;

    it('serializes', () => {
      expect(instance.serialize().toString("hex")).to.equal(
        "f532" + // type
        "06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f" + // chain_hash
        "fdd82e" + // type contract_info
        "fd0131" + // length
        "000000000bebc200" + // total_collateral
        "fda710" + // type contract_descriptor
        "79" + // length
        "03" + // num_outcomes
        "c5a7affd51901bc7a51829b320d588dc7af0ad1f3d56f20a1d3c60c9ba7c6722" + // outcome_1
        "0000000000000000" + // payout_1
        "adf1c23fbeed6611efa5caa0e9ed4c440c450a18bc010a6c867e05873ac08ead" + // outcome_2
        "00000000092363a3" + // payout_2
        "6922250552ad6bb10ab3ddd6981b530aa9a6fd05725bf85b59e3e51163905288" + // outcome_3
        "000000000bebc200" + // payout_3
        "fda712" + // type oracle_info
        "a8" + // length
        "fdd824" + // type oracle_announcement
        "a4" + // length
        "fab22628f6e2602e1671c286a2f63a9246794008627a1749639217f4214cb4a9" + // announcement_signature_r
        "494c93d1a852221080f44f697adb4355df59eb339f6ba0f9b01ba661a8b108d4" + // announcement_signature_s
        "da078bbb1d34e7729e38e2ae34236e776da121af442626fa31e31ae55a279a0b" + // oracle_public_key
        "fdd822" + // type oracle_event
        "40" + // length
        "0001" + // nb_nonces
        "3cfba011378411b20a5ab773cb95daab93e9bcd1e4cce44986a7dda84e01841b" + // oracle_nonces
        "00000000" + // event_maturity_epoch
        "fdd806" + // type enum_event_descriptor
        "10" + // length
        "0002" + // num_outcomes
        "06" + // outcome_1_len
        "64756d6d7931" + // outcome_1
        "06" + // outcome_2_len
        "64756d6d7932" + // outcome_2
        "05" + // event_id_length
        "64756d6d79" + // event_id
        "0000000005f5e100" + // total_collateral_satoshis
        "0000000000000001" + // fee_rate_per_vb
        "00000064" + // cet_locktime
        "000000c8" // refund_locktime
      ); // prettier-ignore
    });

    it('serializes with metadata', () => {
      const metadata = new OrderMetadataV0();
      metadata.offerId = 'strategy-88';
      metadata.createdAt = 1635868041;
      metadata.goodTill = 1635868041;

      instance.metadata = metadata;

      expect(instance.serialize().toString('hex')).to.equal(
        "f532" + // type
        "06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f" + // chain_hash
        "fdd82e" + // type contract_info
        "fd0131" + // length
        "000000000bebc200" + // total_collateral
        "fda710" + // type contract_descriptor
        "79" + // length
        "03" + // num_outcomes
        "c5a7affd51901bc7a51829b320d588dc7af0ad1f3d56f20a1d3c60c9ba7c6722" + // outcome_1
        "0000000000000000" + // payout_1
        "adf1c23fbeed6611efa5caa0e9ed4c440c450a18bc010a6c867e05873ac08ead" + // outcome_2
        "00000000092363a3" + // payout_2
        "6922250552ad6bb10ab3ddd6981b530aa9a6fd05725bf85b59e3e51163905288" + // outcome_3
        "000000000bebc200" + // payout_3
        "fda712" + // type oracle_info
        "a8" + // length
        "fdd824" + // type oracle_announcement
        "a4" + // length
        "fab22628f6e2602e1671c286a2f63a9246794008627a1749639217f4214cb4a9" + // announcement_signature_r
        "494c93d1a852221080f44f697adb4355df59eb339f6ba0f9b01ba661a8b108d4" + // announcement_signature_s
        "da078bbb1d34e7729e38e2ae34236e776da121af442626fa31e31ae55a279a0b" + // oracle_public_key
        "fdd822" + // type oracle_event
        "40" + // length
        "0001" + // nb_nonces
        "3cfba011378411b20a5ab773cb95daab93e9bcd1e4cce44986a7dda84e01841b" + // oracle_nonces
        "00000000" + // event_maturity_epoch
        "fdd806" + // type enum_event_descriptor
        "10" + // length
        "0002" + // num_outcomes
        "06" + // outcome_1_len
        "64756d6d7931" + // outcome_1
        "06" + // outcome_2_len
        "64756d6d7932" + // outcome_2
        "05" + // event_id_length
        "64756d6d79" + // event_id
        "0000000005f5e100" + // total_collateral_satoshis
        "0000000000000001" + // fee_rate_per_vb
        "00000064" + // cet_locktime
        "000000c8" + // refund_locktime
        "fdf536140b73747261746567792d383861815d8961815d89" // order_metadata_v0 tlv
      ); // prettier-ignore
    });

    it('serializes with ircinfo', () => {
      const ircInfo = new OrderIrcInfoV0();
      ircInfo.nick = 'A0F5k4H9C56xgbRF';
      ircInfo.pubKey = Buffer.from(
        '022d40fdc0db01b85bb4de6fe181c093b69c3a4558b7fc98a22e289b9d8da1d6f3',
        'hex',
      );

      instance.ircInfo = ircInfo;

      expect(instance.serialize().toString('hex')).to.equal(
        "f532" + // type
        "06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f" + // chain_hash
        "fdd82e" + // type contract_info
        "fd0131" + // length
        "000000000bebc200" + // total_collateral
        "fda710" + // type contract_descriptor
        "79" + // length
        "03" + // num_outcomes
        "c5a7affd51901bc7a51829b320d588dc7af0ad1f3d56f20a1d3c60c9ba7c6722" + // outcome_1
        "0000000000000000" + // payout_1
        "adf1c23fbeed6611efa5caa0e9ed4c440c450a18bc010a6c867e05873ac08ead" + // outcome_2
        "00000000092363a3" + // payout_2
        "6922250552ad6bb10ab3ddd6981b530aa9a6fd05725bf85b59e3e51163905288" + // outcome_3
        "000000000bebc200" + // payout_3
        "fda712" + // type oracle_info
        "a8" + // length
        "fdd824" + // type oracle_announcement
        "a4" + // length
        "fab22628f6e2602e1671c286a2f63a9246794008627a1749639217f4214cb4a9" + // announcement_signature_r
        "494c93d1a852221080f44f697adb4355df59eb339f6ba0f9b01ba661a8b108d4" + // announcement_signature_s
        "da078bbb1d34e7729e38e2ae34236e776da121af442626fa31e31ae55a279a0b" + // oracle_public_key
        "fdd822" + // type oracle_event
        "40" + // length
        "0001" + // nb_nonces
        "3cfba011378411b20a5ab773cb95daab93e9bcd1e4cce44986a7dda84e01841b" + // oracle_nonces
        "00000000" + // event_maturity_epoch
        "fdd806" + // type enum_event_descriptor
        "10" + // length
        "0002" + // num_outcomes
        "06" + // outcome_1_len
        "64756d6d7931" + // outcome_1
        "06" + // outcome_2_len
        "64756d6d7932" + // outcome_2
        "05" + // event_id_length
        "64756d6d79" + // event_id
        "0000000005f5e100" + // total_collateral_satoshis
        "0000000000000001" + // fee_rate_per_vb
        "00000064" + // cet_locktime
        "000000c8" + // refund_locktime
        "fdf536140b73747261746567792d383861815d8961815d89fdf5383210413046356b3448394335367867625246022d40fdc0db01b85bb4de6fe181c093b69c3a4558b7fc98a22e289b9d8da1d6f3" // order_irc_info_v0 tlv
      ); // prettier-ignore
    });

    it('serializes with cso info', () => {
      const positionInfo = new OrderPositionInfoV0();
      positionInfo.shiftForFees = 'offeror';
      positionInfo.fees = BigInt(10000);

      instance.positionInfo = positionInfo;

      expect(instance.serialize().toString('hex')).to.equal(
        'f532' + // type
          '06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f' + // chain_hash
          'fdd82e' + // type contract_info
          'fd0131' + // length
          '000000000bebc200' + // total_collateral
          'fda710' + // type contract_descriptor
          '79' + // length
          '03' + // num_outcomes
          'c5a7affd51901bc7a51829b320d588dc7af0ad1f3d56f20a1d3c60c9ba7c6722' + // outcome_1
          '0000000000000000' + // payout_1
          'adf1c23fbeed6611efa5caa0e9ed4c440c450a18bc010a6c867e05873ac08ead' + // outcome_2
          '00000000092363a3' + // payout_2
          '6922250552ad6bb10ab3ddd6981b530aa9a6fd05725bf85b59e3e51163905288' + // outcome_3
          '000000000bebc200' + // payout_3
          'fda712' + // type oracle_info
          'a8' + // length
          'fdd824' + // type oracle_announcement
          'a4' + // length
          'fab22628f6e2602e1671c286a2f63a9246794008627a1749639217f4214cb4a9' + // announcement_signature_r
          '494c93d1a852221080f44f697adb4355df59eb339f6ba0f9b01ba661a8b108d4' + // announcement_signature_s
          'da078bbb1d34e7729e38e2ae34236e776da121af442626fa31e31ae55a279a0b' + // oracle_public_key
          'fdd822' + // type oracle_event
          '40' + // length
          '0001' + // nb_nonces
          '3cfba011378411b20a5ab773cb95daab93e9bcd1e4cce44986a7dda84e01841b' + // oracle_nonces
          '00000000' + // event_maturity_epoch
          'fdd806' + // type enum_event_descriptor
          '10' + // length
          '0002' + // num_outcomes
          '06' + // outcome_1_len
          '64756d6d7931' + // outcome_1
          '06' + // outcome_2_len
          '64756d6d7932' + // outcome_2
          '05' + // event_id_length
          '64756d6d79' + // event_id
          '0000000005f5e100' + // total_collateral_satoshis
          '0000000000000001' + // fee_rate_per_vb
          '00000064' + // cet_locktime
          '000000c8' + // refund_locktime
          'fdf536140b73747261746567792d383861815d8961815d89' +
          'fdf5383210413046356b3448394335367867625246022d40fdc0db01b85bb4de6fe181c093b69c3a4558b7fc98a22e289b9d8da1d6f3' +
          'fdf53a09010000000000002710', // order_irc_info_v0 tlv
      );
    });
  });

  describe('deserialize', () => {
    it('deserializes', () => {
      const instance = OrderOfferV0.deserialize(buf);

      expect(instance.chainHash).to.deep.equal(chainHash);
      expect(instance.contractInfo.serialize().toString('hex')).to.equal(
        'fdd82e' + // type contract_info
          'fd0131' + // length
          '000000000bebc200' + // total_collateral
          'fda710' + // type contract_descriptor
          '79' + // length
          '03' + // num_outcomes
          'c5a7affd51901bc7a51829b320d588dc7af0ad1f3d56f20a1d3c60c9ba7c6722' + // outcome_1
          '0000000000000000' + // payout_1
          'adf1c23fbeed6611efa5caa0e9ed4c440c450a18bc010a6c867e05873ac08ead' + // outcome_2
          '00000000092363a3' + // payout_2
          '6922250552ad6bb10ab3ddd6981b530aa9a6fd05725bf85b59e3e51163905288' + // outcome_3
          '000000000bebc200' + // payout_3
          'fda712' + // type oracle_info
          'a8' + // length
          'fdd824' + // type oracle_announcement
          'a4' + // length
          'fab22628f6e2602e1671c286a2f63a9246794008627a1749639217f4214cb4a9' + // announcement_signature_r
          '494c93d1a852221080f44f697adb4355df59eb339f6ba0f9b01ba661a8b108d4' + // announcement_signature_s
          'da078bbb1d34e7729e38e2ae34236e776da121af442626fa31e31ae55a279a0b' + // oracle_public_key
          'fdd822' + // type oracle_event
          '40' + // length
          '0001' + // nb_nonces
          '3cfba011378411b20a5ab773cb95daab93e9bcd1e4cce44986a7dda84e01841b' + // oracle_nonces
          '00000000' + // event_maturity_epoch
          'fdd806' + // type enum_event_descriptor
          '10' + // length
          '0002' + // num_outcomes
          '06' + // outcome_1_len
          '64756d6d7931' + // outcome_1
          '06' + // outcome_2_len
          '64756d6d7932' + // outcome_2
          '05' + // event_id_length
          '64756d6d79', // event_id
      );
      expect(Number(instance.offerCollateralSatoshis)).to.equal(100000000);
      expect(Number(instance.feeRatePerVb)).to.equal(1);
      expect(instance.cetLocktime).to.equal(100);
      expect(instance.refundLocktime).to.equal(200);
    });

    it('deserializes with metadata', () => {
      const bufWithMetadata = Buffer.concat([
        buf,
        Buffer.from('fdf5360c0b73747261746567792d3838', 'hex'),
      ]);

      const instance = OrderOfferV0.deserialize(bufWithMetadata);

      expect((instance.metadata as OrderMetadataV0).offerId).to.equal(
        'strategy-88',
      );
    });

    it('deserializes with positioninfo', () => {
      const bufWithPositionInfo = Buffer.concat([
        buf,
        Buffer.from('fdf53a09010000000000002710', 'hex'),
      ]);

      const instance = OrderOfferV0.deserialize(bufWithPositionInfo);

      expect(
        (instance.positionInfo as OrderPositionInfoV0).shiftForFees,
      ).to.equal('offeror');
      expect((instance.positionInfo as OrderPositionInfoV0).fees).to.equal(
        BigInt(10000),
      );
    });
  });

  describe('toJSON', () => {
    it('converts to JSON with metadata', async () => {
      const bufWithMetadata = Buffer.concat([
        buf,
        Buffer.from('fdf5360c0b73747261746567792d3838', 'hex'),
      ]);

      const instance = OrderOfferV0.deserialize(bufWithMetadata);

      const json = instance.toJSON();
      expect(json.type).to.equal(instance.type);
      expect(json.chainHash).to.equal(instance.chainHash.toString('hex'));
      expect(json.contractInfo.type).to.equal(instance.contractInfo.type);
      expect(json.contractInfo.totalCollateral).to.equal(
        Number(instance.contractInfo.totalCollateral),
      );
      expect(json.feeRatePerVb).to.equal(Number(instance.feeRatePerVb));
      expect(json.cetLocktime).to.equal(instance.cetLocktime);
      expect(json.refundLocktime).to.equal(instance.refundLocktime);
      expect(json.tlvs[0].type === MessageType.OrderMetadataV0).to.equal(true);
      expect((json.tlvs[0] as IOrderMetadataJSON).offerId).to.equal(
        (instance.metadata as OrderMetadataV0).offerId,
      );
    });

    it('converts to JSON with ircinfo', async () => {
      const bufWithIrcInfo = Buffer.concat([
        buf,
        Buffer.from(
          'fdf536140b73747261746567792d383861815d8961815d89fdf5383210413046356b3448394335367867625246022d40fdc0db01b85bb4de6fe181c093b69c3a4558b7fc98a22e289b9d8da1d6f3',
          'hex',
        ),
      ]);

      const instance = OrderOfferV0.deserialize(bufWithIrcInfo);

      const json = instance.toJSON();
      const i = json.tlvs.findIndex(
        (tlv) => tlv.type === MessageType.OrderIrcInfoV0,
      );
      expect(json.tlvs[i].type === MessageType.OrderIrcInfoV0).to.equal(true);
      expect((json.tlvs[i] as IOrderIrcInfoJSON).nick).to.equal(
        (instance.ircInfo as OrderIrcInfoV0).nick,
      );
    });
  });

  describe('validate', () => {
    let instance: OrderOfferV0;
    beforeEach(() => {
      instance = OrderOfferV0.deserialize(buf);
    });

    it('should throw if offerCollateralSatoshis is less than 1000', () => {
      instance.offerCollateralSatoshis = BigInt(999);
      expect(function () {
        instance.validate();
      }).to.throw(
        'offer_collateral_satoshis must be greater than or equal to 1000',
      );

      // boundary check
      instance.offerCollateralSatoshis = BigInt(1000);
      expect(function () {
        instance.validate();
      }).to.not.throw();
    });

    it('should throw if cet_locktime is less than 0', () => {
      instance.cetLocktime = -1;
      expect(() => {
        instance.validate();
      }).to.throw('cet_locktime must be greater than or equal to 0');
    });

    it('should throw if refund_locktime is less than 0', () => {
      instance.refundLocktime = -1;
      expect(() => {
        instance.validate();
      }).to.throw('refund_locktime must be greater than or equal to 0');
    });

    it('should throw if cet_locktime and refund_locktime are not in same units', () => {
      instance.cetLocktime = 100;
      instance.refundLocktime = LOCKTIME_THRESHOLD + 200;
      expect(function () {
        instance.validate();
      }).to.throw(Error);
    });

    it('should not throw if cet_locktime and refund_locktime are in same units', () => {
      instance.cetLocktime = 100;
      instance.refundLocktime = 200;
      expect(function () {
        instance.validate();
      }).to.not.throw(Error);

      instance.cetLocktime = LOCKTIME_THRESHOLD + 100;
      instance.refundLocktime = LOCKTIME_THRESHOLD + 200;
      expect(function () {
        instance.validate();
      }).to.not.throw(Error);
    });

    it('should throw if cet_locktime >= refund_locktime', () => {
      instance.cetLocktime = 200;
      instance.refundLocktime = 100;
      expect(function () {
        instance.validate();
      }).to.throw(Error);

      instance.cetLocktime = 100;
      instance.refundLocktime = 100;
      expect(function () {
        instance.validate();
      }).to.throw(Error);
    });

    it('should throw if totalCollateral <= offerCollateral', () => {
      instance.contractInfo.totalCollateral = BigInt(200000000);
      instance.offerCollateralSatoshis = BigInt(200000000);
      expect(function () {
        instance.validate();
      }).to.throw(Error);

      instance.contractInfo.totalCollateral = BigInt(200000000);
      instance.offerCollateralSatoshis = BigInt(200000001);
      expect(function () {
        instance.validate();
      }).to.throw(Error);
    });
  });

  describe('OrderOfferContainer', () => {
    it('should serialize and deserialize', () => {
      const orderOffer = OrderOfferV0.deserialize(buf);
      // swap payout and change spk to differentiate between dlcoffers
      const orderOffer2 = OrderOfferV0.deserialize(buf);
      orderOffer2.refundLocktime = 300;

      const container = new OrderOfferContainer();
      container.addOffer(orderOffer);
      container.addOffer(orderOffer2);

      const instance = OrderOfferContainer.deserialize(container.serialize());

      expect(container.serialize()).to.deep.equal(instance.serialize());
    });
  });
});
