<?php

namespace App\Services\EmojiCatalog;

use App\Services\System\SettingsService;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use RuntimeException;

class EmojiCatalogService
{
    public function __construct(
        private readonly SettingsService $settings
    ) {}

    /**
     * @return array<string,mixed>|null
     */
    public function findItem(int $emojiId): ?array
    {
        $base = DB::table('emojis')->where('emoji_id', $emojiId)->first();
        if (! $base) {
            return null;
        }

        return $this->hydrateEditorItem((array) $base);
    }

    public function nextEmojiId(): int
    {
        return (int) DB::table('emojis')->max('emoji_id') + 1;
    }

    /**
     * @param  array<string,mixed>  $input
     */
    public function saveItem(array $input): int
    {
        $payload = $this->normalizeItemPayload($input);
        if ($payload['slug'] === '' || $payload['name'] === '') {
            throw new RuntimeException('Slug and name are required.');
        }

        $incomingId = (int) ($input['emoji_id'] ?? 0);
        $existingBySlug = DB::table('emojis')->where('slug', $payload['slug'])->first();
        if ($incomingId > 0) {
            if ($existingBySlug && (int) $existingBySlug->emoji_id !== $incomingId) {
                throw new RuntimeException('Slug is already used by another emoji.');
            }
            $emojiId = $incomingId;
        } else {
            $emojiId = (int) ($existingBySlug->emoji_id ?? 0);
            if ($emojiId <= 0) {
                $emojiId = $this->nextEmojiId();
            }
        }

        DB::transaction(function () use ($emojiId, $payload): void {
            $exists = DB::table('emojis')->where('emoji_id', $emojiId)->exists();
            $base = [
                'emoji_id' => $emojiId,
                'slug' => $payload['slug'],
                'emoji_char' => $payload['emoji'],
                'name' => $payload['name'],
                'category' => $payload['category'],
                'subcategory' => $payload['subcategory'],
                'unified' => $payload['unified'],
                'default_presentation' => $payload['default_presentation'] ?: 'emoji',
                'version' => $payload['version'] ?: 'custom',
                'supports_skin_tone' => (bool) $payload['supports_skin_tone'],
                'permalink' => $payload['permalink'] ?: '/emoji/'.$payload['slug'],
                'description' => $payload['description'],
                'meta_title' => $payload['meta_title'],
                'meta_description' => $payload['meta_description'],
                'title' => $payload['title'],
                'updated_at' => now(),
            ];
            if ($exists) {
                DB::table('emojis')->where('emoji_id', $emojiId)->update($base);
            } else {
                $base['created_at'] = now();
                DB::table('emojis')->insert($base);
            }

            DB::table('emoji_aliases')->where('emoji_id', $emojiId)->delete();
            foreach ($payload['aliases'] as $alias) {
                DB::table('emoji_aliases')->insert([
                    'emoji_id' => $emojiId,
                    'alias' => $alias,
                ]);
            }

            DB::table('emoji_shortcodes')->where('emoji_id', $emojiId)->delete();
            foreach ($payload['shortcodes'] as $shortcode) {
                DB::table('emoji_shortcodes')->insert([
                    'emoji_id' => $emojiId,
                    'shortcode' => $shortcode,
                    'kind' => 'primary',
                ]);
            }
            foreach ($payload['alt_shortcodes'] as $shortcode) {
                DB::table('emoji_shortcodes')->insert([
                    'emoji_id' => $emojiId,
                    'shortcode' => $shortcode,
                    'kind' => 'alt',
                ]);
            }

            DB::table('emoji_keywords')
                ->where('emoji_slug', $payload['slug'])
                ->where(function ($query) {
                    $query->whereNull('owner_user_id')
                        ->orWhere('owner_user_id', '');
                })
                ->delete();

            foreach ($payload['keywords_en'] as $keyword) {
                DB::table('emoji_keywords')->insert([
                    'emoji_slug' => $payload['slug'],
                    'lang' => 'en',
                    'region' => '',
                    'keyword' => $keyword,
                    'status' => 'public',
                    'owner_user_id' => null,
                    'visibility' => 'public',
                    'public_key' => null,
                    'created_at' => now(),
                    'updated_at' => now(),
                ]);
            }
            foreach ($payload['keywords_id'] as $keyword) {
                DB::table('emoji_keywords')->insert([
                    'emoji_slug' => $payload['slug'],
                    'lang' => 'id',
                    'region' => '',
                    'keyword' => $keyword,
                    'status' => 'public',
                    'owner_user_id' => null,
                    'visibility' => 'public',
                    'public_key' => null,
                    'created_at' => now(),
                    'updated_at' => now(),
                ]);
            }
        });

        return $emojiId;
    }

