<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Admin\AdminSettingController;
use App\Http\Controllers\Controller;
use App\Models\AiRequest;
use App\Models\LicenseActivation;
use App\Models\Setting;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;

class AiProxyController extends Controller
{
    public function prompt(Request $request): JsonResponse
    {
        $request->validate([
            'prompt'  => 'required|string|max:10000',
            'context' => 'nullable|string|max:20000',
            'type'    => 'nullable|string|in:assistant,widget,report',
            'history' => 'nullable|array|max:20',
            'history.*.role'    => 'required_with:history|string|in:user,assistant',
            'history.*.content' => 'required_with:history|string|max:5000',
        ]);

        $activation = $this->resolveActivation($request);
        if ($activation instanceof JsonResponse) {
            return $activation;
        }

        $type         = $request->string('type', 'assistant')->toString();
        $model        = Setting::get('openai_model', config('services.openai.model', 'gpt-5-mini'));
        $maxTokensReq = $type === 'assistant'
            ? (int) Setting::get('openai_max_tokens', config('services.openai.max_tokens', 2000))
            : 1500;

        $systemPrompt = $this->buildSystemPrompt($type);
        $messages     = $this->buildMessages(
            $this->sanitizeOutboundText($request->string('prompt')->toString()),
            $this->sanitizeOutboundText($request->input('context')),
            array_map(function (array $message): array {
                $message['content'] = $this->sanitizeOutboundText($message['content'] ?? '');
                return $message;
            }, $request->input('history', []))
        );

        $result = $this->runOpenAiRequest(
            activation: $activation,
            requestType: $type,
            model: $model,
            maxOutputTokens: $maxTokensReq,
            promptForLog: $request->string('prompt')->toString(),
            input: $this->buildOpenAiInput($systemPrompt, $messages)
        );

        if ($result instanceof JsonResponse) {
            return $result;
        }

        return response()->json([
            'response'    => $result['text'],
            'tokens_used' => $result['tokens_used'],
        ]);
    }

    public function plan(Request $request): JsonResponse
    {
        $request->validate([
            'message' => 'required|string|max:5000',
            'history' => 'nullable|array|max:10',
            'history.*.role' => 'required_with:history|string|in:user,assistant',
            'history.*.content' => 'required_with:history|string|max:2000',
            'catalog' => 'required|array',
            'catalog.metrics' => 'required|array|max:20',
            'catalog.group_by' => 'required|array|max:20',
            'catalog.filters' => 'required|array|max:20',
            'catalog.date_ranges' => 'required|array|max:10',
            'catalog.categories' => 'nullable|array|max:500',
            'catalog.attributes' => 'nullable|array|max:100',
        ]);

        $activation = $this->resolveActivation($request);
        if ($activation instanceof JsonResponse) {
            return $activation;
        }

        if (! filter_var(Setting::get('ai_planner_enabled', '1'), FILTER_VALIDATE_BOOL)) {
            return response()->json(['error' => 'AI planner is disabled.'], 403);
        }

        $catalog = $request->input('catalog', []);
        $systemPrompt = $this->buildPlannerSystemPrompt();
        $userPrompt = $this->buildPlannerPrompt(
            message: $this->sanitizeOutboundText($request->string('message')->toString()),
            history: array_map(function (array $message): array {
                $message['content'] = $this->sanitizeOutboundText($message['content'] ?? '');
                return $message;
            }, $request->input('history', [])),
            catalog: $catalog
        );

        $result = $this->runOpenAiRequest(
            activation: $activation,
            requestType: 'planner',
            model: Setting::get('ai_planner_model', Setting::get('openai_model', config('services.openai.model', 'gpt-5-mini'))),
            maxOutputTokens: 700,
            promptForLog: $request->string('message')->toString(),
            input: $this->buildOpenAiInput($systemPrompt, [
                ['role' => 'user', 'content' => $userPrompt],
            ])
        );

        if ($result instanceof JsonResponse) {
            return $result;
        }

        $plan = $this->extractJsonObject($result['text']);
        if (! is_array($plan)) {
            return response()->json(['error' => 'Planner returned invalid JSON.'], 502);
        }

        $plan = $this->sanitizePlannerOutput($plan, $catalog);
        if (! $plan) {
            return response()->json(['error' => 'Planner returned an unsupported plan.'], 502);
        }

        return response()->json([
            'plan'        => $plan,
            'tokens_used' => $result['tokens_used'],
        ]);
    }

