<?php

namespace App\Http\Controllers;

use App\Ai\Agents\MealImageAnalyzer;
use App\Enums\AiUsageType;
use App\Enums\IngredientUnit;
use App\Enums\MealPostType;
use App\Http\Requests\MealImageAnalysisRequest;
use App\Services\AiCreditService;
use App\Services\AiUsageRecorder;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Str;
use Laravel\Ai\Responses\Data\Usage;
use Throwable;

class MealImageAnalysisController extends Controller
{
    public function store(
        MealImageAnalysisRequest $request,
        AiCreditService $aiCredits,
        AiUsageRecorder $aiUsageRecorder,
    ): JsonResponse {
        $user = $request->user();
        $image = $request->file('image');
        $input = $this->analysisInput($image);

        if (! $aiCredits->consume($user)) {
            return response()->json([
                'message' => __('api.meal_image_analysis.insufficient_credits'),
                'credits' => [
                    'balance' => $user->ai_credits_balance,
                ],
            ], 402);
        }

        try {
            $response = MealImageAnalyzer::make()->prompt(
                $this->prompt(),
                [$image],
                timeout: 90,
            );
        } catch (Throwable $exception) {
            report($exception);

            $aiCredits->refund($user);

            $aiUsageRecorder->failed(
                user: $user,
                type: AiUsageType::MEAL_IMAGE_ANALYSIS,
                exception: $exception,
                input: $input,
            );

            return response()->json([
                'message' => __('api.meal_image_analysis.failed'),
            ], 502);
        }

        $draft = $this->normalizeDraft(
            $response instanceof Arrayable
                ? $response->toArray()
                : (json_decode($response->text, true) ?: [])
        );

        $aiUsageRecorder->success(
            user: $user,
            type: AiUsageType::MEAL_IMAGE_ANALYSIS,
            input: $input,
            output: $draft,
            provider: $response->meta->provider ?? null,
            model: $response->meta->model ?? null,
            promptTokens: $response->usage->promptTokens ?? null,
            completionTokens: $response->usage->completionTokens ?? null,
            totalTokens: $this->totalTokens($response->usage ?? null),
        );

        return response()->json([
            'data' => $draft,
            'credits' => [
                'balance' => $user->fresh()->ai_credits_balance,
            ],
        ]);
    }

    private function prompt(): string
    {
        return <<<'PROMPT'
Analyse l'image du repas et prépare un brouillon éditable pour l'utilisateur.

Contraintes:
- Ne renseigne que ce qui est visible ou très probable dans l'image.
- Estime les quantités pour une portion consommable.
- Retourne des calories et macros cohérentes entre elles.
- Si l'image ne montre pas clairement un repas, retourne un titre générique et des valeurs à 0 avec une liste d'ingrédients vide.
PROMPT;
    }

    private function analysisInput(UploadedFile $image): array
    {
        return [
            'image' => [
                'client_original_name' => $image->getClientOriginalName(),
                'mime_type' => $image->getMimeType(),
                'size' => $image->getSize(),
            ],
        ];
    }

    private function totalTokens(?Usage $usage): ?int
    {
        if (! $usage) {
            return null;
        }

        return $usage->promptTokens + $usage->completionTokens;
    }

    private function normalizeDraft(array $draft): array
    {
        $type = MealPostType::tryFrom($this->cleanString($draft['type'] ?? '', 32))
            ?? MealPostType::OTHER;

        return [
            'title' => $this->cleanString($draft['title'] ?? 'Repas', 255) ?: 'Repas',
            'caption' => $this->cleanString($draft['caption'] ?? '', 1000),
            'type' => $type->value,
            'calories' => $this->positiveInteger($draft['calories'] ?? 0),
            'proteins' => $this->positiveNumber($draft['proteins'] ?? 0),
            'carbs' => $this->positiveNumber($draft['carbs'] ?? 0),
            'fats' => $this->positiveNumber($draft['fats'] ?? 0),
            'ingredients' => $this->normalizeIngredients($draft['ingredients'] ?? []),
        ];
    }

    private function normalizeIngredients(mixed $ingredients): array
    {
        if (! is_array($ingredients)) {
            return [];
        }

        $normalized = [];

        foreach (array_slice($ingredients, 0, 12) as $ingredient) {
            if (! is_array($ingredient)) {
                continue;
            }

            $name = $this->cleanString($ingredient['ingredient'] ?? '', 255);

            if ($name === '') {
                continue;
            }

            $unit = IngredientUnit::tryFrom($this->cleanString($ingredient['unit'] ?? '', 16))
                ?? IngredientUnit::GRAM;

            $normalized[] = [
                'position' => count($normalized) + 1,
                'ingredient' => $name,
                'quantity' => max(1, $this->positiveInteger($ingredient['quantity'] ?? 1)),
                'unit' => $unit->value,
            ];
        }

        return $normalized;
    }

    private function cleanString(mixed $value, int $limit): string
    {
        if (! is_string($value) && ! is_numeric($value)) {
            return '';
        }

        $cleaned = trim((string) preg_replace('/\s+/', ' ', (string) $value));

        return Str::limit($cleaned, $limit, '');
    }

    private function positiveInteger(mixed $value): int
    {
        if (! is_numeric($value)) {
            return 0;
        }

        return max(0, (int) round((float) $value));
    }

    private function positiveNumber(mixed $value): float
    {
        if (! is_numeric($value)) {
            return 0;
        }

        return round(max(0, (float) $value), 1);
    }
}