    public function deleteItem(int $emojiId): void
    {
        $emoji = DB::table('emojis')->where('emoji_id', $emojiId)->first();
        if (! $emoji) {
            return;
        }

        DB::transaction(function () use ($emojiId, $emoji): void {
            DB::table('emoji_aliases')->where('emoji_id', $emojiId)->delete();
            DB::table('emoji_shortcodes')->where('emoji_id', $emojiId)->delete();
            DB::table('emoji_keywords')
                ->where('emoji_slug', (string) $emoji->slug)
                ->where(function ($query) {
                    $query->whereNull('owner_user_id')->orWhere('owner_user_id', '');
                })
                ->delete();
            DB::table('emojis')->where('emoji_id', $emojiId)->delete();
        });
    }

    /**
     * Import from JSON in non-destructive mode.
     * Existing rows (matched by slug or emoji_id) are kept as-is.
     *
     * @return array{total:int,imported:int,skipped:int}
     */
    public function importFromDataFile(string $path): array
    {
        if (! is_file($path)) {
            throw new RuntimeException('Dataset file not found: '.$path);
        }

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

        $decoded = json_decode($raw, true);
        if (! is_array($decoded) || ! is_array($decoded['emojis'] ?? null)) {
            throw new RuntimeException('Invalid emoji dataset format.');
        }

        $rows = $decoded['emojis'];
        $total = 0;
        $imported = 0;
        $skipped = 0;
        foreach ($rows as $row) {
            if (! is_array($row)) {
                continue;
            }
            $total++;
            $slug = $this->slugify((string) ($row['slug'] ?? ''));
            $emojiId = (int) ($row['emoji_id'] ?? 0);
            if ($slug !== '' && DB::table('emojis')->where('slug', $slug)->exists()) {
                $skipped++;

                continue;
            }
            if ($emojiId > 0 && DB::table('emojis')->where('emoji_id', $emojiId)->exists()) {
                $skipped++;

                continue;
            }

            $this->saveItem($row);
            $imported++;
        }

        return [
            'total' => $total,
            'imported' => $imported,
            'skipped' => $skipped,
        ];
    }

    /**
     * @param  array<string,mixed>  $input
     * @return array<string,mixed>
     */
    public function normalizeItemPayload(array $input): array
    {
        $slug = $this->slugify((string) ($input['slug'] ?? ''));
        $name = trim((string) ($input['name'] ?? ''));
        $emoji = trim((string) ($input['emoji'] ?? ''));
        $category = trim((string) ($input['category'] ?? ''));
        $subcategory = trim((string) ($input['subcategory'] ?? ''));
        $description = trim((string) ($input['description'] ?? ''));

        $permalink = trim((string) ($input['permalink'] ?? ''));
        if ($permalink === '' && $slug !== '') {
            $permalink = '/emoji/'.$slug;
        }

        $title = trim((string) ($input['title'] ?? ''));
        if ($title === '' && $name !== '') {
            $title = ($emoji !== '' ? $emoji.' ' : '').$name.' — meaning & copy';
        }

        $metaTitle = trim((string) ($input['meta_title'] ?? ''));
        if ($metaTitle === '' && $name !== '') {
            $metaTitle = ($emoji !== '' ? $emoji.' ' : '').$name.' | Meaning & Copy';
        }

        $metaDescription = trim((string) ($input['meta_description'] ?? ''));
        if ($metaDescription === '' && $description !== '') {
            $metaDescription = Str::limit($description, 160, '…');
        }

        $keywordsEn = $this->normalizeArray($input['keywords_en'] ?? []);
        $keywordsId = $this->normalizeArray($input['keywords_id'] ?? []);
        $aliases = $this->normalizeArray($input['aliases'] ?? []);
        $shortcodes = $this->normalizeArray($input['shortcodes'] ?? []);
        $altShortcodes = $this->normalizeArray($input['alt_shortcodes'] ?? []);

        return [
            'emoji_id' => isset($input['emoji_id']) ? (int) $input['emoji_id'] : null,
            'slug' => $slug,
            'emoji' => $emoji,
            'name' => $name,
            'category' => $category,
            'subcategory' => $subcategory,
            'aliases' => $aliases,
            'shortcodes' => $shortcodes,
            'alt_shortcodes' => $altShortcodes,
            'keywords_en' => $keywordsEn,
            'keywords_id' => $keywordsId,
            'usage_examples' => [],
            'description' => $description,
            'codepoints' => [],
            'unified' => trim((string) ($input['unified'] ?? '')),
            'default_presentation' => trim((string) ($input['default_presentation'] ?? '')),
            'version' => trim((string) ($input['version'] ?? '')),
            'supports_skin_tone' => filter_var($input['supports_skin_tone'] ?? false, FILTER_VALIDATE_BOOL),
            'related' => [],
            'intent_tags' => [],
            'search_tokens' => [],
            'permalink' => $permalink,
            'title' => $title,
            'meta_title' => $metaTitle,
            'meta_description' => $metaDescription,
        ];
    }

