<?php

namespace App\Services;

use App\Support\AnswerSheetParser;
use App\Support\GujaratiTextNormalizer;
use Illuminate\Support\Facades\Http;
use RuntimeException;

class OpenAiVisionOcrService
{
    public function isConfigured(): bool
    {
        return filled(config('services.openai.key'));
    }

    /**
     * @param  array<int, string>  $imagePaths
     * @param  array<int, array{number: int, expected: string}>  $answerHints
     * @return array{transcription: string, answers: array<int, string>}
     */
    public function extractSheetData(array $imagePaths, array $answerHints = []): array
    {
        $key = config('services.openai.key');

        if (! filled($key)) {
            throw new RuntimeException('OpenAI API key is not configured.');
        }

        if ($imagePaths === []) {
            throw new RuntimeException('No images available for OCR.');
        }

        $hintText = $this->buildHintText($answerHints);

        $content = [
            [
                'type' => 'text',
                'text' => 'You are reading a Gujarati/English handwritten school answer sheet (pencil/pen). '
                    .'Find EVERY numbered question on ALL pages (Q.1, Q1, Q-1, 1., etc). Do not skip any. '
                    .'Each block may look like: "Q1. question text?" then "Ans- answer text". '
                    .'Extract BOTH the question text written on the sheet AND the answer after Ans/Answer/જવાબ. '
                    .'Keep sheet numbering as written. Do not invent text. '
                    .'For each item also estimate position: page_index (0-based among images sent) and '
                    .'y_percent (0-100, vertical position of the answer end / mark spot on THAT page). '
                    .'Return strict JSON: '
                    .'{"transcription":"full line by line text",'
                    .'"items":[{"number":1,"question":"question text from sheet","answer":"student answer only","page_index":0,"y_percent":35}],'
                    .'"answers":[{"number":1,"answer":"student answer only"}]}. '
                    .'answers[] must mirror items[].answer. Include one item per question found. '
                    .$hintText,
            ],
        ];

        foreach ($imagePaths as $path) {
            $bytes = file_get_contents($path);

            if ($bytes === false) {
                continue;
            }

            $content[] = [
                'type' => 'image_url',
                'image_url' => [
                    'url' => 'data:'.$this->mimeForPath($path).';base64,'.base64_encode($bytes),
                    'detail' => 'auto',
                ],
            ];
        }

        $response = Http::withToken($key)
            ->timeout(90)
            ->post('https://api.openai.com/v1/chat/completions', [
                'model' => config('services.openai.vision_model', 'gpt-4o'),
                'response_format' => ['type' => 'json_object'],
                'temperature' => 0,
                'messages' => [
                    ['role' => 'user', 'content' => $content],
                ],
            ]);

        if (! $response->successful()) {
            throw new RuntimeException('OpenAI vision OCR failed: '.$response->body());
        }

        $rawContent = data_get($response->json(), 'choices.0.message.content', '{}');
        $payload = is_array($rawContent)
            ? $rawContent
            : json_decode((string) $rawContent, true);

        if (! is_array($payload)) {
            throw new RuntimeException('OpenAI vision returned invalid JSON.');
        }

        $transcription = GujaratiTextNormalizer::cleanOcr($this->stringValue($payload['transcription'] ?? ''));
        $answers = [];
        $items = [];

        foreach ($payload['items'] ?? [] as $row) {
            if (! is_array($row)) {
                continue;
            }

            $number = (int) ($row['number'] ?? 0);
            $question = $this->stringValue($row['question'] ?? '');
            $answer = $this->stringValue($row['answer'] ?? '');
            $pageIndex = max(0, (int) ($row['page_index'] ?? $row['page'] ?? 0));
            $yPercent = (float) ($row['y_percent'] ?? $row['y'] ?? 50);
            $yPercent = max(5.0, min(95.0, $yPercent));

            if ($number > 0 && $answer !== '') {
                $answers[$number] = $answer;
                $items[$number] = [
                    'number' => $number,
                    'question' => $question,
                    'answer' => $answer,
                    'page_index' => $pageIndex,
                    'y_percent' => $yPercent,
                ];
            }
        }

        foreach ($payload['answers'] ?? [] as $row) {
            if (! is_array($row)) {
                continue;
            }

            $number = (int) ($row['number'] ?? 0);
            $answer = $this->stringValue($row['answer'] ?? '');

            if ($number > 0 && $answer !== '') {
                $answers[$number] = $answers[$number] ?? $answer;
                if (! isset($items[$number])) {
                    $items[$number] = [
                        'number' => $number,
                        'question' => '',
                        'answer' => $answer,
                        'page_index' => 0,
                        'y_percent' => 50.0,
                    ];
                }
            }
        }

        if ($transcription === '' && $answers === []) {
            throw new RuntimeException('OpenAI vision could not extract answers from the sheet.');
        }

        if ($transcription === '' && $answers !== []) {
            $transcription = $this->buildTextFromAnswers($answers);
        }

        ksort($answers);
        ksort($items);

        return [
            'transcription' => $transcription,
            'answers' => $answers,
            'items' => array_values($items),
        ];
    }

