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

namespace Kompas.Mcp.Tests.Integration;

/// <summary>Интеграция: операция «Отверстие» (API7 IHoles3D/IHole3D).</summary>
[Trait("Category", "Integration")]
[Collection(KompasCollection.Name)]
public sealed class HoleTests : IntegrationTestBase
{
    private readonly DocumentService _docs;
    private readonly PartModeler _modeler;
    private readonly HoleService _holes;
    private readonly QueryService _query;

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

    [Fact]
    public async Task Hole_through_top_face_removes_cylinder_volume()
    {
        await _docs.CreateAsync(KompasDocumentType.Part);
        try
        {
            // Коробка 40×40×20 (центр в начале координат), верхняя грань на 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

            // Сквозное отверстие Ø10 по центру верхней грани (0,0,20).
            await _holes.HoleAsync(0, 0, 20, diameter: 10, depth: 0, throughAll: true);
            var after = (await _query.GetPartInfoAsync()).Volume;

            // Удалён цилиндр π·25·20 ≈ 1570.8 мм³.
            var removed = Math.PI * 25 * 20;
            Assert.True(after < before, "После отверстия объём должен уменьшиться.");
            Assert.InRange(before - after, removed * 0.95, removed * 1.05);
        }
        finally { await _docs.CloseAsync(save: false); }
    }

    [Fact]
    public async Task Hole_blind_in_top_face_removes_partial_cylinder()
    {
        await _docs.CreateAsync(KompasDocumentType.Part);
        try
        {
            // Коробка 40×40×20, верхняя грань на 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

            // Глухое отверстие Ø8 глубиной 10 по центру верхней грани.
            await _holes.HoleAsync(0, 0, 20, diameter: 8, depth: 10, throughAll: false);
            var after = (await _query.GetPartInfoAsync()).Volume;

            // Удалён цилиндр π·16·10 ≈ 502.7 мм³ (без учёта плоского торца — EndFaceType=Flat).
            var removed = Math.PI * 16 * 10;
            Assert.True(after < before, "После глухого отверстия объём должен уменьшиться.");
            Assert.InRange(before - after, removed * 0.9, removed * 1.1);
        }
        finally { await _docs.CloseAsync(save: false); }
    }

    [Fact]
    public async Task Hole_through_bottom_face_uses_direction_fallback()
    {
        await _docs.CreateAsync(KompasDocumentType.Part);
        try
        {
            // Коробка 40×40×20, нижняя грань на Z=0 (нормаль смотрит наружу, −Z).
            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

            // Сверлим со стороны НИЖНЕЙ грани (0,0,0): корректное направление в тело — внутрь (+Z),
            // т.е. противоположно нормали → проверяет авто-подбор направления (fallback).
            await _holes.HoleAsync(0, 0, 0, diameter: 10, depth: 0, throughAll: true);
            var after = (await _query.GetPartInfoAsync()).Volume;

            var removed = Math.PI * 25 * 20;
            Assert.True(after < before, "Отверстие с нижней грани должно построиться (fallback направления).");
            Assert.InRange(before - after, removed * 0.95, removed * 1.05);
        }
        finally { await _docs.CloseAsync(save: false); }
    }

    [Fact]
    public async Task Counterbore_removes_base_plus_spotface()
    {
        await _docs.CreateAsync(KompasDocumentType.Part);
        try
        {
            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;

            // Ø10 сквозное + цековка Ø20×5 по центру верхней грани.
            await _holes.CounterboreHoleAsync(0, 0, 20, diameter: 10, depth: 0, throughAll: true,
                spotDiameter: 20, spotDepth: 5);
            var removed = before - (await _query.GetPartInfoAsync()).Volume;

            // База: π·25·20 ≈ 1570.8; + кольцо цековки (π·100−π·25)·5 ≈ 1178 → итого ≈ 2749.
            var baseCyl = Math.PI * 25 * 20;
            var expected = Math.PI * 25 * 20 + Math.PI * (100 - 25) * 5;
            Assert.True(removed > baseCyl * 1.1, "Цековка должна удалить больше, чем простое отверстие.");
            Assert.InRange(removed, expected * 0.9, expected * 1.1);
        }
        finally { await _docs.CloseAsync(save: false); }
    }

    [Fact]
    public async Task Countersink_removes_base_plus_cone()
    {
        await _docs.CreateAsync(KompasDocumentType.Part);
        try
        {
            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;

            // Ø8 сквозное + зенковка Ø16 под 90° по центру верхней грани.
            await _holes.CountersinkHoleAsync(0, 0, 20, diameter: 8, depth: 0, throughAll: true,
                sinkDiameter: 16, sinkAngle: 90);
            var removed = before - (await _query.GetPartInfoAsync()).Volume;

            // База Ø8 сквозь 20 ≈ 1005; зенковка-конус добавляет ~270 (точное зависит от трактовки угла).
            var baseCyl = Math.PI * 16 * 20;
            Assert.True(removed > baseCyl, "Зенковка должна удалить больше, чем простое отверстие Ø8.");
            Assert.True(removed < baseCyl * 2, "Объём зенковки в разумных пределах.");
        }
        finally { await _docs.CloseAsync(save: false); }
    }

    [Fact]
    public async Task Conic_hole_removes_tapered_volume()
    {
        await _docs.CreateAsync(KompasDocumentType.Part);
        try
        {
            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;

            // Коническое Ø10 у дна, угол 20°, глухое на 15 мм по центру верхней грани.
            await _holes.ConicHoleAsync(0, 0, 20, diameter: 10, depth: 15, throughAll: false, conicAngle: 20);
            var removed = before - (await _query.GetPartInfoAsync()).Volume;

            // Конус расширяется к поверхности → объём больше цилиндра Ø10×15 (π·25·15≈1178) и в разумных пределах.
            var baseCyl = Math.PI * 25 * 15;
            Assert.True(removed > baseCyl, "Конусное отверстие должно удалить больше цилиндра Ø10.");
            Assert.True(removed < baseCyl * 4, "Объём конуса в разумных пределах.");
        }
        finally { await _docs.CloseAsync(save: false); }
    }
}
