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

namespace Kompas.Mcp.Tests.Integration;

/// <summary>
/// Интеграция: механики, на которых держатся слитые инструменты — пакет примитивов эскиза
/// за один заход, мост «индекс грани → точка на ней», скругление/фаска пачкой рёбер и upsert
/// переменной. Каждая цепочка проверяется на живом КОМПАС: слияние инструментов не должно
/// менять результат построения.
/// </summary>
[Trait("Category", "Integration")]
[Collection(KompasCollection.Name)]
public sealed class MergedToolPathsTests : IntegrationTestBase
{
    private readonly DocumentService _docs;
    private readonly PartModeler _modeler;
    private readonly QueryService _query;
    private readonly ModelInspectionService _inspection;
    private readonly VariableService _variables;

    public MergedToolPathsTests(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);
        _inspection = new ModelInspectionService(fx.Session, fx.Dispatcher);
        _variables = new VariableService(fx.Session, fx.Dispatcher);
    }

    private static SketchEntity Rect(double x1, double y1, double x2, double y2) => new()
    {
        Kind = SketchEntityKind.Rectangle,
        Points = [(x1, y1), (x2, y2)],
    };

    private static SketchEntity Circle(double cx, double cy, double r) => new()
    {
        Kind = SketchEntityKind.Circle,
        CenterX = cx, CenterY = cy, Radius = r,
    };

    [Fact]
    public async Task Batch_builds_the_whole_contour_in_one_call()
    {
        await _docs.CreateAsync(KompasDocumentType.Part);
        try
        {
            // Плита 40×20 с четырьмя отверстиями Ø4 — раньше это было 6 вызовов, теперь один.
            var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
            var added = await _modeler.AddEntitiesAsync(s,
            [
                Rect(0, 0, 40, 20),
                Circle(5, 5, 2), Circle(35, 5, 2), Circle(5, 15, 2), Circle(35, 15, 2),
            ], autoClose: true);
            Assert.Equal(5, added.Count);

            await _modeler.ExtrudeAsync(s, depth: 10);
            await _modeler.RebuildAsync();

            // Объём плиты минус четыре цилиндра.
            var expected = (40 * 20 - 4 * Math.PI * 4) * 10;
            var v = (await _query.GetPartInfoAsync()).Volume;
            Assert.InRange(v, expected * 0.98, expected * 1.02);
        }
        finally { await _docs.CloseAsync(save: false); }
    }

    [Fact]
    public async Task Batch_reports_the_failing_position()
    {
        await _docs.CreateAsync(KompasDocumentType.Part);
        try
        {
            var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
            var bad = new SketchEntity { Kind = SketchEntityKind.Circle, CenterX = 0, CenterY = 0, Radius = -1 };

            var ex = await Assert.ThrowsAnyAsync<Exception>(
                () => _modeler.AddEntitiesAsync(s, [Rect(0, 0, 10, 10), Circle(2, 2, 1), bad]));

            Assert.Contains("[2]", ex.Message, StringComparison.Ordinal);
        }
        finally { await _docs.CloseAsync(save: false); }
    }

    [Fact]
    public async Task Face_center_point_lands_on_the_face()
    {
        await _docs.CreateAsync(KompasDocumentType.Part);
        try
        {
            var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
            await _modeler.AddEntitiesAsync(s, [Rect(0, 0, 40, 20)], autoClose: true);
            await _modeler.ExtrudeAsync(s, depth: 10);
            await _modeler.RebuildAsync();

            // Мост «индекс → точка»: точка обязана лежать на грани, иначе выбор по ней провалится.
            var faces = await _query.ListFacesAsync();
            Assert.NotEmpty(faces);

            foreach (var f in faces)
            {
                var (x, y, z) = await _inspection.FaceCenterPointAsync(f.Index);
                Assert.True(double.IsFinite(x) && double.IsFinite(y) && double.IsFinite(z),
                    $"грань [{f.Index}]: точка не вычислена");

                // Точка внутри габарита детали (с допуском на округление).
                Assert.InRange(x, -0.01, 40.01);
                Assert.InRange(y, -0.01, 20.01);
                Assert.InRange(z, -0.01, 10.01);
            }

            // И по такой точке эскиз на грани действительно создаётся.
            var top = faces.First(f => f.Type == "plane");
            var (px, py, pz) = await _inspection.FaceCenterPointAsync(top.Index);
            var onFace = await _modeler.OpenSketchOnFaceAsync(px, py, pz);
            await _modeler.CloseSketchAsync(onFace);
        }
        finally { await _docs.CloseAsync(save: false); }
    }

    [Fact]
    public async Task Fillet_and_chamfer_take_several_edges_at_once()
    {
        await _docs.CreateAsync(KompasDocumentType.Part);
        try
        {
            var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
            await _modeler.AddEntitiesAsync(s, [Rect(0, 0, 40, 20)], autoClose: true);
            await _modeler.ExtrudeAsync(s, depth: 10);
            await _modeler.RebuildAsync();

            var before = (await _query.GetPartInfoAsync()).Volume;
            var vertical = (await _query.ListEdgesAsync()).Where(e => e.Type == "line").Take(4).Select(e => e.Index).ToList();
            Assert.Equal(4, vertical.Count);

            // Одна операция на все рёбра: материал убывает, дерево не растёт на четыре узла.
            var featuresBefore = (await _inspection.ListFeaturesAsync()).Count;
            await _modeler.FilletEdgesAsync(vertical, radius: 2);
            await _modeler.RebuildAsync();

            var after = (await _query.GetPartInfoAsync()).Volume;
            var featuresAfter = (await _inspection.ListFeaturesAsync()).Count;

            Assert.True(after < before, $"объём не убыл: {before} → {after}");
            Assert.Equal(featuresBefore + 1, featuresAfter);
        }
        finally { await _docs.CloseAsync(save: false); }
    }

    [Fact]
    public async Task Set_variable_creates_then_updates()
    {
        await _docs.CreateAsync(KompasDocumentType.Part);
        try
        {
            var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
            await _modeler.AddEntitiesAsync(s, [Rect(0, 0, 40, 20)], autoClose: true);
            await _modeler.ExtrudeAsync(s, depth: 10);

            var (created, wasCreated) = await _variables.SetOrCreateVariableAsync("width", "40", "ширина плиты");
            Assert.True(wasCreated);
            Assert.Equal(40, created, 3);

            // Повторный вызов — уже изменение, и формула со ссылкой считается.
            var (updated, wasCreatedAgain) = await _variables.SetOrCreateVariableAsync("width", "50");
            Assert.False(wasCreatedAgain);
            Assert.Equal(50, updated, 3);

            var (derived, _) = await _variables.SetOrCreateVariableAsync("height", "width/2");
            Assert.Equal(25, derived, 3);

            var vars = await _inspection.ListVariablesAsync();
            Assert.Contains(vars, v => v.Name == "width" && Math.Abs(v.Value - 50) < 1e-6);
            Assert.Contains(vars, v => v.Name == "height");
        }
        finally { await _docs.CloseAsync(save: false); }
    }
}
