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

namespace Kompas.Mcp.Tests.Integration;

/// <summary>Интеграция: формообразующие операции пакетов B и C (массивы, зеркало).</summary>
[Trait("Category", "Integration")]
[Collection(KompasCollection.Name)]
public sealed class FeatureOpsTests : IntegrationTestBase
{
    private readonly DocumentService _docs;
    private readonly PartModeler _modeler;
    private readonly QueryService _query;

    public FeatureOpsTests(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);
    }

    [Fact]
    public async Task Shell_box_hollows_with_open_cap()
    {
        await _docs.CreateAsync(KompasDocumentType.Part);
        try
        {
            // Коробка 40×30×20.
            var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
            await _modeler.AddRectangleAsync(s, 0, 0, 40, 30);
            await _modeler.CloseSketchAsync(s);
            await _modeler.ExtrudeAsync(s, depth: 20);
            var before = (await _query.GetPartInfoAsync()).Volume; // 24000

            // Удаляем один из двух плоских торцов (площадь 40×30=1200) → открытая оболочка.
            // Какой именно торец (верх/низ) — для объёма не важно: результат симметричен.
            var faces = await _query.ListFacesAsync();
            var cap = faces.First(f => f.Type == "plane" && Math.Abs(f.Area - 1200) < 1);
            var id = await _modeler.ShellAsync(new[] { cap.Index }, thickness: 2, outward: false);
            Assert.True(id > 0);
            await _modeler.RebuildAsync();

            // Оболочка 2 мм с открытым торцом: 24000 − 36×26×18 = 7152 мм³.
            var after = (await _query.GetPartInfoAsync()).Volume;
            Assert.True(after < before, "После оболочки объём должен уменьшиться.");
            Assert.InRange(after, 7152 * 0.95, 7152 * 1.05);
        }
        finally { await _docs.CloseAsync(save: false); }
    }

    [Fact]
    public async Task Rib_adds_material_in_corner_of_L_bracket()
    {
        await _docs.CreateAsync(KompasDocumentType.Part);
        try
        {
            // Г-образный уголок: L-профиль на XOZ (замкнутая ломаная), выдавить по Y на 30.
            // Площадь L = 40×5 + 5×35 = 375 → объём 375·30 = 11250.
            var sBody = await _modeler.OpenSketchAsync(BasePlane.XOZ);
            var lprofile = new (double x, double y)[] { (0, 0), (40, 0), (40, 5), (5, 5), (5, 40), (0, 40) };
            await _modeler.AddPolylineAsync(sBody, lprofile, closed: true);
            await _modeler.CloseSketchAsync(sBody);
            await _modeler.ExtrudeAsync(sBody, depth: 30);
            var before = (await _query.GetPartInfoAsync()).Volume;
            Assert.InRange(before, 11250 * 0.95, 11250 * 1.05); // L-уголок 375·30

            // Ребро-косынка: разомкнутый отрезок во внутреннем углу, на XOZ (Y=0).
            // Контур висит в зазоре (не касается полок); side=left, несимметрично → толщина 5 мм.
            var sRib = await _modeler.OpenSketchAsync(BasePlane.XOZ);
            await _modeler.AddLineAsync(sRib, 25, 10, 10, 25); // висит в пустом углу, не касаясь полок
            await _modeler.CloseSketchAsync(sRib);
            var id = await _modeler.RibAsync(sRib, thickness: 5, side: "left", symmetric: false);
            Assert.True(id > 0);
            await _modeler.RebuildAsync();

            // Косынка добавляет материал: объём растёт, но в разумных пределах.
            var after = (await _query.GetPartInfoAsync()).Volume;
            Assert.True(after > before, "После ребра жёсткости объём должен увеличиться.");
            Assert.InRange(after, before, before * 1.3);
        }
        finally { await _docs.CloseAsync(save: false); }
    }

    [Fact]
    public async Task Sweep_circle_along_line_makes_cylinder()
    {
        await _docs.CreateAsync(KompasDocumentType.Part);
        try
        {
            // Профиль: окружность R5 на XOY (центр 0,0).
            var profile = await _modeler.OpenSketchAsync(BasePlane.XOY);
            await _modeler.AddCircleAsync(profile, 0, 0, 5);
            await _modeler.CloseSketchAsync(profile);
            // Траектория: вертикальный отрезок (0,0)→(0,50) на XOZ (по +Z из центра профиля).
            var path = await _modeler.OpenSketchAsync(BasePlane.XOZ);
            await _modeler.AddLineAsync(path, 0, 0, 0, 50);
            await _modeler.CloseSketchAsync(path);

            var id = await _modeler.SweepAsync(profile, path);
            Assert.True(id > 0);
            await _modeler.RebuildAsync();

            // Окружность R5 по прямой длиной 50 → цилиндр: π·25·50 ≈ 3927 мм³.
            var v = (await _query.GetPartInfoAsync()).Volume;
            var expected = Math.PI * 25 * 50;
            Assert.InRange(v, expected * 0.95, expected * 1.05);

            // Габарит подтверждает ось: цилиндр R5 вдоль Z → ≈10×10×50.
            var box = await _query.GetBoundingBoxAsync();
            Assert.InRange(box.SizeX, 10 * 0.95, 10 * 1.05);
            Assert.InRange(box.SizeY, 10 * 0.95, 10 * 1.05);
            Assert.InRange(box.SizeZ, 50 * 0.95, 50 * 1.05);
        }
        finally { await _docs.CloseAsync(save: false); }
    }

    [Fact]
    public async Task OffsetPlane_sketch_extrudes_at_height()
    {
        await _docs.CreateAsync(KompasDocumentType.Part);
        try
        {
            // Эскиз на XOY, смещённой на +20 по Z → цилиндр R5×H10 с основанием на Z≈20.
            var s = await _modeler.OpenSketchOnOffsetPlaneAsync(BasePlane.XOY, offset: 20);
            await _modeler.AddCircleAsync(s, 0, 0, 5);
            await _modeler.CloseSketchAsync(s);
            await _modeler.ExtrudeAsync(s, depth: 10);
            await _modeler.RebuildAsync();

            var box = await _query.GetBoundingBoxAsync();
            Assert.InRange(box.MinZ, 19.5, 20.5); // основание поднято на 20 мм
            var v = (await _query.GetPartInfoAsync()).Volume;
            Assert.InRange(v, Math.PI * 25 * 10 * 0.95, Math.PI * 25 * 10 * 1.05);
        }
        finally { await _docs.CloseAsync(save: false); }
    }

    [Fact]
    public async Task LinearPattern_replicates_cylinder_along_X()
    {
        await _docs.CreateAsync(KompasDocumentType.Part);
        try
        {
            // Цилиндр R5×H10 в центре (объём π·25·10 ≈ 785.4 мм³).
            var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
            await _modeler.AddCircleAsync(s, 0, 0, 5);
            await _modeler.CloseSketchAsync(s);
            var cyl = await _modeler.ExtrudeAsync(s, depth: 10);
            var one = (await _query.GetPartInfoAsync()).Volume;

            // Линейный массив: 3 экземпляра вдоль X с шагом 30 (R5 → не перекрываются).
            var id = await _modeler.LinearPatternAsync(new[] { cyl }, CoordinateAxis.X, count: 3, step: 30);
            Assert.True(id > 0);
            await _modeler.RebuildAsync();

            // count=3 включает исходный → суммарный объём ≈ 3 цилиндра.
            var v = (await _query.GetPartInfoAsync()).Volume;
            Assert.InRange(v, one * 3 * 0.95, one * 3 * 1.05);

            // Габарит вдоль X: копии в 0/30/60, R5 → от −5 до 65 → размер ≈70 (знак шага не важен).
            var box = await _query.GetBoundingBoxAsync();
            Assert.InRange(box.SizeX, 70 * 0.95, 70 * 1.05);
        }
        finally { await _docs.CloseAsync(save: false); }
    }

    [Fact]
    public async Task CircularPattern_replicates_cylinder_around_Z()
    {
        await _docs.CreateAsync(KompasDocumentType.Part);
        try
        {
            // Цилиндр R3×H10 со смещением: центр (20,0) (объём π·9·10 ≈ 282.7 мм³).
            var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
            await _modeler.AddCircleAsync(s, 20, 0, 3);
            await _modeler.CloseSketchAsync(s);
            var cyl = await _modeler.ExtrudeAsync(s, depth: 10);
            var one = (await _query.GetPartInfoAsync()).Volume;

            // Круговой массив вокруг Z: 3 экз. с шагом 90° → центры (20,0),(0,20),(-20,0).
            var id = await _modeler.CircularPatternAsync(new[] { cyl }, CoordinateAxis.Z, count: 3, step: 90);
            Assert.True(id > 0);
            await _modeler.RebuildAsync();

            var v = (await _query.GetPartInfoAsync()).Volume;
            Assert.InRange(v, one * 3 * 0.95, one * 3 * 1.05);

            // Габарит: X от -23 до 23 → 46; Y от -3 до 23 → 26 (поворот реально произошёл).
            var box = await _query.GetBoundingBoxAsync();
            Assert.InRange(box.SizeX, 46 * 0.95, 46 * 1.05);
            Assert.InRange(box.SizeY, 26 * 0.95, 26 * 1.05);
        }
        finally { await _docs.CloseAsync(save: false); }
    }

    [Fact]
    public async Task CircularPattern_reverse_flips_rotation_direction()
    {
        // count=2 (исходный + 1 копия на 90°): forward и reverse уводят копию в РАЗНЫЕ
        // стороны по Y, поэтому MaxY габарита заметно различается (проводка def.inverce).
        async Task<double> CopyMaxY(bool reverse)
        {
            await _docs.CreateAsync(KompasDocumentType.Part);
            try
            {
                var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
                await _modeler.AddCircleAsync(s, 20, 0, 3); // цилиндр R3 центр (20,0)
                await _modeler.CloseSketchAsync(s);
                var cyl = await _modeler.ExtrudeAsync(s, depth: 10);
                await _modeler.CircularPatternAsync(new[] { cyl }, CoordinateAxis.Z, count: 2, step: 90, reverse: reverse);
                await _modeler.RebuildAsync();
                return (await _query.GetBoundingBoxAsync()).MaxY;
            }
            finally { await _docs.CloseAsync(save: false); }
        }

        var fwd = await CopyMaxY(reverse: false);
        var rev = await CopyMaxY(reverse: true);
        // Одно направление: копия в (0,+20) → MaxY≈23; другое: в (0,−20) → MaxY≈3. Разница ~20.
        Assert.True(Math.Abs(fwd - rev) > 10, $"reverse не меняет направление вращения: fwd MaxY={fwd:F1}, rev MaxY={rev:F1}");
    }

    [Fact]
    public async Task MirrorOperation_mirrors_two_features_across_YOZ()
    {
        await _docs.CreateAsync(KompasDocumentType.Part);
        try
        {
            // Две отдельные операции (проверяем заполнение коллекции несколькими источниками):
            // цилиндр R3 центр (20,0) и цилиндр R2 центр (10,0), оба H10.
            var s1 = await _modeler.OpenSketchAsync(BasePlane.XOY);
            await _modeler.AddCircleAsync(s1, 20, 0, 3);
            await _modeler.CloseSketchAsync(s1);
            var c1 = await _modeler.ExtrudeAsync(s1, depth: 10);

            var s2 = await _modeler.OpenSketchAsync(BasePlane.XOY);
            await _modeler.AddCircleAsync(s2, 10, 0, 2);
            await _modeler.CloseSketchAsync(s2);
            var c2 = await _modeler.ExtrudeAsync(s2, depth: 10);
            var before = (await _query.GetPartInfoAsync()).Volume; // π·9·10 + π·4·10 ≈ 408.4

            // Зеркало обеих операций относительно YOZ (x=0) → копии в (-20,0),(-10,0).
            var id = await _modeler.MirrorOperationAsync(new[] { c1, c2 }, BasePlane.YOZ);
            Assert.True(id > 0);
            await _modeler.RebuildAsync();

            var v = (await _query.GetPartInfoAsync()).Volume; // удвоение
            Assert.InRange(v, before * 2 * 0.95, before * 2 * 1.05);

            // Габарит X симметричен: от -23 до 23 → 46.
            var box = await _query.GetBoundingBoxAsync();
            Assert.InRange(box.SizeX, 46 * 0.95, 46 * 1.05);
        }
        finally { await _docs.CloseAsync(save: false); }
    }

    [Fact]
    public async Task MirrorBody_makes_symmetric_part_across_YOZ()
    {
        await _docs.CreateAsync(KompasDocumentType.Part);
        try
        {
            // Цилиндр R3×H10 центр (20,0) (объём ≈ 282.7 мм³).
            var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
            await _modeler.AddCircleAsync(s, 20, 0, 3);
            await _modeler.CloseSketchAsync(s);
            await _modeler.ExtrudeAsync(s, depth: 10);
            var before = (await _query.GetPartInfoAsync()).Volume;

            // Зеркально отразить тело относительно YOZ → оригинал + зеркало в (-20,0).
            var id = await _modeler.MirrorBodyAsync(BasePlane.YOZ);
            Assert.True(id > 0);
            await _modeler.RebuildAsync();

            var v = (await _query.GetPartInfoAsync()).Volume;
            Assert.InRange(v, before * 2 * 0.95, before * 2 * 1.05);

            var box = await _query.GetBoundingBoxAsync();
            Assert.InRange(box.SizeX, 46 * 0.95, 46 * 1.05); // от -23 до 23
        }
        finally { await _docs.CloseAsync(save: false); }
    }

    [Fact]
    public async Task Draft_tapers_box_sides_into_frustum()
    {
        await _docs.CreateAsync(KompasDocumentType.Part);
        try
        {
            // Коробка 40×40×20 (центр 0,0; низ на Z=0, верх на Z=20).
            var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
            await _modeler.AddRectangleAsync(s, -20, -20, 20, 20);
            await _modeler.CloseSketchAsync(s);
            await _modeler.ExtrudeAsync(s, depth: 20);
            var before = (await _query.GetPartInfoAsync()).Volume; // 32000

            // 4 боковые грани (площадь 40×20=800) под уклон 10° внутрь относительно XOY (низ остаётся 40×40).
            var faces = await _query.ListFacesAsync();
            var sides = faces.Where(f => f.Type == "plane" && Math.Abs(f.Area - 800) < 1).Select(f => f.Index).ToArray();
            Assert.Equal(4, sides.Length);

            var id = await _modeler.DraftAsync(sides, BasePlane.XOY, angle: 10, outward: false);
            Assert.True(id > 0);
            await _modeler.RebuildAsync();

            // Сужение к верху → усечённая пирамида. Верх ≈ 40−2·20·tan10° = 32.95; A2≈1085.5.
            // V = (20/3)(1600 + 1085.5 + √(1600·1085.5)) ≈ 26689 мм³.
            var after = (await _query.GetPartInfoAsync()).Volume;
            var expected = (20.0 / 3) * (1600 + 1085.5 + Math.Sqrt(1600 * 1085.5));
            Assert.True(after < before, "Сужающий уклон должен уменьшить объём.");
            Assert.InRange(after, expected * 0.9, expected * 1.1);
        }
        finally { await _docs.CloseAsync(save: false); }
    }

    [Fact]
    public async Task Draft_outward_expands_box()
    {
        await _docs.CreateAsync(KompasDocumentType.Part);
        try
        {
            // Та же коробка 40×40×20 (низ на Z=0).
            var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
            await _modeler.AddRectangleAsync(s, -20, -20, 20, 20);
            await _modeler.CloseSketchAsync(s);
            await _modeler.ExtrudeAsync(s, depth: 20);
            var before = (await _query.GetPartInfoAsync()).Volume; // 32000

            // 4 боковые грани под уклон 10° НАРУЖУ (outward=true) → верх расширяется, объём растёт.
            // Страхует маппинг направления (def.direction = !outward).
            var faces = await _query.ListFacesAsync();
            var sides = faces.Where(f => f.Type == "plane" && Math.Abs(f.Area - 800) < 1).Select(f => f.Index).ToArray();
            var id = await _modeler.DraftAsync(sides, BasePlane.XOY, angle: 10, outward: true);
            Assert.True(id > 0);
            await _modeler.RebuildAsync();

            // Верх ≈ 40 + 2·20·tan10° = 47.05; V = (20/3)(1600 + 47.05² + √(1600·47.05²)) ≈ 37970.
            var after = (await _query.GetPartInfoAsync()).Volume;
            var top = 40 + 2 * 20 * Math.Tan(10 * Math.PI / 180);
            var expected = (20.0 / 3) * (1600 + top * top + Math.Sqrt(1600 * top * top));
            Assert.True(after > before, "Расширяющий уклон должен увеличить объём (маппинг outward).");
            Assert.InRange(after, expected * 0.9, expected * 1.1);
        }
        finally { await _docs.CloseAsync(save: false); }
    }

    [Fact]
    public async Task Loft_two_squares_makes_solid()
    {
        await _docs.CreateAsync(KompasDocumentType.Part);
        try
        {
            // Два квадратных сечения 20×20 на XOY (Z=0) и на XOY+40 → призма 20×20×40.
            var s1 = await _modeler.OpenSketchAsync(BasePlane.XOY);
            await _modeler.AddRectangleAsync(s1, -10, -10, 10, 10);
            await _modeler.CloseSketchAsync(s1);
            var s2 = await _modeler.OpenSketchOnOffsetPlaneAsync(BasePlane.XOY, offset: 40);
            await _modeler.AddRectangleAsync(s2, -10, -10, 10, 10);
            await _modeler.CloseSketchAsync(s2);

            var id = await _modeler.LoftAsync(new[] { s1, s2 });
            Assert.True(id > 0);
            await _modeler.RebuildAsync();

            var v = (await _query.GetPartInfoAsync()).Volume; // 20×20×40 = 16000
            Assert.InRange(v, 16000 * 0.9, 16000 * 1.1);

            // Габарит подтверждает соединение сечений по высоте: ≈20×20×40.
            var box = await _query.GetBoundingBoxAsync();
            Assert.InRange(box.SizeX, 20 * 0.95, 20 * 1.05);
            Assert.InRange(box.SizeY, 20 * 0.95, 20 * 1.05);
            Assert.InRange(box.SizeZ, 40 * 0.95, 40 * 1.05);
        }
        finally { await _docs.CloseAsync(save: false); }
    }
}
