package thb.jeanluc.adventure.loader;

import org.junit.jupiter.api.Test;
import thb.jeanluc.adventure.loader.dto.NpcDto;
import thb.jeanluc.adventure.loader.dto.ReactionDto;
import thb.jeanluc.adventure.loader.dto.RoomDto;
import thb.jeanluc.adventure.model.Direction;
import thb.jeanluc.adventure.model.Npc;
import thb.jeanluc.adventure.model.Room;
import thb.jeanluc.adventure.model.item.Item;
import thb.jeanluc.adventure.model.item.PlainItem;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

class ReferenceResolverTest {

    private static Map<String, Item> oneItem(String id) {
        Map<String, Item> m = new HashMap<>();
        m.put(id, PlainItem.builder().id(id).name(id).description("d").build());
        return m;
    }

    @Test
    void resolveRooms_wiresExitsAndItems() {
        Map<String, Room> rooms = new HashMap<>();
        rooms.put("a", new Room("a", "A", "d"));
        rooms.put("b", new Room("b", "B", "d"));
        Map<String, Item> items = oneItem("key");

        new ReferenceResolver(items, new HashMap<>(), rooms).resolveRooms(List.of(
                new RoomDto("a", "A", "d", Map.of("north", "b"), List.of("key"), List.of()),
                new RoomDto("b", "B", "d", Map.of("south", "a"), List.of(), List.of())
        ));

        assertThat(rooms.get("a").getExit(Direction.NORTH)).contains(rooms.get("b"));
        assertThat(rooms.get("a").findItem("key")).isPresent();
    }

    @Test
    void resolveRooms_unknownExitTarget_throws() {
        Map<String, Room> rooms = new HashMap<>();
        rooms.put("a", new Room("a", "A", "d"));

        assertThatThrownBy(() ->
                new ReferenceResolver(Map.of(), Map.of(), rooms).resolveRooms(List.of(
                        new RoomDto("a", "A", "d", Map.of("north", "ghost"), List.of(), List.of())
                )))
                .isInstanceOf(WorldLoadException.class)
                .hasMessageContaining("ghost");
    }

    @Test
    void resolveRooms_unknownDirection_throws() {
        Map<String, Room> rooms = new HashMap<>();
        rooms.put("a", new Room("a", "A", "d"));

        assertThatThrownBy(() ->
                new ReferenceResolver(Map.of(), Map.of(), rooms).resolveRooms(List.of(
                        new RoomDto("a", "A", "d", Map.of("up", "a"), List.of(), List.of())
                )))
                .isInstanceOf(WorldLoadException.class)
                .hasMessageContaining("up");
    }

    @Test
    void resolveNpcs_wiresReactionItems() {
        Map<String, Item> items = oneItem("lamp");
        items.put("key", PlainItem.builder().id("key").name("Key").description("d").build());
        Map<String, Npc> npcs = new HashMap<>();
        npcs.put("man", Npc.shell("man", "Man", "d", "hi"));

        new ReferenceResolver(items, npcs, Map.of()).resolveNpcs(List.of(
                new NpcDto("man", "Man", "d", "hi", List.of(
                        new ReactionDto("lamp", "thx", "key", "lamp")
                ))
        ));

        var reaction = npcs.get("man").reactionFor("lamp").orElseThrow();
        assertThat(reaction.getGives()).isEqualTo(items.get("key"));
        assertThat(reaction.getConsumes()).isEqualTo(items.get("lamp"));
    }

    @Test
    void resolveNpcs_duplicateTrigger_throws() {
        Map<String, Item> items = oneItem("lamp");
        Map<String, Npc> npcs = new HashMap<>();
        npcs.put("man", Npc.shell("man", "Man", "d", "hi"));

        assertThatThrownBy(() ->
                new ReferenceResolver(items, npcs, Map.of()).resolveNpcs(List.of(
                        new NpcDto("man", "Man", "d", "hi", List.of(
                                new ReactionDto("lamp", "a", null, null),
                                new ReactionDto("lamp", "b", null, null)
                        ))
                )))
                .isInstanceOf(WorldLoadException.class)
                .hasMessageContaining("duplicate");
    }

    @Test
    void resolveNpcs_unknownGivesItem_throws() {
        Map<String, Item> items = oneItem("lamp");
        Map<String, Npc> npcs = new HashMap<>();
        npcs.put("man", Npc.shell("man", "Man", "d", "hi"));

        assertThatThrownBy(() ->
                new ReferenceResolver(items, npcs, Map.of()).resolveNpcs(List.of(
                        new NpcDto("man", "Man", "d", "hi", List.of(
                                new ReactionDto("lamp", "a", "ghost", null)
                        ))
                )))
                .isInstanceOf(WorldLoadException.class)
                .hasMessageContaining("ghost");
    }
}
