import React from 'react';
import { mount, ReactWrapper } from 'enzyme';
import { Room, PendingEventOrdering, MatrixEvent, MatrixClient } from 'matrix-js-sdk/src/matrix';

import * as TestUtils from '../../../test-utils';
import { MatrixClientPeg } from '../../../../src/MatrixClientPeg';
import DMRoomMap from '../../../../src/utils/DMRoomMap';
import RoomHeader from '../../../../src/components/views/rooms/RoomHeader';
import { SearchScope } from '../../../../src/components/views/rooms/SearchBar';
import { E2EStatus } from '../../../../src/utils/ShieldUtils';
import { mkEvent } from '../../../test-utils';
import { IRoomState } from "../../../../src/components/structures/RoomView";
import RoomContext from '../../../../src/contexts/RoomContext';

describe('RoomHeader', () => {
    it('shows the room avatar in a room with only ourselves', () => {
        // When we render a non-DM room with 1 person in it
        const room = createRoom({ name: "X Room", isDm: false, userIds: [] });
        const rendered = render(room);

        // Then the room's avatar is the initial of its name
        const initial = findSpan(rendered, ".mx_BaseAvatar_initial");
        expect(initial.text()).toEqual("X");

        // And there is no image avatar (because it's not set on this room)
        const image = findImg(rendered, ".mx_BaseAvatar_image");
        expect(image.prop("src")).toEqual("data:image/png;base64,00");
    });

    it('shows the room avatar in a room with 2 people', () => {
        // When we render a non-DM room with 2 people in it
        const room = createRoom(
            { name: "Y Room", isDm: false, userIds: ["other"] });
        const rendered = render(room);

        // Then the room's avatar is the initial of its name
        const initial = findSpan(rendered, ".mx_BaseAvatar_initial");
        expect(initial.text()).toEqual("Y");

        // And there is no image avatar (because it's not set on this room)
        const image = findImg(rendered, ".mx_BaseAvatar_image");
        expect(image.prop("src")).toEqual("data:image/png;base64,00");
    });

    it('shows the room avatar in a room with >2 people', () => {
        // When we render a non-DM room with 3 people in it
        const room = createRoom({ name: "Z Room", isDm: false, userIds: ["other1", "other2"] });
        const rendered = render(room);

        // Then the room's avatar is the initial of its name
        const initial = findSpan(rendered, ".mx_BaseAvatar_initial");
        expect(initial.text()).toEqual("Z");

        // And there is no image avatar (because it's not set on this room)
        const image = findImg(rendered, ".mx_BaseAvatar_image");
        expect(image.prop("src")).toEqual("data:image/png;base64,00");
    });

    it('shows the room avatar in a DM with only ourselves', () => {
        // When we render a non-DM room with 1 person in it
        const room = createRoom({ name: "Z Room", isDm: true, userIds: [] });
        const rendered = render(room);

        // Then the room's avatar is the initial of its name
        const initial = findSpan(rendered, ".mx_BaseAvatar_initial");
        expect(initial.text()).toEqual("Z");

        // And there is no image avatar (because it's not set on this room)
        const image = findImg(rendered, ".mx_BaseAvatar_image");
        expect(image.prop("src")).toEqual("data:image/png;base64,00");
    });

    it('shows the user avatar in a DM with 2 people', () => {
        // Note: this is the interesting case - this is the ONLY
        //       time we should use the user's avatar.

        // When we render a DM room with only 2 people in it
        const room = createRoom({ name: "Y Room", isDm: true, userIds: ["other"] });
        const rendered = render(room);

        // Then we use the other user's avatar as our room's image avatar
        const image = findImg(rendered, ".mx_BaseAvatar_image");
        expect(image.prop("src")).toEqual("http://this.is.a.url/example.org/other");

        // And there is no initial avatar
        expect(rendered.find(".mx_BaseAvatar_initial")).toHaveLength(0);
    });

    it('shows the room avatar in a DM with >2 people', () => {
        // When we render a DM room with 3 people in it
        const room = createRoom({
            name: "Z Room", isDm: true, userIds: ["other1", "other2"] });
        const rendered = render(room);

        // Then the room's avatar is the initial of its name
        const initial = findSpan(rendered, ".mx_BaseAvatar_initial");
        expect(initial.text()).toEqual("Z");

        // And there is no image avatar (because it's not set on this room)
        const image = findImg(rendered, ".mx_BaseAvatar_image");
        expect(image.prop("src")).toEqual("data:image/png;base64,00");
    });

    it("renders call buttons normally", () => {
        const room = createRoom({ name: "Room", isDm: false, userIds: [] });
        const wrapper = render(room);

        expect(wrapper.find('[aria-label="Voice call"]').hostNodes()).toHaveLength(1);
        expect(wrapper.find('[aria-label="Video call"]').hostNodes()).toHaveLength(1);
    });

    it("hides call buttons when the room is tombstoned", () => {
        const room = createRoom({ name: "Room", isDm: false, userIds: [] });
        const wrapper = render(room, {
            tombstone: mkEvent({
                event: true,
                type: "m.room.tombstone",
                room: room.roomId,
                user: "@user1:server",
                skey: "",
                content: {},
                ts: Date.now(),
            }),
        });

        expect(wrapper.find('[aria-label="Voice call"]').hostNodes()).toHaveLength(0);
        expect(wrapper.find('[aria-label="Video call"]').hostNodes()).toHaveLength(0);
    });
});

