<?php

namespace App\Http\Controllers\Api\V1;

use App\Http\Controllers\Controller;
use App\Services\Auth\ApiKeyService;
use App\Services\System\SettingsService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use RuntimeException;

class EmojiApiController extends Controller
{
    private const TIER_FREE = 'free';

    private const TIER_PRO = 'pro';

    /** @var array<string,mixed>|null */
    private static ?array $dataset = null;

    public function __construct(
        private readonly ApiKeyService $apiKeys
    ) {}

    /** @var array<string,string> */
    private const CATEGORY_MAP = [
        'all' => 'all',
        'smileys' => 'Smileys & Emotion',
        'people' => 'People & Body',
        'animals' => 'Animals & Nature',
        'food' => 'Food & Drink',
        'travel' => 'Travel & Places',
        'activities' => 'Activities',
        'objects' => 'Objects',
        'symbols' => 'Symbols',
        'flags' => 'Flags',
    ];

    public function categories(Request $request): JsonResponse
    {
        if ($blocked = $this->enforcePublicAccess($request)) {
            return $blocked;
        }

        $tier = $this->detectTier($request);
        try {
            $data = $this->loadData();
        } catch (RuntimeException $e) {
            return $this->jsonWithTier($request, [
                'error' => 'data_load_failed',
                'message' => $e->getMessage(),
            ], $tier, 500);
        }

        $items = $data['emojis'] ?? [];
        $map = [];
        foreach ($items as $item) {
            $category = trim((string) ($item['category'] ?? ''));
            $subcategory = trim((string) ($item['subcategory'] ?? ''));
            if ($category === '' || $subcategory === '') {
                continue;
            }

            $map[$category] ??= [];
            $map[$category][$subcategory] = true;
        }

        $out = [];
        foreach ($map as $category => $subcategories) {
            $out[$category] = array_keys($subcategories);
            sort($out[$category], SORT_NATURAL | SORT_FLAG_CASE);
        }
        ksort($out, SORT_NATURAL | SORT_FLAG_CASE);

        $etag = '"'.sha1(json_encode($out)).'"';
        if ($this->isNotModified($request, $etag)) {
            return $this->jsonWithTier($request, [], $tier, 304, [
                'ETag' => $etag,
                'Cache-Control' => 'public, max-age=3600',
            ]);
        }

        return $this->jsonWithTier($request, $out, $tier, 200, [
            'ETag' => $etag,
            'Cache-Control' => 'public, max-age=3600',
        ]);
    }

