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

namespace Kompas.Mcp.Tests.Integration;

/// <summary>Интеграция: сопряжения компонентов сборки (API7 IMateConstraints3D).</summary>
[Trait("Category", "Integration")]
[Collection(KompasCollection.Name)]
public sealed class AssemblyMateTests : IntegrationTestBase
{
    private readonly DocumentService _docs;
    private readonly PartModeler _modeler;
    private readonly AssemblyService _assembly;
    private readonly QueryService _query;

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

    private async Task<string> BuildAndSaveCubeAsync(double side)
    {
        var path = TestPaths.NewFile(".m3d");
        await _docs.CreateAsync(KompasDocumentType.Part);
        try
        {
            var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
            await _modeler.AddRectangleAsync(s, 0, 0, side, side);
            await _modeler.CloseSketchAsync(s);
            await _modeler.ExtrudeAsync(s, depth: side);
            await _modeler.RebuildAsync();
            await _docs.SaveAsAsync(path);
        }
        finally { await _docs.CloseAsync(save: false); }
        return path;
    }

    /// <summary>Создать сборку из двух кубов 20³: A в нуле (world x[0,20]), B со сдвигом +100 (x[100,120]).</summary>
    private async Task<string> SetupTwoCubesAsync()
    {
        var cube = await BuildAndSaveCubeAsync(20);
        await _docs.CreateAsync(KompasDocumentType.Assembly);
        await _assembly.AddComponentAsync(cube, 0, 0, 0);
        await _assembly.AddComponentAsync(cube, 100, 0, 0);
        return cube;
    }

    [Fact]
    public async Task Coincidence_mate_brings_faces_flush()
    {
        await SetupTwoCubesAsync();
        try
        {
            var bbBefore = await _query.GetBoundingBoxAsync();
            Assert.InRange(bbBefore.MaxX - bbBefore.MinX, 118, 122); // 2 куба врозь ≈ 120

            // Совпадение +X грани A (20,10,10) с −X гранью B (100,10,10).
            var m = await _assembly.AddMateAsync(MateType.Coincidence, 20, 10, 10, 100, 10, 10, value: 0);
            Assert.True(m.Valid, "Сопряжение должно быть валидным.");

            var bbAfter = await _query.GetBoundingBoxAsync();
            Assert.InRange(bbAfter.MaxX - bbAfter.MinX, 38, 42); // кубы соприкоснулись ≈ 40

            // Размеры самих компонентов не изменились (двигаются, не деформируются).
            var comps = await _query.ListComponentsAsync();
            Assert.Equal(2, comps.Count);
            Assert.All(comps, c => Assert.InRange(c.SizeX, 19, 21));
        }
        finally { await _docs.CloseAsync(save: false); }
    }

    [Fact]
    public async Task Distance_mate_holds_faces_apart()
    {
        await SetupTwoCubesAsync();
        try
        {
            var bbBefore = await _query.GetBoundingBoxAsync();
            Assert.InRange(bbBefore.MaxX - bbBefore.MinX, 118, 122);

            // Расстояние 30 между +X гранью A и −X гранью B → ширина 20+30+20 = 70.
            var m = await _assembly.AddMateAsync(MateType.Distance, 20, 10, 10, 100, 10, 10, value: 30);
            Assert.True(m.Valid, "Сопряжение должно быть валидным.");

            var bbAfter = await _query.GetBoundingBoxAsync();
            Assert.InRange(bbAfter.MaxX - bbAfter.MinX, 66, 74); // ≈ 70

            var comps = await _query.ListComponentsAsync();
            Assert.All(comps, c => Assert.InRange(c.SizeX, 19, 21));
        }
        finally { await _docs.CloseAsync(save: false); }
    }

    [Fact]
    public async Task Mate_with_point_off_any_face_throws()
    {
        await SetupTwoCubesAsync();
        try
        {
            // Вторая точка — в пустоте (далеко от обоих кубов): грань не найдена → понятная ошибка.
            await Assert.ThrowsAsync<InvalidOperationException>(
                () => _assembly.AddMateAsync(MateType.Coincidence, 20, 10, 10, 1000, 1000, 1000, value: 0));
        }
        finally { await _docs.CloseAsync(save: false); }
    }
}
