<?php

namespace App\Services\Ai;

use App\Models\Central\Company;
use App\Tenancy\TenantManager;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;

/**
 * Extracts structured invoice data from an uploaded image using Claude Vision.
 * Output shape (when ok=true):
 *   data => [
 *     'supplier_name' => string|null,
 *     'date'          => string|null (YYYY-MM-DD),
 *     'currency'      => string|null,
 *     'items'         => [{name, qty, unit_price, total?}],
 *     'total'         => float|null,
 *   ]
 */
class OcrInvoiceService
{
    public const SUPPORTED_MIME = ['image/jpeg', 'image/png', 'image/webp', 'image/gif'];

    public function extract(string $absPath, ?string $mime = null): array
    {
        if (! is_file($absPath)) {
            return ['ok' => false, 'error' => 'Fișierul nu există.'];
        }

        $mime ??= mime_content_type($absPath) ?: 'image/jpeg';
        if (! in_array($mime, self::SUPPORTED_MIME, true)) {
            return ['ok' => false, 'error' => "Tip fișier neacceptat: {$mime}. Folosește JPG/PNG/WebP."];
        }

        $company = $this->currentCompany();
        if (! $company) {
            return ['ok' => false, 'error' => 'Tenant nerezolvat.'];
        }

        // Per-tenant rate limit — caps Claude Vision spend even if a user
        // accidentally (or maliciously) submits many invoices.
        $key = 'ocr-invoice:' . $company->id;
        if (\Illuminate\Support\Facades\RateLimiter::tooManyAttempts($key, 30)) {
            $retry = \Illuminate\Support\Facades\RateLimiter::availableIn($key);
            return ['ok' => false, 'error' => "Prea multe importuri OCR. Reîncearcă în {$retry} sec."];
        }
        \Illuminate\Support\Facades\RateLimiter::hit($key, 3600); // 30 / hour

        $key = data_get($company->settings, 'ai.claude_key');
        if (! $key) {
            return ['ok' => false, 'error' => '⚠️ Lipsește cheia Claude în Setări → AI.'];
        }

        $b64 = base64_encode(file_get_contents($absPath));

        $r = Http::withHeaders([
                'x-api-key' => $key,
                'anthropic-version' => '2023-06-01',
                'content-type' => 'application/json',
            ])
            ->timeout(60)
            ->post('https://api.anthropic.com/v1/messages', [
                'model' => app(AiAssistantService::class)->modelFor('claude', $company),
                'max_tokens' => 2048,
                'system' => $this->systemPrompt(),
                'messages' => [[
                    'role' => 'user',
                    'content' => [
                        ['type' => 'image', 'source' => [
                            'type' => 'base64', 'media_type' => $mime, 'data' => $b64,
                        ]],
                        ['type' => 'text', 'text' => 'Extrage datele din această factură. Răspunde DOAR cu obiectul JSON, fără text suplimentar și fără ```.'],
                    ],
                ]],
            ]);

        if (! $r->successful()) {
            Log::warning('ocr.extract API error', ['status' => $r->status(), 'body' => $r->body()]);
            return ['ok' => false, 'error' => 'API Claude: ' . ($r->json('error.message') ?? $r->status())];
        }

        $body = $r->json();
        $text = collect($body['content'] ?? [])
            ->where('type', 'text')->pluck('text')->implode("\n");
        $text = trim($text);

        $data = $this->parseJson($text);
        if ($data === null) {
            return ['ok' => false, 'error' => 'Răspuns AI ne-parsabil ca JSON.', 'raw' => $text];
        }

        return [
            'ok' => true,
            'data' => $this->normalize($data),
            'raw' => $text,
            'tokens' => [
                'in' => $body['usage']['input_tokens'] ?? null,
                'out' => $body['usage']['output_tokens'] ?? null,
            ],
        ];
    }

    private function systemPrompt(): string
    {
        return <<<TXT
Ești expert OCR pentru facturi auto-service / piese auto din România/Moldova.
Primești o imagine de factură. Extragi datele într-un obiect JSON STRICT cu schema:

{
  "supplier_name": string | null,
  "date": string | null,        // format YYYY-MM-DD
  "currency": string | null,    // MDL / EUR / USD / RON
  "items": [
    { "name": string, "qty": number, "unit_price": number, "total": number | null }
  ],
  "total": number | null
}

Reguli:
- Răspunde DOAR cu JSON-ul, fără cod de ghilimele markdown.
- Dacă o valoare nu poate fi citită clar, pune null (NU ghicești).
- Cantitățile și prețurile sunt numere zecimale (punct, nu virgulă).
- Numele piesei = scurt, fără TVA/observații.
TXT;
    }

    /** Tolerate markdown code fences + leading/trailing text around the JSON object. */
    private function parseJson(string $text): ?array
    {
        // Strip markdown fences if present.
        $clean = preg_replace('/^```(?:json)?\s*|\s*```$/m', '', $text);
        $clean = trim($clean);

        $data = json_decode($clean, true);
        if (is_array($data)) return $data;

        // Fallback: find first {...} block.
        if (preg_match('/\{.*\}/s', $clean, $m)) {
            $data = json_decode($m[0], true);
            if (is_array($data)) return $data;
        }
        return null;
    }

    private function normalize(array $d): array
    {
        $items = [];
        foreach ((array) ($d['items'] ?? []) as $it) {
            if (! is_array($it)) continue;
            $name = trim((string) ($it['name'] ?? ''));
            if ($name === '') continue;
            $qty = (float) ($it['qty'] ?? 1);
            $unit = (float) ($it['unit_price'] ?? 0);
            $total = isset($it['total']) ? (float) $it['total'] : round($qty * $unit, 2);
            $items[] = ['name' => $name, 'qty' => $qty, 'unit_price' => $unit, 'total' => $total];
        }

        return [
            'supplier_name' => $d['supplier_name'] ?? null,
            'date' => $d['date'] ?? null,
            'currency' => $d['currency'] ?? null,
            'items' => $items,
            'total' => isset($d['total']) ? (float) $d['total'] : array_sum(array_column($items, 'total')),
        ];
    }

    private function currentCompany(): ?Company
    {
        $id = app(TenantManager::class)->currentId();
        if (! $id) return null;
        return Company::withoutGlobalScopes()->find($id);
    }
}
