<?php

use App\Ai\Agents\MealImageAnalyzer;
use App\Enums\AiUsageStatus;
use App\Enums\AiUsageType;
use App\Enums\CreditLedgerEntryType;
use App\Enums\IngredientUnit;
use App\Enums\MealPostType;
use App\Models\AiUsage;
use App\Models\CreditLedgerEntry;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Exceptions;
use Laravel\Sanctum\Sanctum;

uses(RefreshDatabase::class);

function fakeMealAnalysisImage(): UploadedFile
{
    $path = tempnam(sys_get_temp_dir(), 'meal-analysis-');

    file_put_contents($path, base64_decode(
        'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAFgwJ/lmHqWQAAAABJRU5ErkJggg=='
    ));

    return new UploadedFile($path, 'meal.png', 'image/png', null, true);
}

it('analyzes a meal image into a draft', function () {
    $user = User::factory()->create();

    Sanctum::actingAs($user);

    MealImageAnalyzer::fake([
        [
            'title' => ' Bowl saumon avocat ',
            'caption' => 'Un bowl avec du saumon, du riz et de l’avocat.',
            'type' => MealPostType::LUNCH->value,
            'calories' => 612,
            'proteins' => 39.4,
            'carbs' => 58.2,
            'fats' => 25.1,
            'ingredients' => [
                [
                    'position' => 12,
                    'ingredient' => ' Riz ',
                    'quantity' => 150,
                    'unit' => IngredientUnit::GRAM->value,
                ],
                [
                    'position' => 13,
                    'ingredient' => 'Saumon',
                    'quantity' => 120,
                    'unit' => IngredientUnit::GRAM->value,
                ],
            ],
        ],
    ]);

    $response = $this
        ->withHeader('Accept', 'application/json')
        ->post('/api/meal-posts/analyze-image', [
            'image' => fakeMealAnalysisImage(),
        ]);

    $response
        ->assertOk()
        ->assertJsonPath('credits.balance', 2)
        ->assertJsonPath('data.title', 'Bowl saumon avocat')
        ->assertJsonPath('data.type', MealPostType::LUNCH->value)
        ->assertJsonPath('data.calories', 612)
        ->assertJsonPath('data.proteins', 39.4)
        ->assertJsonPath('data.ingredients.0.position', 1)
        ->assertJsonPath('data.ingredients.0.ingredient', 'Riz')
        ->assertJsonPath('data.ingredients.1.ingredient', 'Saumon');

    MealImageAnalyzer::assertPrompted(
        fn ($prompt): bool => str_contains($prompt->prompt, "Analyse l'image")
            && $prompt->attachments->count() === 1
    );

    $usage = AiUsage::query()->sole();

    expect($usage->user_id)->toBe($user->id)
        ->and($usage->type)->toBe(AiUsageType::MEAL_IMAGE_ANALYSIS)
        ->and($usage->status)->toBe(AiUsageStatus::SUCCESS)
        ->and($usage->input['image'])->toMatchArray([
            'client_original_name' => 'meal.png',
            'mime_type' => 'image/png',
        ])
        ->and($usage->output)->toMatchArray([
            'title' => 'Bowl saumon avocat',
            'type' => MealPostType::LUNCH->value,
            'calories' => 612,
            'proteins' => 39.4,
            'ingredients' => [
                [
                    'position' => 1,
                    'ingredient' => 'Riz',
                    'quantity' => 150,
                    'unit' => IngredientUnit::GRAM->value,
                ],
                [
                    'position' => 2,
                    'ingredient' => 'Saumon',
                    'quantity' => 120,
                    'unit' => IngredientUnit::GRAM->value,
                ],
            ],
        ]);

    $user->refresh();
    $ledgerEntry = CreditLedgerEntry::query()->sole();

    expect($user->ai_credits_balance)->toBe(2)
        ->and($ledgerEntry->user_id)->toBe($user->id)
        ->and($ledgerEntry->type)->toBe(CreditLedgerEntryType::USAGE)
        ->and($ledgerEntry->credits)->toBe(-1)
        ->and($ledgerEntry->balance_after)->toBe(2);
});

it('records a failed meal image analysis attempt', function () {
    $user = User::factory()->create();

    Sanctum::actingAs($user);
    Exceptions::fake();

    MealImageAnalyzer::fake(
        fn () => throw new \RuntimeException('Provider unavailable')
    );

    $this
        ->withHeader('Accept', 'application/json')
        ->post('/api/meal-posts/analyze-image', [
            'image' => fakeMealAnalysisImage(),
        ])
        ->assertStatus(502)
        ->assertJsonPath('message', __('api.meal_image_analysis.failed'));

    Exceptions::assertReported(\RuntimeException::class);

    $usage = AiUsage::query()->sole();

    expect($usage->user_id)->toBe($user->id)
        ->and($usage->type)->toBe(AiUsageType::MEAL_IMAGE_ANALYSIS)
        ->and($usage->status)->toBe(AiUsageStatus::FAILED)
        ->and($usage->input['image'])->toMatchArray([
            'client_original_name' => 'meal.png',
            'mime_type' => 'image/png',
        ])
        ->and($usage->output)->toBeNull()
        ->and($usage->error_message)->toContain('Provider unavailable');

    expect($user->fresh()->ai_credits_balance)->toBe(3);
});

it('requires available credits to analyze a meal image', function () {
    $user = User::factory()->create([
        'ai_credits_balance' => 0,
    ]);

    Sanctum::actingAs($user);

    MealImageAnalyzer::fake([
        [
            'title' => 'Bowl',
        ],
    ]);

    $this
        ->withHeader('Accept', 'application/json')
        ->post('/api/meal-posts/analyze-image', [
            'image' => fakeMealAnalysisImage(),
        ])
        ->assertStatus(402)
        ->assertJsonPath('message', __('api.meal_image_analysis.insufficient_credits'))
        ->assertJsonPath('credits.balance', 0);

    expect(AiUsage::query()->count())->toBe(0)
        ->and(CreditLedgerEntry::query()->count())->toBe(0);
});

it('requires authentication to analyze a meal image', function () {
    $this
        ->withHeader('Accept', 'application/json')
        ->post('/api/meal-posts/analyze-image', [
            'image' => fakeMealAnalysisImage(),
        ])
        ->assertUnauthorized();
});

it('validates the analyzed image', function () {
    Sanctum::actingAs(User::factory()->create());

    $this
        ->postJson('/api/meal-posts/analyze-image')
        ->assertUnprocessable()
        ->assertJsonValidationErrors(['image']);
});