    public function humanize(Request $request): JsonResponse
    {
        $request->validate([
            'question' => 'required|string|max:5000',
            'style' => 'nullable|string|in:concise,summary,report',
            'result' => 'required|array',
        ]);

        $activation = $this->resolveActivation($request);
        if ($activation instanceof JsonResponse) {
            return $activation;
        }

        if (! filter_var(Setting::get('ai_humanizer_enabled', '1'), FILTER_VALIDATE_BOOL)) {
            return response()->json(['error' => 'AI humaniser is disabled.'], 403);
        }

        $style = $request->string('style', 'concise')->toString();
        $systemPrompt = $this->buildHumanizerSystemPrompt($style);
        $payload = [
            'question' => $this->sanitizeOutboundText($request->string('question')->toString()),
            'result'   => $request->input('result'),
        ];

        $result = $this->runOpenAiRequest(
            activation: $activation,
            requestType: 'humanizer',
            model: Setting::get('ai_humanizer_model', Setting::get('openai_model', config('services.openai.model', 'gpt-5-mini'))),
            maxOutputTokens: 500,
            promptForLog: $request->string('question')->toString(),
            input: $this->buildOpenAiInput($systemPrompt, [
                ['role' => 'user', 'content' => json_encode($payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)],
            ])
        );

        if ($result instanceof JsonResponse) {
            return $result;
        }

        return response()->json([
            'response'    => trim($result['text']),
            'tokens_used' => $result['tokens_used'],
        ]);
    }

    private function resolveActivation(Request $request): LicenseActivation|JsonResponse
    {
        $token = $request->bearerToken() ?? $request->header('X-License-Token');
        if (! $token) {
            return response()->json(['error' => 'Missing authorization token.'], 401);
        }

        try {
            $payload = JWT::decode($token, new Key(config('app.key'), 'HS256'));
        } catch (\Exception) {
            return response()->json(['error' => 'Invalid or expired token.'], 401);
        }

        $activation = LicenseActivation::with('license')->find($payload->site_id);
        if (! $activation || $activation->status !== 'active') {
            return response()->json(['error' => 'License activation not found or inactive.'], 403);
        }

        if (! $activation->license->isValid()) {
            return response()->json(['error' => 'License is no longer valid.'], 403);
        }

        $dailyCount = $activation->todayRequestCount();
        $dailyLimit = (int) Setting::get('rate_limit_per_day', env('RATE_LIMIT_PER_DAY', 200));
        if ($dailyLimit > 0 && $dailyCount >= $dailyLimit) {
            return response()->json(['error' => 'Daily request limit reached.'], 429);
        }

        $globalTokensToday = AiRequest::whereDate('created_at', today())->sum('tokens_used');
        $maxTokens = (int) Setting::get('max_tokens_per_day', env('MAX_TOKENS_PER_DAY', 500000));
        if ($maxTokens > 0 && $globalTokensToday >= $maxTokens) {
            return response()->json(['error' => 'Global daily token limit reached.'], 429);
        }

        $activation->update(['last_seen_at' => now()]);

        return $activation;
    }

    private function runOpenAiRequest(
        LicenseActivation $activation,
        string $requestType,
        string $model,
        int $maxOutputTokens,
        string $promptForLog,
        array $input
    ): array|JsonResponse {
        $apiKey = $this->resolveOpenAiKey();
        if (! $apiKey) {
            return response()->json(['error' => 'AI service not configured.'], 500);
        }

        $startTime = microtime(true);

        try {
            $response = Http::withHeaders([
                'Authorization' => 'Bearer ' . $apiKey,
                'Content-Type' => 'application/json',
            ])->timeout(60)->post('https://api.openai.com/v1/responses', [
                'model' => $model,
                'max_output_tokens' => $maxOutputTokens,
                'input' => $input,
            ]);

            $responseTime = (int) ((microtime(true) - $startTime) * 1000);

            if (! $response->successful()) {
                $message = $response->json('error.message') ?: 'AI service error.';
                return response()->json(['error' => $message], 502);
            }

            $data = $response->json();
            $content = $this->extractOpenAiText($data);
            $tokensUsed = (int) (($data['usage']['input_tokens'] ?? 0)
                + ($data['usage']['output_tokens'] ?? 0)
                + ($data['usage']['output_tokens_details']['reasoning_tokens'] ?? 0));

            AiRequest::create([
                'license_activation_id' => $activation->id,
                'domain'                => $activation->domain,
                'prompt'                => $promptForLog,
                'response'              => $content,
                'request_type'          => $requestType,
                'tokens_used'           => $tokensUsed,
                'response_time_ms'      => $responseTime,
            ]);

            return [
                'text' => $content,
                'tokens_used' => $tokensUsed,
            ];
        } catch (\Exception $e) {
            return response()->json(['error' => 'AI service unavailable.'], 502);
        }
    }

