using Kompas.Mcp.Core.Documents;
using Kompas.Mcp.Core.Modeling;
using Kompas.Mcp.Core.Query;

namespace Kompas.Mcp.Tests.Integration;

/// <summary>
/// Интеграция: надпись в эскизе. Текст сам по себе — оформление, выдавить его нельзя;
/// примитив type=text переводит его в кривые (ksConvertTextToCurve), и контур глифов
/// становится обычным сечением операции.
/// </summary>
[Trait("Category", "Integration")]
[Collection(KompasCollection.Name)]
public sealed class SketchTextTests : IntegrationTestBase
{
    private readonly DocumentService _docs;
    private readonly PartModeler _modeler;
    private readonly QueryService _query;

    public SketchTextTests(KompasFixture fx) : base(fx)
    {
        _docs = new DocumentService(fx.Session, fx.Dispatcher);
        _modeler = new PartModeler(fx.Session, fx.Dispatcher);
        _query = new QueryService(fx.Session, fx.Dispatcher);
    }

    private static SketchEntity Text(string text, double x, double y, double height, string? font = null) => new()
    {
        Kind = SketchEntityKind.Text,
        Points = [(x, y)],
        Text = text,
        Height = height,
        FontName = font,
    };

    [Fact]
    public async Task Text_becomes_curves_and_extrudes()
    {
        await _docs.CreateAsync(KompasDocumentType.Part);
        try
        {
            var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
            var notes = await _modeler.AddEntitiesAsync(s, [Text("HI", 0, 0, 10, "Arial")], autoClose: true);
            await _modeler.ExtrudeAsync(s, depth: 2);
            await _modeler.RebuildAsync();

            // Метрики нужны агенту для выравнивания: до выдавливания их узнать неоткуда.
            Assert.Contains("глифы", notes[0], StringComparison.Ordinal);
            Assert.Contains("ширина ячейки", notes[0], StringComparison.Ordinal);

            var info = await _query.GetPartInfoAsync();
            Assert.True(info.Volume > 0, "надпись не дала объёма — текст не переведён в кривые");

            var box = await _query.GetBoundingBoxAsync();
            Assert.InRange(box.SizeZ, 1.99, 2.01);
            Assert.InRange(box.SizeY, 6, 11);   // высота прописных ≈ height
            Assert.True(box.SizeX > box.SizeY); // «HI» шире, чем выше
        }
        finally { await _docs.CloseAsync(save: false); }
    }

    [Fact]
    public async Task Reported_glyph_box_matches_the_extruded_geometry()
    {
        await _docs.CreateAsync(KompasDocumentType.Part);
        try
        {
            // Ширина ячейки (её и возвращали раньше) больше габарита глифов, поэтому агент,
            // вписывающий надпись в поле, промахивался. Сверяем заявленный габарит с фактическим.
            var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
            var notes = await _modeler.AddEntitiesAsync(s, [Text("HH", 0, 0, 10, "Arial")], autoClose: true);
            await _modeler.ExtrudeAsync(s, depth: 2);
            await _modeler.RebuildAsync();

            var numbers = System.Text.RegularExpressions.Regex
                .Matches(notes[0], @"-?\d+\.\d{2}")
                .Select(m => double.Parse(m.Value, System.Globalization.CultureInfo.InvariantCulture))
                .ToList();
            Assert.True(numbers.Count >= 7, $"в заметке нет чисел габарита: {notes[0]}");
            var (minX, maxX, minY, maxY) = (numbers[0], numbers[1], numbers[2], numbers[3]);

            var box = await _query.GetBoundingBoxAsync();
            Assert.InRange(maxX - minX, box.SizeX - 0.05, box.SizeX + 0.05);
            Assert.InRange(maxY - minY, box.SizeY - 0.05, box.SizeY + 0.05);

            // Единицы — миллиметры эскиза, а не сантиметры листа: высота прописной ≈ height.
            Assert.InRange(maxY - minY, 9.5, 10.5);
        }
        finally { await _docs.CloseAsync(save: false); }
    }

    [Fact]
    public async Task Unknown_font_is_reported_by_name()
    {
        await _docs.CreateAsync(KompasDocumentType.Part);
        try
        {
            var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
            var bad = Text("HI", 0, 0, 10, "No Such Font 12345");

            // КОМПАС подставляет замену вместо отсутствующего шрифта, поэтому надпись
            // всё равно строится — важно, что она не рушит пакет и даёт геометрию.
            var notes = await _modeler.AddEntitiesAsync(s, [bad], autoClose: true);
            Assert.False(string.IsNullOrEmpty(notes[0]));
        }
        finally { await _docs.CloseAsync(save: false); }
    }

    [Fact]
    public async Task Thin_wall_outward_grows_the_contour()
    {
        await _docs.CreateAsync(KompasDocumentType.Part);
        try
        {
            // Квадрат 20×20 стенкой 2 мм наружу: габарит вырастает на 2·2 мм по каждой оси,
            // а объём равен объёму рамки, а не сплошной плиты.
            var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
            await _modeler.AddRectangleAsync(s, 0, 0, 20, 20);
            await _modeler.CloseSketchAsync(s);
            await _modeler.ExtrudeAsync(s, depth: 5, thinThickness: 2, thinSide: ThinSide.Outward);
            await _modeler.RebuildAsync();

            var box = await _query.GetBoundingBoxAsync();
            Assert.InRange(box.SizeX, 23.9, 24.1);
            Assert.InRange(box.SizeY, 23.9, 24.1);

            var v = (await _query.GetPartInfoAsync()).Volume; // (24² − 20²)·5 = 880
            Assert.InRange(v, 880 * 0.98, 880 * 1.02);
        }
        finally { await _docs.CloseAsync(save: false); }
    }

    [Fact]
    public async Task Thin_wall_inward_keeps_the_contour_and_hollows_it()
    {
        await _docs.CreateAsync(KompasDocumentType.Part);
        try
        {
            // Стенка внутрь контура: габарит остаётся 20×20, материал — только рамка по периметру.
            var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
            await _modeler.AddRectangleAsync(s, 0, 0, 20, 20);
            await _modeler.CloseSketchAsync(s);
            await _modeler.ExtrudeAsync(s, depth: 5, thinThickness: 2, thinSide: ThinSide.Inward);
            await _modeler.RebuildAsync();

            var box = await _query.GetBoundingBoxAsync();
            Assert.InRange(box.SizeX, 19.9, 20.1);
            Assert.InRange(box.SizeY, 19.9, 20.1);

            // (20² − 16²)·5 = 720; сплошная плита дала бы 2000 — так ловится «стенка не применилась».
            var v = (await _query.GetPartInfoAsync()).Volume;
            Assert.InRange(v, 720 * 0.98, 720 * 1.02);
        }
        finally { await _docs.CloseAsync(save: false); }
    }
}
