using Xunit;
using SynorCompute;

namespace SynorCompute.Tests;

public class TensorTests
{
    [Fact]
    public void Constructor_WithShapeAndData_CreatesTensor()
    {
        var tensor = new Tensor([2, 3], [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);

        Assert.Equal([2, 3], tensor.Shape);
        Assert.Equal(6, tensor.Size);
        Assert.Equal(2, tensor.Ndim);
    }

    [Fact]
    public void Zeros_CreatesZeroTensor()
    {
        var tensor = Tensor.Zeros([3, 4]);

        Assert.Equal([3, 4], tensor.Shape);
        Assert.Equal(12, tensor.Size);
        Assert.All(tensor.Data, v => Assert.Equal(0.0, v));
    }

    [Fact]
    public void Ones_CreatesOnesTensor()
    {
        var tensor = Tensor.Ones([2, 2]);

        Assert.All(tensor.Data, v => Assert.Equal(1.0, v));
    }

    [Fact]
    public void Random_CreatesRandomTensor()
    {
        var tensor = Tensor.Random([10, 10]);

        Assert.Equal([10, 10], tensor.Shape);
        Assert.All(tensor.Data, v => Assert.InRange(v, 0.0, 1.0));
    }

    [Fact]
    public void Randn_CreatesNormalDistributionTensor()
    {
        var tensor = Tensor.Randn([1000]);

        var mean = tensor.Mean();
        var std = tensor.Std();

        Assert.InRange(Math.Abs(mean), 0, 0.2);
        Assert.InRange(std, 0.8, 1.2);
    }

    [Fact]
    public void Eye_CreatesIdentityMatrix()
    {
        var tensor = Tensor.Eye(3);

        Assert.Equal([3, 3], tensor.Shape);
        Assert.Equal(1.0, tensor[0, 0]);
        Assert.Equal(1.0, tensor[1, 1]);
        Assert.Equal(1.0, tensor[2, 2]);
        Assert.Equal(0.0, tensor[0, 1]);
    }

    [Fact]
    public void Arange_CreatesRangeTensor()
    {
        var tensor = Tensor.Arange(0, 5, 1);

        Assert.Equal([5], tensor.Shape);
        Assert.Equal(0.0, tensor[0]);
        Assert.Equal(4.0, tensor[4]);
    }

    [Fact]
    public void Linspace_CreatesLinearlySpacedTensor()
    {
        var tensor = Tensor.Linspace(0, 10, 11);

        Assert.Equal([11], tensor.Shape);
        Assert.Equal(0.0, tensor[0], 4);
        Assert.Equal(10.0, tensor[10], 4);
        Assert.Equal(5.0, tensor[5], 4);
    }

    [Fact]
    public void Reshape_ChangesShape()
    {
        var tensor = new Tensor([6], [1, 2, 3, 4, 5, 6]);
        var reshaped = tensor.Reshape([2, 3]);

        Assert.Equal([2, 3], reshaped.Shape);
        Assert.Equal(6, reshaped.Size);
    }

    [Fact]
    public void Reshape_ThrowsOnInvalidShape()
    {
        var tensor = Tensor.Zeros([4]);

        Assert.Throws<ArgumentException>(() => tensor.Reshape([2, 3]));
    }

    [Fact]
    public void Transpose_Transposes2DTensor()
    {
        var tensor = new Tensor([2, 3], [1, 2, 3, 4, 5, 6]);
        var transposed = tensor.Transpose();

        Assert.Equal([3, 2], transposed.Shape);
        Assert.Equal(1.0, transposed[0, 0]);
        Assert.Equal(4.0, transposed[0, 1]);
    }

    [Fact]
    public void Sum_CalculatesSum()
    {
        var tensor = new Tensor([4], [1, 2, 3, 4]);

        Assert.Equal(10.0, tensor.Sum());
    }

    [Fact]
    public void Mean_CalculatesMean()
    {
        var tensor = new Tensor([4], [1, 2, 3, 4]);

        Assert.Equal(2.5, tensor.Mean());
    }

    [Fact]
    public void Std_CalculatesStandardDeviation()
    {
        var tensor = new Tensor([4], [1, 2, 3, 4]);

        Assert.Equal(1.118, tensor.Std(), 3);
    }

    [Fact]
    public void Min_FindsMinimum()
    {
        var tensor = new Tensor([4], [3, 1, 4, 2]);

        Assert.Equal(1.0, tensor.Min());
    }

    [Fact]
    public void Max_FindsMaximum()
    {
        var tensor = new Tensor([4], [3, 1, 4, 2]);

        Assert.Equal(4.0, tensor.Max());
    }

    [Fact]
    public void ReLU_AppliesActivation()
    {
        var tensor = new Tensor([5], [-2, -1, 0, 1, 2]);
        var result = tensor.ReLU();

        Assert.Equal([0.0, 0.0, 0.0, 1.0, 2.0], result.Data);
    }

    [Fact]
    public void Sigmoid_AppliesActivation()
    {
        var tensor = new Tensor([1], [0]);
        var result = tensor.Sigmoid();

        Assert.Equal(0.5, result[0], 4);
    }

    [Fact]
    public void Softmax_AppliesActivation()
    {
        var tensor = new Tensor([3], [1, 2, 3]);
        var result = tensor.Softmax();

        Assert.Equal(1.0, result.Sum(), 4);
        Assert.True(result[2] > result[1]);
        Assert.True(result[1] > result[0]);
    }

    [Fact]
    public void Serialization_RoundTrip()
    {
        var original = new Tensor([2, 3], [1, 2, 3, 4, 5, 6]);
        var json = original.ToJson();
        var restored = Tensor.FromJson(json);

        Assert.Equal(original.Shape, restored.Shape);
        Assert.Equal(original.Data, restored.Data);
    }
}