    private function resolveOpenAiKey(): ?string
    {
        $storedApiKey = Setting::get('openai_api_key_enc', '');
        return AdminSettingController::decryptStoredApiKey($storedApiKey) ?: config('services.openai.api_key');
    }

    private function buildSystemPrompt(string $type): string
    {
        $date = now()->format('Y-m-d');

        return match ($type) {
            'widget' =>
                "Today: {$date}. You are a WooCommerce analytics assistant. " .
                "You will receive pre-fetched store data and a prompt describing a widget. " .
                "Return ONLY valid JSON with no markdown fences or explanation.",
            'report' =>
                "Today: {$date}. You are a WooCommerce analytics report writer. " .
                "Write 2-3 short paragraphs using only the supplied aggregate data. " .
                "Return flowing prose only.",
            default =>
                "Today: {$date}. You are an AI analytics assistant for WooCommerce. " .
                "Use only the supplied store data. Be concise, specific, and direct.",
        };
    }

    private function buildPlannerSystemPrompt(): string
    {
        return "You convert WooCommerce analytics questions into a safe structured query plan. " .
            "Return ONLY valid JSON. Do not answer the question directly. " .
            "Choose only metrics, groupings, date ranges, and filter types that appear in the supplied catalog. " .
            "Never invent fields. Prefer simple plans the plugin can run locally. " .
            "Use filters as objects with keys type and value. Include taxonomy for product_attribute filters when known. " .
            "Output shape: " .
            "{\"metric\":\"...\",\"group_by\":null|\"...\",\"group_key\":null|\"...\",\"group_label\":null|\"...\",\"time_range\":\"...\",\"filters\":[{\"type\":\"...\",\"value\":\"...\"}],\"sort\":\"desc\",\"limit\":10,\"confidence\":0.0}";
    }

    private function buildHumanizerSystemPrompt(string $style): string
    {
        $styleInstruction = match ($style) {
            'summary' => 'Write a short summary paragraph.',
            'report' => 'Write 2 concise analytical paragraphs.',
            default => 'Write one concise answer in plain language.',
        };

        return "You rewrite local WooCommerce analytics results into merchant-friendly language. " .
            "Use only the supplied aggregate result JSON. Do not invent figures. " .
            "Do not mention personal data. {$styleInstruction}";
    }

