<?php

namespace App\Services;

use App\Config\CarConsultantPrompt;
use Illuminate\Support\Facades\Cache;
use OpenAI\Client;
use OpenAI\Factory;

class LlmService
{
    private const CACHE_PREFIX = 'voice_session:';
    private const CACHE_TTL = 3600; // 1 hour

    protected function client(): Client
    {
        $apiKey = config('services.openai.api_key');
        if (empty($apiKey)) {
            throw new \RuntimeException('OPENAI_API_KEY is not set. Please add it to your .env file.');
        }
        return (new Factory())->withApiKey($apiKey)->make();
    }

    protected function getSessionState(string $sessionId): array
    {
        $key = self::CACHE_PREFIX . $sessionId;
        $state = Cache::get($key);
        if ($state === null) {
            $state = [
                'current_state' => CarConsultantPrompt::INITIAL_STATE,
                'collected_data' => [],
            ];
            Cache::put($key, $state, self::CACHE_TTL);
        }
        return $state;
    }

    protected function putSessionState(string $sessionId, array $state): void
    {
        Cache::put(self::CACHE_PREFIX . $sessionId, $state, self::CACHE_TTL);
    }

    protected function parseAgentResponse(?string $content): ?array
    {
        if ($content === null || $content === '') {
            return null;
        }
        $trimmed = trim($content);
        try {
            return json_decode($trimmed, true, 512, JSON_THROW_ON_ERROR);
        } catch (\JsonException $e) {
            // Try extract from ```json ... ``` or {...}
            if (preg_match('/```(?:json)?\s*([\s\S]*?)```/', $trimmed, $m)) {
                $str = trim($m[1]);
            } elseif (preg_match('/\{[\s\S]*\}/', $trimmed, $m)) {
                $str = $m[0];
            } else {
                return null;
            }
            try {
                return json_decode($str, true, 512, JSON_THROW_ON_ERROR);
            } catch (\JsonException $e2) {
                return null;
            }
        }
    }

    /**
     * Path to the editable instructions file (used by the in-app Agent Instructions page).
     */
    public static function getEditableInstructionsPath(): string
    {
        return storage_path('app/agent_instructions.txt');
    }

    /**
     * Resolve agent instructions: from env, from OPENAI_AGENT_INSTRUCTIONS_FILE, or from the editable file (storage/app/agent_instructions.txt).
     */
    protected function getAgentInstructions(): ?string
    {
        $custom = config('services.openai.agent_instructions');
        if ($custom !== null && trim((string) $custom) !== '') {
            return trim($custom);
        }
        $path = config('services.openai.agent_instructions_file');
        if ($path !== null && $path !== '') {
            $fullPath = base_path($path);
            if (is_file($fullPath) && is_readable($fullPath)) {
                return trim((string) file_get_contents($fullPath));
            }
        }
        $editablePath = self::getEditableInstructionsPath();
        if (is_file($editablePath) && is_readable($editablePath)) {
            return trim((string) file_get_contents($editablePath));
        }
        return null;
    }

    /**
     * Get system prompt: custom instructions (Custom GPT style) or built-in car consultant, or both.
     */
    protected function getSystemPrompt(): string
    {
        $custom = $this->getAgentInstructions();
        $builtIn = CarConsultantPrompt::SYSTEM_PROMPT;

        if (config('services.openai.simple_chat', false) && $custom !== null && $custom !== '') {
            return $custom;
        }
        if ($custom !== null && $custom !== '') {
            return $custom . "\n\n---\n\n" . $builtIn;
        }
        return $builtIn;
    }

    /**
     * Car consultant agent: structured input -> JSON output with reply, next_state, extracted_data, analysis.
     * When simple_chat is true and agent_instructions are set, runs as plain chat (no state machine, no JSON).
     *
     * @return array{reply: string, next_state: string, collected_data: array, analysis: array}
     */
    public function getLLMResponse(string $userMessage, string $sessionId = 'default', string $language = 'en'): array
    {
        $model = config('services.openai.chat_model', 'gpt-3.5-turbo');
        $simpleChat = config('services.openai.simple_chat', false);
        $customInstructions = $this->getAgentInstructions();
        $useSimpleChat = $simpleChat && $customInstructions !== null && $customInstructions !== '';

        if ($useSimpleChat) {
            return $this->getLLMResponseSimple($userMessage, $sessionId, $language, $model);
        }

        $state = $this->getSessionState($sessionId);

        $structuredInput = [
            'mode' => 'voice_india',
            'current_state' => $state['current_state'],
            'collected_data' => $state['collected_data'],
            'user_message' => $userMessage,
        ];

        $userContent = "Respond in the same language as the user (language code: {$language}). Return ONLY the JSON object, no other text.\n\nInput:\n" . json_encode($structuredInput, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);

        $response = $this->client()->chat()->create([
            'model' => $model,
            'messages' => [
                ['role' => 'system', 'content' => $this->getSystemPrompt()],
                ['role' => 'user', 'content' => $userContent],
            ],
            'temperature' => 0.4,
            'max_tokens' => 400,
        ]);

        $rawContent = $response->choices[0]->message->content ?? '';
        $parsed = $this->parseAgentResponse($rawContent);

        if ($parsed !== null && isset($parsed['reply']) && is_string($parsed['reply'])) {
            $state['current_state'] = $parsed['next_state'] ?? $state['current_state'];
            if (!empty($parsed['extracted_data']) && is_array($parsed['extracted_data'])) {
                $state['collected_data'] = array_merge($state['collected_data'], $parsed['extracted_data']);
            }
            $this->putSessionState($sessionId, $state);

            return [
                'reply' => $parsed['reply'],
                'next_state' => $state['current_state'],
                'collected_data' => $state['collected_data'],
                'analysis' => $parsed['analysis'] ?? [],
            ];
        }

        return [
            'reply' => $rawContent ?: 'Sorry, I did not get that. Could you repeat?',
            'next_state' => $state['current_state'],
            'collected_data' => $state['collected_data'],
            'analysis' => [],
        ];
    }

    /**
     * Simple chat mode: use only custom instructions (e.g. from Custom GPT), no state machine, no JSON.
     */
    protected function getLLMResponseSimple(string $userMessage, string $sessionId, string $language, string $model): array
    {
        $systemPrompt = $this->getAgentInstructions() ?? '';
        $systemPrompt .= "\n\nRespond in the same language as the user when possible (language code: {$language}). Keep voice responses concise (1–3 sentences) for a voice chat.";

        $response = $this->client()->chat()->create([
            'model' => $model,
            'messages' => [
                ['role' => 'system', 'content' => $systemPrompt],
                ['role' => 'user', 'content' => $userMessage],
            ],
            'temperature' => 0.7,
            'max_tokens' => 500,
        ]);

        $reply = $response->choices[0]->message->content ?? '';

        return [
            'reply' => $reply ?: 'I didn’t catch that. Could you say it again?',
            'next_state' => 'complete',
            'collected_data' => [],
            'analysis' => [],
        ];
    }

    public function clearHistory(string $sessionId = 'default'): void
    {
        Cache::forget(self::CACHE_PREFIX . $sessionId);
    }
}
