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

namespace Kompas.Mcp.Tests.Integration;

/// <summary>
/// Интеграция: элементарные тела (API7 <c>IElementaryBodies</c>) — второй путь формообразования
/// рядом с «эскиз → выдавливание», в том числе вычитание примитива вместо выреза по эскизу.
/// </summary>
[Trait("Category", "Integration")]
[Collection(KompasCollection.Name)]
public sealed class PrimitiveTests : IntegrationTestBase
{
    private readonly DocumentService _docs;
    private readonly PrimitiveService _primitives;
    private readonly PartModeler _modeler;
    private readonly QueryService _query;

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

    [Fact]
    public async Task Block_is_built_by_sizes_from_the_anchor_corner()
    {
        await _docs.CreateAsync(KompasDocumentType.Part);
        try
        {
            await _primitives.PrimitiveAsync(PrimitiveKind.Block, x: 4, y: 0, z: 0,
                length: 82, width: 10.5, height: 3);
            await _modeler.RebuildAsync();

            var box = await _query.GetBoundingBoxAsync();
            Assert.InRange(box.MinX, 3.99, 4.01);      // точка привязки — угол, а не центр
            Assert.InRange(box.SizeX, 81.9, 82.1);
            Assert.InRange(box.SizeY, 10.4, 10.6);
            Assert.InRange(box.SizeZ, 2.99, 3.01);

            var v = (await _query.GetPartInfoAsync()).Volume; // 82·10.5·3 = 2583
            Assert.InRange(v, 2583 * 0.99, 2583 * 1.01);
        }
        finally { await _docs.CloseAsync(save: false); }
    }

    [Fact]
    public async Task Subtracted_block_hollows_a_pocket_without_a_sketch()
    {
        await _docs.CreateAsync(KompasDocumentType.Part);
        try
        {
            await _primitives.PrimitiveAsync(PrimitiveKind.Block, 0, 0, 0, length: 40, width: 20, height: 10);
            // Карман 30×10 глубиной 4 — вычитаем блок, приподняв его до дна кармана.
            await _primitives.PrimitiveAsync(PrimitiveKind.Block, 5, 5, 6, length: 30, width: 10, height: 10,
                result: PrimitiveResult.Subtract);
            await _modeler.RebuildAsync();

            var box = await _query.GetBoundingBoxAsync();
            Assert.InRange(box.SizeZ, 9.9, 10.1); // габарит не изменился — вырезано внутри

            var v = (await _query.GetPartInfoAsync()).Volume; // 40·20·10 − 30·10·4 = 6800
            Assert.InRange(v, 6800 * 0.99, 6800 * 1.01);
        }
        finally { await _docs.CloseAsync(save: false); }
    }

    [Fact]
    public async Task Cylinder_can_be_added_to_an_existing_body()
    {
        await _docs.CreateAsync(KompasDocumentType.Part);
        try
        {
            await _primitives.PrimitiveAsync(PrimitiveKind.Block, 0, 0, 0, length: 20, width: 20, height: 5);
            await _primitives.PrimitiveAsync(PrimitiveKind.Cylinder, 10, 10, 5, diameter: 8, height: 10,
                result: PrimitiveResult.Union);
            await _modeler.RebuildAsync();

            var bodies = await _query.ListFacesAsync();
            Assert.NotEmpty(bodies);

            var box = await _query.GetBoundingBoxAsync();
            Assert.InRange(box.SizeZ, 14.9, 15.1); // 5 плиты + 10 цилиндра

            var v = (await _query.GetPartInfoAsync()).Volume; // 20·20·5 + π·4²·10 ≈ 2502.7
            var expected = 20 * 20 * 5 + Math.PI * 16 * 10;
            Assert.InRange(v, expected * 0.98, expected * 1.02);
        }
        finally { await _docs.CloseAsync(save: false); }
    }

    [Fact]
    public async Task Subtracting_where_nothing_intersects_fails_with_a_hint()
    {
        await _docs.CreateAsync(KompasDocumentType.Part);
        try
        {
            await _primitives.PrimitiveAsync(PrimitiveKind.Block, 0, 0, 0, length: 10, width: 10, height: 10);

            // Update() при вычитании мимо тела возвращает TRUE — ловим по неизменившемуся объёму.
            var ex = await Assert.ThrowsAsync<InvalidOperationException>(() =>
                _primitives.PrimitiveAsync(PrimitiveKind.Sphere, 100, 100, 100, diameter: 5,
                    result: PrimitiveResult.Subtract));

            Assert.Contains("не удалило материал", ex.Message, StringComparison.Ordinal);

            // Неудачная операция не должна оставаться в дереве: объём прежний, тело одно.
            var v = (await _query.GetPartInfoAsync()).Volume;
            Assert.InRange(v, 1000 * 0.99, 1000 * 1.01);
        }
        finally { await _docs.CloseAsync(save: false); }
    }
}