    public function emojis(Request $request): JsonResponse
    {
        if ($blocked = $this->enforcePublicAccess($request)) {
            return $blocked;
        }

        $tier = $this->detectTier($request);
        try {
            $data = $this->loadData();
        } catch (RuntimeException $e) {
            return $this->jsonWithTier($request, [
                'error' => 'data_load_failed',
                'message' => $e->getMessage(),
            ], $tier, 500);
        }

        $items = $data['emojis'] ?? [];
        $q = trim((string) ($request->query('q', $request->query('query', ''))));
        $category = $this->normalizeCategoryFilter((string) $request->query('category', ''));
        $subSlug = $this->slugify((string) $request->query('subcategory', ''));
        $page = max((int) $request->query('page', 1), 1);

        $defaultLimit = max((int) config('dewemoji.pagination.default_limit', 20), 1);
        // Search result pagination is now feature-parity for all users.
        // Keyword/glossary limits are enforced elsewhere (user_keywords quota logic).
        $maxLimit = max((int) config('dewemoji.pagination.max_limit', 50), 1);
        $limit = min(max((int) $request->query('limit', $defaultLimit), 1), $maxLimit);

        $filtered = $this->filterItems($items, $q, $category, $subSlug);

        // Signed-in users can enrich search with their active private keywords.
        $user = $this->apiKeys->resolveUser($request) ?? $request->user();
        if ($user && $q !== '') {
            $bySlug = [];
            foreach ($filtered as $row) {
                $slug = (string) ($row['slug'] ?? '');
                if ($slug !== '') {
                    $bySlug[$slug] = $row;
                }
            }

            $itemsBySlug = [];
            foreach ($items as $row) {
                $slug = (string) ($row['slug'] ?? '');
                if ($slug !== '') {
                    $itemsBySlug[$slug] = $row;
                }
            }

            $privateRows = DB::table('user_keywords')
                ->where('user_id', $user->id)
                ->where('is_active', true)
                ->whereRaw('LOWER(keyword) LIKE ?', ['%'.strtolower($q).'%'])
                ->get(['id', 'emoji_slug', 'keyword', 'lang']);

            foreach ($privateRows as $privateRow) {
                $slug = (string) $privateRow->emoji_slug;
                if ($slug === '' || isset($bySlug[$slug])) {
                    continue;
                }

                $sourceItem = $itemsBySlug[$slug] ?? null;
                if (! $sourceItem) {
                    continue;
                }

                // Preserve current category/subcategory filters.
                $itemCategory = trim((string) ($sourceItem['category'] ?? ''));
                $itemSubcategory = trim((string) ($sourceItem['subcategory'] ?? ''));
                if ($category !== '' && strcasecmp($itemCategory, $category) !== 0) {
                    continue;
                }
                if ($subSlug !== '' && $this->slugify($itemSubcategory) !== $subSlug) {
                    continue;
                }

                $sourceItem['source'] = 'private';
                $sourceItem['matched_keyword_id'] = (int) $privateRow->id;
                $sourceItem['matched_keyword'] = (string) $privateRow->keyword;
                $sourceItem['matched_lang'] = (string) $privateRow->lang;
                $bySlug[$slug] = $sourceItem;
            }

            $filtered = array_values($bySlug);
        }

        $total = count($filtered);
        $offset = ($page - 1) * $limit;
        $pageItems = array_slice($filtered, $offset, $limit);
        $outItems = array_map(function (array $item) use ($tier): array {
            $out = $this->transformItem($item, $tier);
            if (isset($item['source'])) {
                $out['source'] = (string) $item['source'];
                $out['matched_keyword_id'] = $item['matched_keyword_id'] ?? null;
                $out['matched_keyword'] = $item['matched_keyword'] ?? null;
                $out['matched_lang'] = $item['matched_lang'] ?? null;
            }

            return $out;
        }, $pageItems);

        $responsePayload = [
            'items' => $outItems,
            'total' => $total,
            'page' => $page,
            'limit' => $limit,
            'plan' => $tier,
        ];

        $etag = '"'.sha1(json_encode([$responsePayload, $tier, $q, $category, $subSlug])).'"';
        if ($this->isNotModified($request, $etag)) {
            return $this->jsonWithTier($request, [], $tier, 304, [
                'ETag' => $etag,
                'Cache-Control' => 'public, max-age=120',
            ]);
        }

        return $this->jsonWithTier($request, $responsePayload, $tier, 200, [
            'ETag' => $etag,
            'Cache-Control' => 'public, max-age=120',
        ]);
    }

