// tslint:disable: no-unused-expression
import { BitField, Value } from "@node-lightning/core";
import { ShortChannelId } from "@node-lightning/core";
import { OutPoint } from "@node-lightning/core";
import { AddressIPv4 } from "@node-lightning/wire";
import { ExtendedChannelAnnouncementMessage } from "@node-lightning/wire";
import { IGossipEmitter } from "@node-lightning/wire";
import { ChannelAnnouncementMessage } from "@node-lightning/wire";
import { ChannelUpdateMessage } from "@node-lightning/wire";
import { NodeAnnouncementMessage } from "@node-lightning/wire";
import { expect } from "chai";
import { EventEmitter } from "events";
import { Channel } from "../lib/channel";
import { ChannelSettings } from "../lib/channel-settings";
import { GraphErrorCode } from "../lib/graph-error";
import { GraphManager } from "../lib/graph-manager";
import { Node } from "../lib/node";

class FakeGossipEmitter extends EventEmitter implements IGossipEmitter {}

describe("GraphManager", () => {
    let sut: GraphManager;
    let gossipEmitter: FakeGossipEmitter;
    const scid = new ShortChannelId(1, 1, 1);
    const node1 = Buffer.alloc(32, 1);
    const node2 = Buffer.alloc(32, 2);

    beforeEach(() => {
        gossipEmitter = new FakeGossipEmitter();
        sut = new GraphManager(gossipEmitter);
    });

    describe("channel_announcement", () => {
        describe("new channel", () => {
            function createMsg() {
                const msg = new ChannelAnnouncementMessage();
                msg.shortChannelId = scid;
                msg.nodeId1 = node1;
                msg.nodeId2 = node2;
                msg.features = new BitField();
                return msg;
            }

            it("should add a channel to the graph", done => {
                const msg = createMsg();

                sut.on("channel", () => {
                    const chan = sut.graph.getChannel(scid);
                    expect(chan.shortChannelId).to.be.instanceof(ShortChannelId);
                    done();
                });
                gossipEmitter.emit("message", msg);
            });

            it("should add node1 and node2 to graph", done => {
                const msg = createMsg();
                let hasNode1 = false;
                let hasNode2 = false;

                sut.on("node", n => {
                    if (n.nodeId.equals(node1)) hasNode1 = true;
                    if (n.nodeId.equals(node2)) hasNode2 = true;
                    if (!hasNode1 || !hasNode2) return;
                    const n1 = sut.graph.getNode(node1);
                    const n2 = sut.graph.getNode(node2);
                    expect(n1.nodeId.toString("hex")).to.equal(node1.toString("hex"));
                    expect(n2.nodeId.toString("hex")).to.equal(node2.toString("hex"));
                    done();
                });
                gossipEmitter.emit("message", msg);
            });
        });

        describe("duplicate channel", () => {
            function createMsg() {
                const msg = new ChannelAnnouncementMessage();
                msg.shortChannelId = scid;
                msg.nodeId1 = node1;
                msg.nodeId2 = node2;
                msg.features = new BitField();
                return msg;
            }

            it("should not emit error", () => {
                const msg = createMsg();
                gossipEmitter.emit("message", msg);
                gossipEmitter.emit("message", msg);
            });
        });
    });

    describe("channel_update", () => {
        function createMsg() {
            const msg = new ChannelAnnouncementMessage();
            msg.shortChannelId = scid;
            msg.nodeId1 = node1;
            msg.nodeId2 = node2;
            msg.features = new BitField();
            return msg;
        }

        function createUpdateMsg(dir: number) {
            const msg = new ChannelUpdateMessage();
            msg.shortChannelId = scid;
            msg.channelFlags = new BitField(BigInt(dir));
            return msg;
        }

        it("should emit channel_update for side 1", () => {
            sut.on("channel_update", (c: Channel, u: ChannelSettings) => {
                expect(c.shortChannelId).to.deep.equal(scid);
                expect(u.direction).to.equal(0);

                const chan = sut.graph.getChannel(scid);
                expect(chan.node1Settings).to.equal(u);
                expect(chan.node2Settings).to.be.undefined;
            });

            gossipEmitter.emit("message", createMsg());
            gossipEmitter.emit("message", createUpdateMsg(0));
        });

        it("should emit channel_update for side 2", () => {
            sut.on("channel_update", (c: Channel, u: ChannelSettings) => {
                expect(c.shortChannelId).to.deep.equal(scid);
                expect(u.direction).to.equal(1);

                const chan = sut.graph.getChannel(scid);

                expect(chan.node1Settings).to.be.undefined;
                expect(chan.node2Settings).to.equal(u);
            });

            gossipEmitter.emit("message", createMsg());
            gossipEmitter.emit("message", createUpdateMsg(1));
        });

        it("should emit error when channel doesnt exist", done => {
            sut.on("error", err => {
                expect(err.code).to.equal(GraphErrorCode.ChannelNotFound);
                done();
            });
            gossipEmitter.emit("message", createUpdateMsg(0));
        });
    });

    describe("node_announcement", () => {
        function createMsg() {
            const msg = new NodeAnnouncementMessage();
            msg.nodeId = node1;
            msg.features = new BitField();
            msg.timestamp = 1;
            return msg;
        }

        it("should add new node", done => {
            sut.on("node", (n: Node) => {
                expect(n.nodeId.equals(node1)).to.be.true;
                const node = sut.graph.getNode(node1);
                expect(node).to.equal(n);
                done();
            });
            gossipEmitter.emit("message", createMsg());
        });

        it("should update existing node", done => {
            gossipEmitter.emit("message", createMsg());
            sut.on("node", (n: Node) => {
                expect(n.lastUpdate).to.equal(2);
                expect(n.rgbColorString).to.equal("#111111");
                expect(n.aliasString).to.equal("test");
                expect(n.addresses[0]).to.deep.equal({ host: "1.1.1.1", port: 9735 });
                const node = sut.graph.getNode(node1);
                expect(node).to.equal(n);
                done();
            });
            const msg = createMsg();
            msg.timestamp = 2;
            msg.alias = Buffer.from("test");
            msg.rgbColor = Buffer.from("111111", "hex");
            msg.features = new BitField(BigInt(2));
            msg.addresses = [new AddressIPv4("1.1.1.1", 9735)];
            gossipEmitter.emit("message", msg);
        });
    });

    describe("close channel", () => {
        function createMsg(scid: ShortChannelId, outpoint?: OutPoint) {
            const msg = new ExtendedChannelAnnouncementMessage();
            msg.shortChannelId = scid;
            msg.outpoint = outpoint;
            msg.capacity = BigInt(1000);
            msg.nodeId1 = node1;
            msg.nodeId2 = node2;
            msg.features = new BitField();
            return msg;
        }

        it("should remove a channel from the graph", () => {
            const outpoint = OutPoint.fromString("1111111111111111111111111111111111111111111111111111111111111111:1"); // prettier-ignore
            gossipEmitter.emit("message", createMsg(new ShortChannelId(1, 1, 0)));
            gossipEmitter.emit("message", createMsg(new ShortChannelId(1, 1, 1), outpoint));
            expect(sut.graph.channels.size).to.equal(2);
            sut.removeChannel(outpoint);
            expect(sut.graph.channels.size).to.equal(1);
        });
    });
});
