import json
import subprocess
from pathlib import Path


ROOT = Path(__file__).parents[1]


def run_php(source: str):
    completed = subprocess.run(
        ["php", "-r", source], cwd=ROOT, text=True, capture_output=True, check=True
    )
    return json.loads(completed.stdout)


def test_nonfinite_observed_equity_uses_protective_unavailable_path():
    source = """
    require 'classes/CapitalLossGuard.php';
    $results = [];
    foreach ([NAN, INF, -INF] as $equity) {
        $calls = [];
        $guard = new CapitalLossGuard([
            'enabled' => true, 'mode' => 'percent', 'threshold' => 5,
        ]);
        $guard->start(100);
        $result = $guard->observe(
            $equity,
            function () use (&$calls) { $calls[] = 'cancel'; return true; },
            function () use (&$calls) { $calls[] = 'close'; return true; },
            function () use (&$calls) { $calls[] = 'flat'; return true; },
        );
        $results[] = [$result['reason'], $guard->blocksNewExposure(), $calls];
    }
    echo json_encode($results);
    """
    assert run_php(source) == [
        ["risk_data_unavailable", True, ["cancel", "close", "flat"]],
    ] * 3


def test_nonfinite_mark_to_market_uses_protective_unavailable_path():
    source = """
    require 'classes/CapitalLossGuard.php';
    $results = [];
    foreach ([NAN, INF, -INF] as $mark) {
        $calls = [];
        $guard = new CapitalLossGuard([
            'enabled' => true, 'mode' => 'percent', 'threshold' => 5,
        ]);
        $guard->start(100);
        $result = $guard->observeMarkToMarket(
            100,
            $mark,
            0,
            function () use (&$calls) { $calls[] = 'cancel'; return true; },
            function () use (&$calls) { $calls[] = 'close'; return true; },
            function () use (&$calls) { $calls[] = 'flat'; return true; },
        );
        $results[] = [$result['reason'], $guard->blocksNewExposure(), $calls];
    }
    echo json_encode($results);
    """
    assert run_php(source) == [
        ["risk_data_unavailable", True, ["cancel", "close", "flat"]],
    ] * 3


def test_enabled_snapshot_is_authoritative_over_missing_or_false_top_level_settings():
    source = """
    require 'classes/CapitalLossGuard.php';
    require 'classes/Test.php';
    class FakeRedis {
        public function jsonset(...$args) { return true; }
        public function jsondel(...$args) { return true; }
    }
    $reflection = new ReflectionClass(Test::class);
    $test = $reflection->newInstanceWithoutConstructor();
    foreach ([['redis', new FakeRedis()], ['test_id', 1]] as [$name, $value]) {
        $property = new ReflectionProperty(Test::class, $name);
        $property->setAccessible(true);
        $property->setValue($test, $value);
    }
    $snapshot = [
        'capital_loss_guard' => [
            'enabled' => true, 'mode' => 'amount', 'threshold' => 10,
        ],
        'capital_loss_guard_baseline_equity' => 100,
        'capital_loss_guard_status' => 'armed',
        'capital_loss_guard_last_equity' => 100,
    ];
    $results = [];
    foreach ([[], ['enabled' => false]] as $settings) {
        $runtime = [
            'capital_loss_guard_snapshot' => $snapshot,
            'capital_loss_guard' => $settings,
            'equity' => 90,
            'unrealized_pnl' => 0,
            'fee' => 0,
            'timestamp' => 1,
        ];
        $result = $test->applyCapitalLossGuard(
            $runtime,
            function () { return true; },
            function () { return true; },
            function () { return true; },
        );
        $results[] = [
            $result['reason'],
            $result['status'],
            $runtime['capital_loss_guard_status'] ?? null,
            $runtime['capital_loss_guard']['enabled'] ?? null,
        ];
    }
    echo json_encode($results);
    """
    assert run_php(source) == [
        ["capital_loss_limit", "capital_loss_limit", "capital_loss_limit", True],
    ] * 2


def test_malformed_snapshot_is_not_bypassed_by_false_top_level_settings():
    source = """
    require 'classes/CapitalLossGuard.php';
    require 'classes/Test.php';
    class FakeRedis { public function jsonset(...$args) { return true; } }
    $reflection = new ReflectionClass(Test::class);
    $test = $reflection->newInstanceWithoutConstructor();
    foreach ([['redis', new FakeRedis()], ['test_id', 1]] as [$name, $value]) {
        $property = new ReflectionProperty(Test::class, $name);
        $property->setAccessible(true);
        $property->setValue($test, $value);
    }
    try {
        $runtime = [
            'capital_loss_guard' => ['enabled' => false],
            'capital_loss_guard_snapshot' => ['capital_loss_guard' => []],
            'equity' => 100,
        ];
        $test->applyCapitalLossGuard($runtime);
        echo json_encode('accepted');
    } catch (Throwable $error) {
        echo json_encode('rejected');
    }
    """
    assert run_php(source) == "rejected"


def test_legacy_runtime_without_guard_or_snapshot_stays_disabled():
    source = """
    require 'classes/CapitalLossGuard.php';
    require 'classes/Test.php';
    class FakeRedis { public function jsonset(...$args) { return true; } }
    $reflection = new ReflectionClass(Test::class);
    $test = $reflection->newInstanceWithoutConstructor();
    foreach ([['redis', new FakeRedis()], ['test_id', 1]] as [$name, $value]) {
        $property = new ReflectionProperty(Test::class, $name);
        $property->setAccessible(true);
        $property->setValue($test, $value);
    }
    $runtime = ['equity' => 100, 'unrealized_pnl' => 0, 'fee' => 0];
    $result = $test->applyCapitalLossGuard($runtime);
    echo json_encode([$result['reason'], $result['status']]);
    """
    assert run_php(source) == [None, "disabled"]