    public function search(Request $request): JsonResponse
    {
        $private = filter_var($request->query('private', false), FILTER_VALIDATE_BOOL);
        if (! $private) {
            return $this->emojis($request);
        }

        $user = $this->apiKeys->resolveUser($request) ?? $request->user();
        if (! $user) {
            return response()->json(['ok' => false, 'error' => 'unauthorized'], 401);
        }
        $q = trim((string) ($request->query('q', $request->query('query', ''))));
        $category = $this->normalizeCategoryFilter((string) $request->query('category', ''));
        $subSlug = $this->slugify((string) $request->query('subcategory', ''));

        try {
            $data = $this->loadData();
        } catch (RuntimeException $e) {
            return $this->jsonWithTier($request, [
                'error' => 'data_load_failed',
                'message' => $e->getMessage(),
            ], self::TIER_FREE, 500);
        }

        $items = $data['emojis'] ?? [];
        $itemsBySlug = [];
        foreach ($items as $item) {
            $slug = (string) ($item['slug'] ?? '');
            if ($slug !== '') {
                $itemsBySlug[$slug] = $item;
            }
        }
        $filtered = $this->filterItems($items, $q, $category, $subSlug);
        $publicBySlug = [];
        foreach ($filtered as $item) {
            $slug = (string) ($item['slug'] ?? '');
            if ($slug !== '') {
                $publicBySlug[$slug] = $item;
            }
        }

        $privateMatches = [];
        if ($q !== '') {
            $rows = DB::table('user_keywords')
                ->where('user_id', $user->id)
                ->where('is_active', true)
                ->whereRaw('LOWER(keyword) LIKE ?', ['%'.strtolower($q).'%'])
                ->get(['id', 'emoji_slug', 'keyword', 'lang']);

            foreach ($rows as $row) {
                $slug = (string) $row->emoji_slug;
                if ($slug === '') {
                    continue;
                }
                $privateMatches[$slug] = [
                    'id' => (int) $row->id,
                    'keyword' => (string) $row->keyword,
                    'lang' => (string) $row->lang,
                ];
            }
        }

        $tier = $this->detectTier($request);
        $merged = [];
        foreach ($privateMatches as $slug => $meta) {
            $sourceItem = $publicBySlug[$slug] ?? $itemsBySlug[$slug] ?? null;
            if ($sourceItem === null) {
                continue;
            }
            $item = $this->transformItem($sourceItem, $tier);
            $item['source'] = 'private';
            $item['matched_keyword_id'] = $meta['id'] ?? null;
            $item['matched_keyword'] = $meta['keyword'];
            $item['matched_lang'] = $meta['lang'];
            $merged[$slug] = $item;
        }

        foreach ($publicBySlug as $slug => $item) {
            if (isset($merged[$slug])) {
                continue;
            }
            $row = $this->transformItem($item, $tier);
            $row['source'] = 'public';
            $merged[$slug] = $row;
        }

        $mergedItems = array_values($merged);
        $page = max((int) $request->query('page', 1), 1);
        $limit = max((int) $request->query('limit', 20), 1);
        $total = count($mergedItems);
        $pageItems = array_slice($mergedItems, ($page - 1) * $limit, $limit);

        return response()->json([
            'ok' => true,
            'items' => $pageItems,
            'total' => $total,
            'page' => $page,
            'limit' => $limit,
            'plan' => $tier,
        ]);
    }

    public function emoji(Request $request, ?string $slug = null): JsonResponse
    {
        if ($blocked = $this->enforcePublicAccess($request)) {
            return $blocked;
        }

        $tier = $this->detectTier($request);
        $slug = trim((string) ($slug ?? $request->query('slug', '')));
        if ($slug === '') {
            return $this->jsonWithTier($request, [
                'ok' => false,
                'error' => 'missing_slug',
            ], $tier, 400);
        }

        try {
            $data = $this->loadData();
        } catch (RuntimeException $e) {
            return $this->jsonWithTier($request, [
                'error' => 'data_load_failed',
                'message' => $e->getMessage(),
            ], $tier, 500);
        }

        $items = $data['emojis'] ?? [];
        $match = null;
        foreach ($items as $item) {
            if (strcasecmp((string) ($item['slug'] ?? ''), $slug) === 0) {
                $match = $item;
                break;
            }
        }
        if ($match === null) {
            return $this->jsonWithTier($request, [
                'ok' => false,
                'error' => 'not_found',
                'slug' => $slug,
            ], $tier, 404);
        }

        $payload = $this->transformEmojiDetail($match, $tier);
        $includeUserKeywords = filter_var($request->query('include_user_keywords', false), FILTER_VALIDATE_BOOL);
        if ($includeUserKeywords) {
            $user = $this->apiKeys->resolveUser($request) ?? $request->user();
            if ($user && (string) $user->tier === 'personal') {
                $payload['user_keywords'] = DB::table('user_keywords')
                    ->where('user_id', $user->id)
                    ->where('emoji_slug', $slug)
                    ->where('is_active', true)
                    ->orderByDesc('id')
                    ->get(['id', 'keyword', 'lang']);
            } else {
                $payload['user_keywords'] = [];
            }
        }
        $etag = '"'.sha1(json_encode([$payload, $tier])).'"';
        if ($this->isNotModified($request, $etag)) {
            return $this->jsonWithTier($request, [], $tier, 304, [
                'ETag' => $etag,
                'Cache-Control' => 'public, max-age=300',
            ]);
        }

        return $this->jsonWithTier($request, $payload, $tier, 200, [
            'ETag' => $etag,
            'Cache-Control' => 'public, max-age=300',
        ]);
    }