interface IRoomCreationInfo {
    name: string;
    isDm: boolean;
    userIds: string[];
}

function createRoom(info: IRoomCreationInfo) {
    TestUtils.stubClient();
    const client: MatrixClient = MatrixClientPeg.get();

    const roomId = '!1234567890:domain';
    const userId = client.getUserId();
    if (info.isDm) {
        client.getAccountData = (eventType) => {
            expect(eventType).toEqual("m.direct");
            return mkDirectEvent(roomId, userId, info.userIds);
        };
    }

    DMRoomMap.makeShared().start();

    const room = new Room(roomId, client, userId, {
        pendingEventOrdering: PendingEventOrdering.Detached,
    });

    const otherJoinEvents = [];
    for (const otherUserId of info.userIds) {
        otherJoinEvents.push(mkJoinEvent(roomId, otherUserId));
    }

    room.currentState.setStateEvents([
        mkCreationEvent(roomId, userId),
        mkNameEvent(roomId, userId, info.name),
        mkJoinEvent(roomId, userId),
        ...otherJoinEvents,
    ]);
    room.recalculate();

    return room;
}

function render(room: Room, roomContext?: Partial<IRoomState>): ReactWrapper {
    return mount((
        <RoomContext.Provider value={{ ...roomContext, room } as IRoomState}>
            <RoomHeader
                room={room}
                inRoom={true}
                onSearchClick={() => {}}
                onInviteClick={null}
                onForgetClick={() => {}}
                onCallPlaced={(_type) => { }}
                onAppsClick={() => {}}
                e2eStatus={E2EStatus.Normal}
                appsShown={true}
                searchInfo={{
                    searchTerm: "",
                    searchScope: SearchScope.Room,
                    searchCount: 0,
                }}
            />
        </RoomContext.Provider>
    ));
}

function mkCreationEvent(roomId: string, userId: string): MatrixEvent {
    return mkEvent({
        event: true,
        type: "m.room.create",
        room: roomId,
        user: userId,
        content: {
            creator: userId,
            room_version: "5",
            predecessor: {
                room_id: "!prevroom",
                event_id: "$someevent",
            },
        },
    });
}

function mkNameEvent(
    roomId: string, userId: string, name: string,
): MatrixEvent {
    return mkEvent({
        event: true,
        type: "m.room.name",
        room: roomId,
        user: userId,
        content: { name },
    });
}

function mkJoinEvent(roomId: string, userId: string) {
    const ret = mkEvent({
        event: true,
        type: "m.room.member",
        room: roomId,
        user: userId,
        content: {
            "membership": "join",
            "avatar_url": "mxc://example.org/" + userId,
        },
    });
    ret.event.state_key = userId;
    return ret;
}

function mkDirectEvent(
    roomId: string, userId: string, otherUsers: string[],
): MatrixEvent {
    const content = {};
    for (const otherUserId of otherUsers) {
        content[otherUserId] = [roomId];
    }
    return mkEvent({
        event: true,
        type: "m.direct",
        room: roomId,
        user: userId,
        content,
    });
}

function findSpan(wrapper: ReactWrapper, selector: string): ReactWrapper {
    const els = wrapper.find(selector).hostNodes();
    expect(els).toHaveLength(1);
    return els.at(0);
}

function findImg(wrapper: ReactWrapper, selector: string): ReactWrapper {
    const els = wrapper.find(selector).hostNodes();
    expect(els).toHaveLength(1);
    return els.at(0);
}