    /**
     * @param  array<int, array{number: int, expected: string}>  $answerHints
     */
    private function buildHintText(array $answerHints): string
    {
        if ($answerHints === []) {
            return '';
        }

        $lines = [];

        foreach ($answerHints as $hint) {
            $number = (int) ($hint['number'] ?? 0);
            $expected = trim((string) ($hint['expected'] ?? ''));
            $question = trim((string) ($hint['question'] ?? ''));

            if ($number > 0) {
                $chunk = "Paper Q{$number}";
                if ($question !== '') {
                    $chunk .= " asks: {$question}";
                }
                if ($expected !== '') {
                    $chunk .= " (model answer hint: {$expected})";
                }
                $lines[] = $chunk;
            }
        }

        if ($lines === []) {
            return '';
        }

        return 'Use these only to help decode unclear handwriting (still transcribe what is written): '
            .implode('; ', $lines).'. ';
    }

    /**
     * @param  array<int, string>  $imagePaths
     * @return array<int, string>
     */
    public function extractStructuredAnswers(array $imagePaths, array $answerHints = []): array
    {
        $sheet = $this->extractSheetData($imagePaths, $answerHints);

        return AnswerSheetParser::sanitize($sheet['answers'], $sheet['transcription']);
    }

    /**
     * @param  array<int, string>  $imagePaths
     */
    public function extractTextFromImages(array $imagePaths): string
    {
        $key = config('services.openai.key');

        if (! filled($key)) {
            throw new RuntimeException('OpenAI API key is not configured.');
        }

        $content = [
            [
                'type' => 'text',
                'text' => 'Transcribe this Gujarati handwritten answer sheet exactly in Gujarati script. '
                    .'Format uses Q1 -, Q2 -, Q3 - with answers on the same line. Plain text only.',
            ],
        ];

        foreach ($imagePaths as $path) {
            $bytes = file_get_contents($path);

            if ($bytes === false) {
                continue;
            }

            $content[] = [
                'type' => 'image_url',
                'image_url' => [
                    'url' => 'data:'.$this->mimeForPath($path).';base64,'.base64_encode($bytes),
                    'detail' => 'auto',
                ],
            ];
        }

        $response = Http::withToken($key)
            ->timeout(90)
            ->post('https://api.openai.com/v1/chat/completions', [
                'model' => config('services.openai.vision_model', 'gpt-4o'),
                'temperature' => 0,
                'messages' => [
                    ['role' => 'user', 'content' => $content],
                ],
            ]);

        if (! $response->successful()) {
            throw new RuntimeException('OpenAI vision OCR failed: '.$response->body());
        }

        $text = trim((string) data_get($response->json(), 'choices.0.message.content', ''));

        if ($text === '') {
            throw new RuntimeException('OpenAI vision could not read text from the uploaded sheet.');
        }

        return $text;
    }

    /**
     * @param  array<int, string>  $answers
     */
    private function buildTextFromAnswers(array $answers): string
    {
        ksort($answers);
        $lines = [];

        foreach ($answers as $number => $answer) {
            $lines[] = "Q{$number} - {$answer}";
        }

        return implode("\n", $lines);
    }

    private function mimeForPath(string $path): string
    {
        return match (strtolower(pathinfo($path, PATHINFO_EXTENSION))) {
            'jpg', 'jpeg' => 'image/jpeg',
            'webp' => 'image/webp',
            'gif' => 'image/gif',
            default => 'image/png',
        };
    }

    private function stringValue(mixed $value): string
    {
        if (is_array($value)) {
            $lines = array_map(
                fn (mixed $line) => is_scalar($line) ? trim((string) $line) : '',
                $value
            );

            return trim(implode("\n", array_filter($lines)));
        }

        return trim((string) $value);
    }
}
