/* eslint-disable @typescript-eslint/no-explicit-any */
/* tslint:disable: no-unused-expression */
/* tslint:disable: no-floating-promises */

import { ShortChannelId } from '@node-dlc/common';
import { ILogger } from '@node-dlc/logger';
import { expect } from 'chai';
import Sinon from 'sinon';

import {
  ChannelRangeQuery,
  ChannelRangeQueryState,
} from '../../lib/gossip/ChannelRangeQuery';
import { QueryChannelRangeMessage } from '../../lib/messages/QueryChannelRangeMessage';
import { ReplyChannelRangeMessage } from '../../lib/messages/ReplyChannelRangeMessage';
import { IMessageSender } from '../../lib/Peer';
import { createFakeLogger, createFakePeer } from '../_test-utils';

describe('ChannelRangeQuery', () => {
  let chainHash: Buffer;
  let sut: ChannelRangeQuery;
  let peer: Sinon.SinonStubbedInstance<IMessageSender>;
  let logger: ILogger;

  beforeEach(() => {
    chainHash = Buffer.alloc(32, 1);
    peer = createFakePeer();
    logger = createFakeLogger();
    sut = new ChannelRangeQuery(chainHash, peer, logger);
  });

  describe('.queryRange()', () => {
    describe('with defaults', async () => {
      it('should send query_channel_range', () => {
        sut.queryRange();
        const msg = peer.sendMessage.args[0][0] as QueryChannelRangeMessage;
        expect(msg.firstBlocknum).to.equal(0);
        expect(msg.numberOfBlocks).to.equal(4294967295);
      });

      it('should be in active state', () => {
        sut.queryRange();
        expect(sut.state).to.equal(ChannelRangeQueryState.Active);
      });
    });

    describe('with options', () => {
      it('should send query_channel_range', () => {
        sut.queryRange(1000000, 1000);
        const msg = peer.sendMessage.args[0][0] as QueryChannelRangeMessage;
        expect(msg.firstBlocknum).to.equal(1000000);
        expect(msg.numberOfBlocks).to.equal(1000);
      });

      it('should be in active state', () => {
        sut.queryRange(1000000, 1000);
        expect(sut.state).to.equal(ChannelRangeQueryState.Active);
      });
    });

    describe('with overflow', () => {
      it('should send query_channel_range', () => {
        sut.queryRange(1000);
        const msg = peer.sendMessage.args[0][0] as QueryChannelRangeMessage;
        expect(msg.firstBlocknum).to.equal(1000);
        expect(msg.numberOfBlocks).to.equal(4294966295);
      });

      it('should be in active state', () => {
        sut.queryRange(1000);
        expect(sut.state).to.equal(ChannelRangeQueryState.Active);
      });
    });
  });

  describe('error reply', () => {
    let promise: Promise<ShortChannelId[]>;

    beforeEach(() => {
      promise = sut.queryRange(0, 1000);

      const msg = new ReplyChannelRangeMessage();
      msg.fullInformation = false;
      msg.firstBlocknum = 0;
      msg.numberOfBlocks = 1000;

      sut.handleReplyChannelRange(msg);
    });

    it('should reject with error', (done) => {
      promise.catch(() => done());
    });

    it('transitions to failed', () => {
      promise.catch(() => {
        expect(sut.state).to.equal(ChannelRangeQueryState.Failed);
      });
    });
  });

  describe('multiple errors', () => {
    it('should reject with single error', (done) => {
      const promise = sut.queryRange(0, 1000);

      const msg = new ReplyChannelRangeMessage();
      msg.fullInformation = false;
      msg.firstBlocknum = 0;
      msg.numberOfBlocks = 1000;
      sut.handleReplyChannelRange(msg);
      sut.handleReplyChannelRange(msg);
      promise.catch(() => done());
    });
  });

  describe('multiple success', () => {
    it('should resolve with single success', async () => {
      const promise = sut.queryRange(0, 1000);

      const msg = new ReplyChannelRangeMessage();
      msg.fullInformation = true;
      msg.firstBlocknum = 0;
      msg.numberOfBlocks = 1000;
      msg.shortChannelIds.push(new ShortChannelId(1, 1, 1));
      sut.handleReplyChannelRange(msg);
      sut.handleReplyChannelRange(msg);

      await promise;
    });
  });

  describe('single reply', () => {
    let promise: Promise<ShortChannelId[]>;

    beforeEach(() => {
      promise = sut.queryRange(0, 1000);

      const msg = new ReplyChannelRangeMessage();
      msg.fullInformation = true;
      msg.firstBlocknum = 0;
      msg.numberOfBlocks = 1000;
      msg.shortChannelIds.push(new ShortChannelId(1, 1, 1));
      sut.handleReplyChannelRange(msg);
    });

    it('should returns scids', async () => {
      const scids = await promise;
      expect(scids).to.deep.equal([new ShortChannelId(1, 1, 1)]);
    });

    it('transitions to complete', async () => {
      await promise;
      expect(sut.state).to.equal(ChannelRangeQueryState.Complete);
    });
  });

  describe('multi reply incomplete', () => {
    beforeEach(() => {
      sut.queryRange(0, 1000);

      const msg = new ReplyChannelRangeMessage();
      msg.fullInformation = true;
      msg.firstBlocknum = 0;
      msg.numberOfBlocks = 500;
      msg.shortChannelIds.push(new ShortChannelId(1, 1, 1));
      sut.handleReplyChannelRange(msg);
    });

    it('should enqueue scids', () => {
      expect(sut.results).to.deep.equal([new ShortChannelId(1, 1, 1)]);
    });

    it('stays in active state', () => {
      expect(sut.state).to.equal(ChannelRangeQueryState.Active);
    });
  });

  describe('multi reply complete', () => {
    let promise: Promise<ShortChannelId[]>;

    beforeEach(() => {
      promise = sut.queryRange(0, 1000);

      const msg = new ReplyChannelRangeMessage();
      msg.fullInformation = true;
      msg.firstBlocknum = 0;
      msg.numberOfBlocks = 500;
      msg.shortChannelIds.push(new ShortChannelId(1, 1, 1));
      sut.handleReplyChannelRange(msg);

      const msg2 = new ReplyChannelRangeMessage();
      msg2.fullInformation = true;
      msg2.firstBlocknum = 500;
      msg2.numberOfBlocks = 500;
      msg2.shortChannelIds.push(new ShortChannelId(500, 1, 1));
      sut.handleReplyChannelRange(msg2);
    });

    it('should enqueue scids', async () => {
      const scids = await promise;
      expect(scids).to.deep.equal([
        new ShortChannelId(1, 1, 1),
        new ShortChannelId(500, 1, 1),
      ]);
    });

    it('state should be complete', async () => {
      await promise;
      expect(sut.state).to.equal(ChannelRangeQueryState.Complete);
    });
  });

  describe('detect legacy', () => {
    describe('full_info=false, with_scids=true', () => {
      beforeEach(() => {
        sut.queryRange(0, 4294967295);
        (sut as any)._isLegacy = true;
        const msg = new ReplyChannelRangeMessage();
        msg.fullInformation = false;
        msg.shortChannelIds.push(new ShortChannelId(1, 1, 1));
        msg.firstBlocknum = 1000;
        msg.numberOfBlocks = 8000;
        sut.handleReplyChannelRange(msg);
      });

      it('should be active', async () => {
        expect(sut.state).to.equal(ChannelRangeQueryState.Active);
      });

      it('should be legacy', async () => {
        expect(sut.isLegacy).to.be.true;
      });
    });
  });

  describe('when isLegacy', () => {
    let promise: Promise<ShortChannelId[]>;

    beforeEach(() => {
      (sut as any)._isLegacy = true;
    });

    describe('full_info=true, with_scids=true', () => {
      beforeEach(() => {
        promise = sut.queryRange(0, 4294967295);
        const msg = new ReplyChannelRangeMessage();
        msg.fullInformation = true;
        msg.shortChannelIds.push(new ShortChannelId(1, 1, 1));
        msg.firstBlocknum = 1000;
        msg.numberOfBlocks = 8000;
        sut.handleReplyChannelRange(msg);
      });

      it('should emit complete', async () => {
        const scids = await promise;
        expect(scids).to.deep.equal([new ShortChannelId(1, 1, 1)]);
      });

      it('should be in complete state', async () => {
        await promise;
        expect(sut.state).to.equal(ChannelRangeQueryState.Complete);
      });
    });

    describe('full_info=true, with_scids=false', () => {
      beforeEach(() => {
        promise = sut.queryRange(0, 4294967295);
        const msg = new ReplyChannelRangeMessage();
        msg.fullInformation = true;
        msg.firstBlocknum = 1000;
        msg.numberOfBlocks = 8000;
        msg.shortChannelIds = [];
        sut.handleReplyChannelRange(msg);
      });

      it('should error', (done) => {
        promise.catch(() => done());
      });

      it('should be in failed state', (done) => {
        promise.catch(() => {
          expect(sut.state).to.equal(ChannelRangeQueryState.Failed);
          done();
        });
      });
    });

    describe('full_info=false, with_scids=true', () => {
      beforeEach(() => {
        promise = sut.queryRange(0, 4294967295);
        const msg = new ReplyChannelRangeMessage();
        msg.fullInformation = false;
        msg.firstBlocknum = 1000;
        msg.numberOfBlocks = 8000;
        msg.shortChannelIds.push(new ShortChannelId(1, 1, 1));
        sut.handleReplyChannelRange(msg);
      });

      it('should enqueue results', () => {
        expect(sut.results).to.deep.equal([new ShortChannelId(1, 1, 1)]);
      });

      it('should still be active', () => {
        expect(sut.state).to.equal(ChannelRangeQueryState.Active);
      });
    });
  });
});