    private function detectTier(Request $request): string
    {
        $apiUser = $this->apiKeys->resolveUser($request);
        if ($apiUser && (string) $apiUser->tier === 'personal') {
            return self::TIER_PRO;
        }

        $webUser = $request->user();
        if ($webUser && (string) $webUser->tier === 'personal') {
            return self::TIER_PRO;
        }

        return self::TIER_FREE;
    }

    private function enforcePublicAccess(Request $request): ?JsonResponse
    {
        $settings = app(SettingsService::class);
        $maintenanceEnabled = (bool) $settings->get('maintenance_enabled', false);
        if ($maintenanceEnabled) {
            return $this->jsonWithTier($request, [
                'ok' => false,
                'error' => 'maintenance',
            ], self::TIER_FREE, 503);
        }

        return null;
    }

    private function isNotModified(Request $request, string $etag): bool
    {
        $ifNoneMatch = trim((string) $request->header('If-None-Match', ''));

        return $ifNoneMatch !== '' && $ifNoneMatch === $etag;
    }

    /**
     * @return array<string,mixed>
     */
    private function loadData(): array
    {
        if (self::$dataset !== null) {
            return self::$dataset;
        }

        $settings = app(SettingsService::class);
        $activePath = (string) $settings->get('emoji_dataset_active_path', '');
        $path = $activePath !== '' && is_file($activePath)
            ? $activePath
            : (string) config('dewemoji.data_path');

        if (! is_file($path)) {
            throw new RuntimeException('Emoji dataset file was not found at: '.$path);
        }

        $raw = file_get_contents($path);
        if ($raw === false) {
            throw new RuntimeException('Emoji dataset file could not be read.');
        }

        $decoded = json_decode($raw, true);
        if (! is_array($decoded)) {
            throw new RuntimeException('Emoji dataset JSON is invalid.');
        }

        self::$dataset = $decoded;

        return self::$dataset;
    }

    private function normalizeCategoryFilter(string $category): string
    {
        $value = strtolower(trim($category));
        if ($value === '' || $value === 'all') {
            return '';
        }

        return self::CATEGORY_MAP[$value] ?? $category;
    }

    private function slugify(string $text): string
    {
        $value = strtolower(trim($text));
        $value = str_replace('&', 'and', $value);
        $value = preg_replace('/[^a-z0-9]+/', '-', $value) ?? '';

        return trim($value, '-');
    }

    /**
     * @param  array<int,array<string,mixed>>  $items
     * @return array<int,array<string,mixed>>
     */
    private function filterItems(array $items, string $q, string $category, string $subSlug): array
    {
        return array_values(array_filter($items, function (array $item) use ($q, $category, $subSlug): bool {
            $itemCategory = trim((string) ($item['category'] ?? ''));
            $itemSubcategory = trim((string) ($item['subcategory'] ?? ''));

            if ($category !== '' && strcasecmp($itemCategory, $category) !== 0) {
                return false;
            }
            if ($subSlug !== '' && $this->slugify($itemSubcategory) !== $subSlug) {
                return false;
            }
            if ($q === '') {
                return true;
            }

            $haystack = strtolower(implode(' ', [
                (string) ($item['emoji'] ?? ''),
                (string) ($item['name'] ?? ''),
                (string) ($item['slug'] ?? ''),
                $itemCategory,
                $itemSubcategory,
                implode(' ', $item['keywords_en'] ?? []),
                implode(' ', $item['keywords_id'] ?? []),
                implode(' ', $item['aliases'] ?? []),
                implode(' ', $item['shortcodes'] ?? []),
                implode(' ', $item['alt_shortcodes'] ?? []),
                implode(' ', $item['intent_tags'] ?? []),
            ]));

            $tokens = preg_split('/\s+/', strtolower($q)) ?: [];
            foreach ($tokens as $token) {
                if ($token === '') {
                    continue;
                }
                if (! str_contains($haystack, $token)) {
                    return false;
                }
            }

            return true;
        }));
    }

