<?php

use App\Services\Forecasting\LinearAlgebra;

it('transposes a 2x3 to a 3x2', function () {
    $m = [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]];
    expect(LinearAlgebra::transpose($m))->toBe([
        [1.0, 4.0],
        [2.0, 5.0],
        [3.0, 6.0],
    ]);
});

it('multiplies two compatible matrices', function () {
    $a = [[1.0, 2.0], [3.0, 4.0]];
    $b = [[5.0, 6.0], [7.0, 8.0]];
    // Hand-checked: [[19,22],[43,50]]
    expect(LinearAlgebra::multiply($a, $b))->toBe([
        [19.0, 22.0],
        [43.0, 50.0],
    ]);
});

it('multiplies a matrix by a vector', function () {
    $a = [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]];
    $v = [1.0, 0.0, -1.0];
    expect(LinearAlgebra::multiplyVector($a, $v))->toBe([-2.0, -2.0]);
});

it('builds an identity matrix', function () {
    expect(LinearAlgebra::identity(3))->toBe([
        [1.0, 0.0, 0.0],
        [0.0, 1.0, 0.0],
        [0.0, 0.0, 1.0],
    ]);
});

it('solves a 2x2 linear system', function () {
    // 2x + y  = 5
    //  x + 3y = 10  →  x=1, y=3
    $A = [[2.0, 1.0], [1.0, 3.0]];
    $b = [5.0, 10.0];
    $x = LinearAlgebra::solve($A, $b);
    expect($x[0])->toBeGreaterThan(0.999)->toBeLessThan(1.001)
        ->and($x[1])->toBeGreaterThan(2.999)->toBeLessThan(3.001);
});

it('solves a 3x3 linear system with partial pivoting', function () {
    // First pivot is 0 — only succeeds with partial pivoting.
    // det(A) = -4 (non-singular). Solution: x=2, y=1, z=3 → b = [5, 6, 13]
    $A = [[0.0, 2.0, 1.0], [1.0, 1.0, 1.0], [2.0, 0.0, 3.0]];
    $b = [5.0, 6.0, 13.0];
    $x = LinearAlgebra::solve($A, $b);
    expect($x[0])->toEqualWithDelta(2.0, 1e-9)
        ->and($x[1])->toEqualWithDelta(1.0, 1e-9)
        ->and($x[2])->toEqualWithDelta(3.0, 1e-9);
});

it('ridgeSolve recovers a known signal under low lambda', function () {
    // y = 3x + noise. Lambda = 0.001 (effectively OLS).
    // X is single-feature. Expect coefficient ≈ 3.
    $X = [[1.0], [2.0], [3.0], [4.0], [5.0]];
    $y = [3.0, 6.0, 9.0, 12.0, 15.0];
    $beta = LinearAlgebra::ridgeSolve($X, $y, 0.001);
    expect($beta[0])->toEqualWithDelta(3.0, 1e-3);
});

it('ridgeSolve shrinks coefficients toward zero with high lambda', function () {
    $X = [[1.0], [2.0], [3.0], [4.0], [5.0]];
    $y = [3.0, 6.0, 9.0, 12.0, 15.0];
    $betaLow = LinearAlgebra::ridgeSolve($X, $y, 0.001);
    $betaHigh = LinearAlgebra::ridgeSolve($X, $y, 1000.0);
    expect(abs($betaHigh[0]))->toBeLessThan(abs($betaLow[0]));
});

it('rejects multiplication of incompatible matrices', function () {
    $a = [[1.0, 2.0]];           // 1x2
    $b = [[1.0], [2.0], [3.0]]; // 3x1
    LinearAlgebra::multiply($a, $b);
})->throws(InvalidArgumentException::class);

it('throws when solving a singular matrix', function () {
    $A = [[1.0, 2.0], [2.0, 4.0]]; // row 2 is 2× row 1
    $b = [3.0, 6.0];
    LinearAlgebra::solve($A, $b);
})->throws(RuntimeException::class);
