using Theriapolis.Core.Data;
using Xunit;

namespace Theriapolis.Tests.Dungeons;

/// <summary>
/// Phase 7 M0 — content-load tests for the room-template + dungeon-layout
/// schema. These run on the actual <c>Content/Data/room_templates/</c>
/// + <c>Content/Data/dungeon_layouts/</c> directories so a broken
/// authoring edit fails the build.
/// </summary>
public sealed class RoomTemplateValidationTests
{
    private static ContentLoader Loader() => new(TestHelpers.DataDirectory);

    [Fact]
    public void RoomTemplates_LoadAndValidate()
    {
        // M0 vertical-slice: 5 imperium + 3 mine + 2 cave = 10 templates.
        // Test asserts ≥ 5 to allow content authoring growth without
        // modifying this test on every drop.
        var rooms = Loader().LoadRoomTemplates();
        Assert.True(rooms.Length >= 10,
            $"expected ≥10 room templates after Phase 7 M0 vertical slice, got {rooms.Length}");

        // Every template must declare at least one role and be one of the
        // five known dungeon types.
        var validTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
        { "imperium", "mine", "cult", "cave", "overgrown" };
        foreach (var r in rooms)
        {
            Assert.True(validTypes.Contains(r.Type), $"room '{r.Id}' has invalid type '{r.Type}'");
            Assert.NotEmpty(r.RolesEligible);
        }
    }

    [Fact]
    public void EveryRoomTemplate_HasGridMatchingFootprint()
    {
        var rooms = Loader().LoadRoomTemplates();
        foreach (var r in rooms)
        {
            Assert.Equal(r.FootprintHTiles, r.Grid.Length);
            for (int y = 0; y < r.Grid.Length; y++)
                Assert.Equal(r.FootprintWTiles, r.Grid[y].Length);
        }
    }

    [Fact]
    public void EveryRoomTemplate_HasIntactPerimeter()
    {
        var rooms = Loader().LoadRoomTemplates();
        foreach (var r in rooms)
        {
            int w = r.FootprintWTiles, h = r.FootprintHTiles;
            for (int x = 0; x < w; x++)
            {
                Assert.True(IsPerimeterChar(r.Grid[0][x]),
                    $"room '{r.Id}' top perimeter ({x},0) is '{r.Grid[0][x]}'");
                Assert.True(IsPerimeterChar(r.Grid[h - 1][x]),
                    $"room '{r.Id}' bottom perimeter ({x},{h - 1}) is '{r.Grid[h - 1][x]}'");
            }
            for (int y = 0; y < h; y++)
            {
                Assert.True(IsPerimeterChar(r.Grid[y][0]),
                    $"room '{r.Id}' left perimeter (0,{y}) is '{r.Grid[y][0]}'");
                Assert.True(IsPerimeterChar(r.Grid[y][w - 1]),
                    $"room '{r.Id}' right perimeter ({w - 1},{y}) is '{r.Grid[y][w - 1]}'");
            }
        }
    }

    private static bool IsPerimeterChar(char c) => c == '#' || c == 'D' || c == 'S';

    [Fact]
    public void DungeonLayouts_LoadAndValidate()
    {
        var loader = Loader();
        var rooms  = loader.LoadRoomTemplates();
        var loot   = loader.LoadLootTables(loader.LoadItems());
        var layouts = loader.LoadDungeonLayouts(rooms, loot);

        // M0 vertical-slice: imperium_medium + mine_small = 2 layouts.
        Assert.True(layouts.Length >= 2,
            $"expected ≥2 dungeon layouts after Phase 7 M0, got {layouts.Length}");

        // Every layout must declare a coherent room-count band.
        foreach (var l in layouts)
        {
            Assert.True(l.RoomCountMin >= 1, $"layout '{l.Id}' room_count_min < 1");
            Assert.True(l.RoomCountMax >= l.RoomCountMin, $"layout '{l.Id}' room_count_max < min");
        }
    }

    [Fact]
    public void EveryLayout_LootTableReferences_Resolve()
    {
        var loader = Loader();
        var loot   = loader.LoadLootTables(loader.LoadItems());
        var layouts = loader.LoadDungeonLayouts(loader.LoadRoomTemplates(), loot);
        var ids    = new HashSet<string>(loot.Select(t => t.Id), StringComparer.OrdinalIgnoreCase);
        foreach (var l in layouts)
        foreach (var (band, table) in l.LootTablePerBand)
            Assert.True(ids.Contains(table),
                $"layout '{l.Id}' loot_table_per_band['{band}'] = '{table}' not in loot_tables.json");
    }
}