    /**
     * @return array<string,mixed>
     */
    public function publishSnapshot(?string $createdBy = null): array
    {
        $emojiRows = DB::table('emojis')->orderBy('emoji_id')->get();
        $slugs = $emojiRows->pluck('slug')->filter()->values()->all();
        $keywords = DB::table('emoji_keywords')
            ->whereIn('emoji_slug', $slugs)
            ->where(function ($query) {
                $query->whereNull('owner_user_id')->orWhere('owner_user_id', '');
            })
            ->select(['emoji_slug', 'lang', 'keyword'])
            ->get()
            ->groupBy('emoji_slug');
        $aliases = DB::table('emoji_aliases')
            ->whereIn('emoji_id', $emojiRows->pluck('emoji_id')->all())
            ->select(['emoji_id', 'alias'])
            ->get()
            ->groupBy('emoji_id');
        $shortcodes = DB::table('emoji_shortcodes')
            ->whereIn('emoji_id', $emojiRows->pluck('emoji_id')->all())
            ->select(['emoji_id', 'shortcode', 'kind'])
            ->get()
            ->groupBy('emoji_id');

        $payload = [
            '@version' => (string) ($emojiRows->first()->version ?? 'custom'),
            '@source' => 'db:emojis(+aliases,+keywords,+shortcodes)',
            'count' => $emojiRows->count(),
            'emojis' => $emojiRows->map(function ($item) use ($aliases, $shortcodes, $keywords): array {
                $itemAliases = $aliases->get($item->emoji_id, collect())->pluck('alias')->filter()->values()->all();
                $itemShortcodes = $shortcodes->get($item->emoji_id, collect());
                $primaryShortcodes = $itemShortcodes->where('kind', 'primary')->pluck('shortcode')->filter()->values()->all();
                $altShortcodes = $itemShortcodes->where('kind', 'alt')->pluck('shortcode')->filter()->values()->all();
                $itemKeywords = $keywords->get($item->slug, collect());
                $keywordsEn = $itemKeywords->where('lang', 'en')->pluck('keyword')->filter()->values()->all();
                $keywordsId = $itemKeywords->where('lang', 'id')->pluck('keyword')->filter()->values()->all();

                return [
                    'emoji' => (string) $item->emoji_char,
                    'slug' => (string) $item->slug,
                    'permalink' => (string) ($item->permalink ?: '/emoji/'.$item->slug),
                    'title' => (string) ($item->title ?: (((string) $item->emoji_char !== '' ? $item->emoji_char.' ' : '').$item->name.' — meaning & copy')),
                    'meta_title' => (string) ($item->meta_title ?: (((string) $item->emoji_char !== '' ? $item->emoji_char.' ' : '').$item->name.' | Meaning & Copy')),
                    'meta_description' => (string) ($item->meta_description ?: Str::limit((string) ($item->description ?? ''), 160, '…')),
                    'name' => (string) $item->name,
                    'aliases' => $itemAliases,
                    'shortcodes' => $primaryShortcodes,
                    'alt_shortcodes' => $altShortcodes,
                    'keywords_en' => $keywordsEn,
                    'keywords_id' => $keywordsId,
                    'usage_examples' => [],
                    'description' => (string) ($item->description ?? ''),
                    'category' => (string) $item->category,
                    'subcategory' => (string) $item->subcategory,
                    'codepoints' => [],
                    'unified' => (string) $item->unified,
                    'default_presentation' => (string) ($item->default_presentation ?: 'emoji'),
                    'version' => (string) ($item->version ?: 'custom'),
                    'supports_skin_tone' => (bool) $item->supports_skin_tone,
                    'related' => [],
                    'intent_tags' => [],
                    'search_tokens' => $this->buildSearchTokens(
                        (string) $item->emoji_char,
                        (string) $item->slug,
                        (string) $item->name,
                        (string) $item->category,
                        (string) $item->subcategory,
                        $keywordsEn,
                        $keywordsId,
                        $itemAliases,
                        $primaryShortcodes,
                        $altShortcodes,
                        [],
                        []
                    ),
                ];
            })->values()->all(),
        ];

        $snapshotDir = $this->snapshotDirectory();
        if (! is_dir($snapshotDir) && ! mkdir($snapshotDir, 0775, true) && ! is_dir($snapshotDir)) {
            throw new RuntimeException('Could not create snapshot directory.');
        }

        $version = now()->format('YmdHis');
        $filename = 'emojis-'.$version.'.json';
        $fullPath = $snapshotDir.'/'.$filename;
        $written = file_put_contents($fullPath, json_encode($payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
        if ($written === false) {
            throw new RuntimeException('Could not write snapshot file.');
        }

        DB::transaction(function () use ($version, $fullPath, $createdBy): void {
            $this->settings->setMany([
                'emoji_dataset_active_path' => $fullPath,
                'emoji_dataset_active_version' => $version,
                'emoji_dataset_last_published_at' => now()->toIso8601String(),
            ], $createdBy);
        });

        return [
            'snapshot_id' => null,
            'version' => $version,
            'file_path' => $fullPath,
            'count' => $emojiRows->count(),
        ];
    }

    /**
     * @return array<int,array{name:string,version:string,path:string,is_active:bool,modified_at:int}>
     */
    public function listSnapshots(): array
    {
        $dir = $this->snapshotDirectory();
        if (! is_dir($dir)) {
            return [];
        }

        $activePath = (string) $this->settings->get('emoji_dataset_active_path', '');
        $files = glob($dir.'/emojis-*.json') ?: [];
        $items = [];
        foreach ($files as $file) {
            if (! is_file($file)) {
                continue;
            }
            $name = basename($file);
            if (! preg_match('/^emojis-(\d{14})\.json$/', $name, $m)) {
                continue;
            }
            $items[] = [
                'name' => $name,
                'version' => $m[1],
                'path' => $file,
                'is_active' => $activePath !== '' && realpath($activePath) === realpath($file),
                'modified_at' => (int) @filemtime($file),
            ];
        }

        usort($items, static function (array $a, array $b): int {
            return $b['version'] <=> $a['version'];
        });

        return $items;
    }

    public function activateSnapshot(string $filename, ?string $updatedBy = null): array
    {
        if (! preg_match('/^emojis-(\d{14})\.json$/', $filename, $m)) {
            throw new RuntimeException('Invalid snapshot filename.');
        }

        $fullPath = $this->snapshotDirectory().'/'.$filename;
        if (! is_file($fullPath)) {
            throw new RuntimeException('Snapshot file not found.');
        }

        $version = $m[1];
        $this->settings->setMany([
            'emoji_dataset_active_path' => $fullPath,
            'emoji_dataset_active_version' => $version,
            'emoji_dataset_last_published_at' => now()->toIso8601String(),
        ], $updatedBy);

        return [
            'version' => $version,
            'file_path' => $fullPath,
        ];
    }

    /**
     * @param  array<string,mixed>  $base
     * @return array<string,mixed>
     */
    private function hydrateEditorItem(array $base): array
    {
        $emojiId = (int) ($base['emoji_id'] ?? 0);
        $slug = (string) ($base['slug'] ?? '');
        $aliases = DB::table('emoji_aliases')->where('emoji_id', $emojiId)->pluck('alias')->filter()->values()->all();
        $sc = DB::table('emoji_shortcodes')->where('emoji_id', $emojiId)->get(['shortcode', 'kind']);
        $keywords = DB::table('emoji_keywords')
            ->where('emoji_slug', $slug)
            ->where(function ($query) {
                $query->whereNull('owner_user_id')->orWhere('owner_user_id', '');
            })
            ->get(['lang', 'keyword']);

        return [
            'emoji_id' => $emojiId,
            'slug' => $slug,
            'emoji' => (string) ($base['emoji_char'] ?? ''),
            'name' => (string) ($base['name'] ?? ''),
            'category' => (string) ($base['category'] ?? ''),
            'subcategory' => (string) ($base['subcategory'] ?? ''),
            'aliases' => $aliases,
            'shortcodes' => $sc->where('kind', 'primary')->pluck('shortcode')->filter()->values()->all(),
            'alt_shortcodes' => $sc->where('kind', 'alt')->pluck('shortcode')->filter()->values()->all(),
            'keywords_en' => $keywords->where('lang', 'en')->pluck('keyword')->filter()->values()->all(),
            'keywords_id' => $keywords->where('lang', 'id')->pluck('keyword')->filter()->values()->all(),
            'usage_examples' => [],
            'description' => (string) ($base['description'] ?? ''),
            'codepoints' => [],
            'unified' => (string) ($base['unified'] ?? ''),
            'default_presentation' => (string) ($base['default_presentation'] ?? ''),
            'version' => (string) ($base['version'] ?? ''),
            'supports_skin_tone' => (bool) ($base['supports_skin_tone'] ?? false),
            'related' => [],
            'intent_tags' => [],
            'search_tokens' => [],
            'permalink' => (string) ($base['permalink'] ?? ''),
            'title' => (string) ($base['title'] ?? ''),
            'meta_title' => (string) ($base['meta_title'] ?? ''),
            'meta_description' => (string) ($base['meta_description'] ?? ''),
            'is_active' => true,
            'sort_order' => 0,
        ];
    }

    /**
     * @return array<int,string>
     */
    private function normalizeArray(mixed $input): array
    {
        if (is_string($input)) {
            $input = preg_split('/\r\n|\r|\n|,/', $input) ?: [];
        }
        if (! is_array($input)) {
            return [];
        }

        $out = [];
        foreach ($input as $value) {
            $v = trim((string) $value);
            if ($v !== '') {
                $out[] = $v;
            }
        }

        return array_values(array_unique($out));
    }

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

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

    private function snapshotDirectory(): string
    {
        return storage_path('app/private/snapshots');
    }

    /**
     * @param  array<int,string>  $keywordsEn
     * @param  array<int,string>  $keywordsId
     * @param  array<int,string>  $aliases
     * @param  array<int,string>  $shortcodes
     * @param  array<int,string>  $altShortcodes
     * @param  array<int,string>  $intentTags
     * @param  array<int,string>  $codepoints
     * @return array<int,string>
     */
    private function buildSearchTokens(
        string $emoji,
        string $slug,
        string $name,
        string $category,
        string $subcategory,
        array $keywordsEn,
        array $keywordsId,
        array $aliases,
        array $shortcodes,
        array $altShortcodes,
        array $intentTags,
        array $codepoints
    ): array {
        $tokens = [
            $emoji,
            $slug,
            $name,
            strtolower($name),
            strtolower($category),
            strtolower($subcategory),
        ];
        $tokens = array_merge($tokens, $keywordsEn, $keywordsId, $aliases, $shortcodes, $altShortcodes, $intentTags, $codepoints);
        $cleaned = [];
        foreach ($tokens as $token) {
            $t = trim((string) $token);
            if ($t !== '') {
                $cleaned[] = $t;
            }
        }

        return array_values(array_unique($cleaned));
    }
}