    /**
     * @param  array<string,mixed>  $item
     * @return array<string,mixed>
     */
    private function transformItem(array $item, string $tier): array
    {
        $supportsTone = (bool) ($item['supports_skin_tone'] ?? false);
        $emoji = (string) ($item['emoji'] ?? '');

        $out = [
            'emoji' => $emoji,
            'name' => (string) ($item['name'] ?? ''),
            'slug' => (string) ($item['slug'] ?? ''),
            'category' => (string) ($item['category'] ?? ''),
            'subcategory' => (string) ($item['subcategory'] ?? ''),
            'supports_skin_tone' => $supportsTone,
            'summary' => $this->summary((string) ($item['description'] ?? ''), 150),
        ];

        if ($supportsTone && $emoji !== '') {
            $base = preg_replace('/\x{1F3FB}|\x{1F3FC}|\x{1F3FD}|\x{1F3FE}|\x{1F3FF}/u', '', $emoji) ?? $emoji;
            $out['emoji_base'] = $base;
            $out['variants'] = array_map(
                fn (string $tone): string => $base.$tone,
                ["\u{1F3FB}", "\u{1F3FC}", "\u{1F3FD}", "\u{1F3FE}", "\u{1F3FF}"]
            );
        }

        if ($tier === self::TIER_PRO) {
            $out += [
                'unified' => (string) ($item['unified'] ?? ''),
                'codepoints' => $item['codepoints'] ?? [],
                'shortcodes' => $item['shortcodes'] ?? [],
                'aliases' => $item['aliases'] ?? [],
                'keywords_en' => array_slice($item['keywords_en'] ?? [], 0, 30),
                'keywords_id' => array_slice($item['keywords_id'] ?? [], 0, 30),
                'related' => $item['related'] ?? [],
                'intent_tags' => $item['intent_tags'] ?? [],
                'description' => (string) ($item['description'] ?? ''),
            ];
        }

        return $out;
    }

    /**
     * @param  array<string,mixed>  $item
     * @return array<string,mixed>
     */
    private function transformEmojiDetail(array $item, string $tier): array
    {
        $payload = $this->transformItem($item, $tier);
        $payload['permalink'] = (string) ($item['permalink'] ?? '');
        $payload['title'] = (string) ($item['title'] ?? $item['name'] ?? '');
        $payload['meta_title'] = (string) ($item['meta_title'] ?? '');
        $payload['meta_description'] = (string) ($item['meta_description'] ?? '');
        $payload['usage_examples'] = $item['usage_examples'] ?? [];
        $payload['alt_shortcodes'] = $item['alt_shortcodes'] ?? [];

        return $payload;
    }

    private function summary(string $text, int $max): string
    {
        $text = trim(preg_replace('/\s+/', ' ', strip_tags($text)) ?? '');
        if (mb_strlen($text) <= $max) {
            return $text;
        }

        return rtrim(mb_substr($text, 0, $max - 1), ' ,.;:-').'…';
    }

    /**
     * @param  array<string,mixed>  $payload
     * @param  array<string,string>  $extraHeaders
     */
    private function jsonWithTier(Request $request, array $payload, string $tier, int $status = 200, array $extraHeaders = []): JsonResponse
    {
        $headers = [
            'X-Dewemoji-Tier' => $tier,
            'X-Dewemoji-Plan' => $tier,
            'Vary' => 'Origin',
            'Access-Control-Allow-Methods' => (string) config('dewemoji.cors.allow_methods', 'GET, POST, OPTIONS'),
            'Access-Control-Allow-Headers' => (string) config('dewemoji.cors.allow_headers', 'Content-Type, Authorization, X-Api-Key, X-Admin-Token, X-Account-Id, X-Dewemoji-Frontend, X-Extension-Token'),
        ];

        $origin = (string) $request->headers->get('Origin', '');
        $allowedOrigins = config('dewemoji.cors.allowed_origins', []);
        if (is_array($allowedOrigins) && in_array($origin, $allowedOrigins, true)) {
            $headers['Access-Control-Allow-Origin'] = $origin;
        } elseif ((bool) config('dewemoji.cors.allow_all_public', true)) {
            $headers['Access-Control-Allow-Origin'] = '*';
        }

        return response()->json($payload, $status, $headers + $extraHeaders);
    }
}