    private function buildPlannerPrompt(string $message, array $history, array $catalog): string
    {
        return "User question:\n{$message}\n\n" .
            "Recent history:\n" . json_encode($history, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n\n" .
            "Supported query catalog:\n" . json_encode($catalog, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
    }

    private function buildMessages(string $prompt, ?string $context, array $history): array
    {
        $firstContent = $context ? "Store data:\n{$context}\n\n{$prompt}" : $prompt;
        if (empty($history)) {
            return [['role' => 'user', 'content' => $firstContent]];
        }

        $messages = [];
        foreach ($history as $i => $msg) {
            $content = $i === 0 && $context
                ? "Store data:\n{$context}\n\n{$msg['content']}"
                : $msg['content'];
            $messages[] = ['role' => $msg['role'], 'content' => $content];
        }
        $messages[] = ['role' => 'user', 'content' => $prompt];

        return $messages;
    }

    private function buildOpenAiInput(string $systemPrompt, array $messages): array
    {
        $input = [[
            'role' => 'system',
            'content' => [[
                'type' => 'input_text',
                'text' => $systemPrompt,
            ]],
        ]];

        foreach ($messages as $message) {
            $input[] = [
                'role' => $message['role'],
                'content' => [[
                    'type' => 'input_text',
                    'text' => $message['content'],
                ]],
            ];
        }

        return $input;
    }

    private function extractOpenAiText(array $data): string
    {
        if (! empty($data['output_text']) && is_string($data['output_text'])) {
            return $data['output_text'];
        }

        $chunks = [];
        foreach (($data['output'] ?? []) as $outputItem) {
            foreach (($outputItem['content'] ?? []) as $contentItem) {
                if (($contentItem['type'] ?? '') === 'output_text' && isset($contentItem['text'])) {
                    $chunks[] = $contentItem['text'];
                }
            }
        }

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

    private function extractJsonObject(string $text): ?array
    {
        $trimmed = trim($text);
        $trimmed = preg_replace('/^```(?:json)?\s*/i', '', $trimmed);
        $trimmed = preg_replace('/\s*```$/', '', $trimmed);

        $decoded = json_decode($trimmed, true);
        if (is_array($decoded)) {
            return $decoded;
        }

        if (preg_match('/\{.*\}/s', $trimmed, $matches)) {
            $decoded = json_decode($matches[0], true);
            if (is_array($decoded)) {
                return $decoded;
            }
        }

        return null;
    }

    private function sanitizePlannerOutput(array $plan, array $catalog): ?array
    {
        $metric = (string) ($plan['metric'] ?? '');
        if (! in_array($metric, $catalog['metrics'] ?? [], true)) {
            return null;
        }

        $groupBy = $plan['group_by'] ?? null;
        if ($groupBy !== null && ! in_array($groupBy, $catalog['group_by'] ?? [], true)) {
            $groupBy = null;
        }

        $timeRange = (string) ($plan['time_range'] ?? '6m');
        if (! in_array($timeRange, $catalog['date_ranges'] ?? [], true)) {
            $timeRange = '6m';
        }

        $safePlan = [
            'metric' => $metric,
            'group_by' => $groupBy,
            'group_key' => $groupBy === 'attribute' ? ($plan['group_key'] ?? null) : null,
            'group_label' => $groupBy === 'attribute' ? ($plan['group_label'] ?? null) : null,
            'time_range' => $timeRange,
            'filters' => [],
            'sort' => ($plan['sort'] ?? 'desc') === 'asc' ? 'asc' : 'desc',
            'limit' => max(1, min(20, (int) ($plan['limit'] ?? 10))),
            'confidence' => max(0.0, min(0.99, (float) ($plan['confidence'] ?? 0.75))),
        ];

        foreach ((array) ($plan['filters'] ?? []) as $filter) {
            if (! is_array($filter)) {
                continue;
            }
            $type = (string) ($filter['type'] ?? '');
            if (! in_array($type, $catalog['filters'] ?? [], true)) {
                continue;
            }

            $safePlan['filters'][] = [
                'type' => $type,
                'value' => (string) ($filter['value'] ?? ''),
                'taxonomy' => isset($filter['taxonomy']) ? (string) $filter['taxonomy'] : null,
                'label' => isset($filter['label']) ? (string) $filter['label'] : null,
            ];
        }

        return $safePlan;
    }

    private function sanitizeOutboundText(?string $text): string
    {
        $text = (string) $text;
        $allowPersonalData = filter_var(Setting::get('ai_allow_personal_data', '0'), FILTER_VALIDATE_BOOL);
        $redact = filter_var(Setting::get('ai_redact_personal_data', '1'), FILTER_VALIDATE_BOOL);

        if ($allowPersonalData || ! $redact) {
            return $text;
        }

        $text = preg_replace('/[A-Z0-9._%+\-]+@[A-Z0-9.\-]+\.[A-Z]{2,}/i', '[redacted-email]', $text);
        $text = preg_replace('/\+?\d[\d\s\-\(\)]{7,}\d/', '[redacted-phone]', $text);
        $text = preg_replace('/\b\d{1,5}\s+[A-Za-z0-9.\'\-\s]{3,40}(street|st|road|rd|avenue|ave|lane|ln|drive|dr|close|way)\b/i', '[redacted-address]', $text);

        return trim($text);
    }
}
