<?php

namespace MintyPHP\Module\Bookmarks\Service;

use MintyPHP\Module\Bookmarks\Repository\BookmarkGroupRepository;
use MintyPHP\Module\Bookmarks\Repository\BookmarkNavigationRepository;
use MintyPHP\Module\Bookmarks\Repository\BookmarkRepository;
use MintyPHP\Module\Bookmarks\Support\BookmarkUrlNormalizer;

class BookmarkService
{
    private const MAX_BOOKMARKS = 50;
    private const MAX_GROUPS = 10;
    private const NAME_MAX = 120;
    private const GROUP_NAME_MAX = 100;
    private const URL_MAX = 500;

    private const ALLOWED_GROUP_ICONS = [
        'bi-house', 'bi-people', 'bi-person', 'bi-folder', 'bi-file-text',
        'bi-gear', 'bi-shield-lock', 'bi-bar-chart', 'bi-envelope', 'bi-calendar',
        'bi-clock', 'bi-star', 'bi-heart', 'bi-flag', 'bi-bookmark',
        'bi-pin-map', 'bi-lightning', 'bi-database', 'bi-globe', 'bi-code-slash',
    ];

    public function __construct(
        private readonly BookmarkRepository $bookmarkRepository,
        private readonly BookmarkGroupRepository $groupRepository,
        private readonly BookmarkNavigationRepository $navigationRepository
    ) {
    }

    /** @return list<string> */
    public static function allowedGroupIcons(): array
    {
        return self::ALLOWED_GROUP_ICONS;
    }

    /** @return array<string, string> icon class => translated label */
    public static function allowedGroupIconLabels(): array
    {
        return [
            'bi-house' => t('Icon: Home'),
            'bi-people' => t('Icon: People'),
            'bi-person' => t('Icon: Person'),
            'bi-folder' => t('Icon: Folder'),
            'bi-file-text' => t('Icon: Document'),
            'bi-gear' => t('Icon: Settings'),
            'bi-shield-lock' => t('Icon: Security'),
            'bi-bar-chart' => t('Icon: Chart'),
            'bi-envelope' => t('Icon: Mail'),
            'bi-calendar' => t('Icon: Calendar'),
            'bi-clock' => t('Icon: Clock'),
            'bi-star' => t('Icon: Star'),
            'bi-heart' => t('Icon: Heart'),
            'bi-flag' => t('Icon: Flag'),
            'bi-bookmark' => t('Icon: Bookmark'),
            'bi-pin-map' => t('Icon: Location'),
            'bi-lightning' => t('Icon: Lightning'),
            'bi-database' => t('Icon: Database'),
            'bi-globe' => t('Icon: Globe'),
            'bi-code-slash' => t('Icon: Code'),
        ];
    }

    /**
     * @return array{groups: list<array<string, mixed>>, ungrouped: list<array<string, mixed>>}
     */
    public function listGroupedForUser(int $userId): array
    {
        $groups = $this->groupRepository->listByUser($userId);
        $bookmarks = $this->bookmarkRepository->listByUser($userId);

        $grouped = [];
        foreach ($groups as $group) {
            $gid = (int) ($group['id'] ?? 0);
            $grouped[$gid] = [
                'id' => $gid,
                'name' => (string) ($group['name'] ?? ''),
                'icon' => (string) ($group['icon'] ?? 'bi-folder'),
                'sort_order' => (int) ($group['sort_order'] ?? 0),
                'bookmarks' => [],
            ];
        }

        $ungrouped = [];
        foreach ($bookmarks as $bm) {
            $item = [
                'id' => (int) ($bm['id'] ?? 0),
                'name' => (string) ($bm['name'] ?? ''),
                'url' => BookmarkUrlNormalizer::canonicalizeRelative((string) ($bm['url'] ?? '')),
                'group_id' => isset($bm['group_id']) ? (int) $bm['group_id'] : null,
                'sort_order' => (int) ($bm['sort_order'] ?? 0),
            ];

            $gid = $item['group_id'];
            if ($gid !== null && $gid > 0 && isset($grouped[$gid])) {
                $grouped[$gid]['bookmarks'][] = $item;
            } else {
                $ungrouped[] = $item;
            }
        }

        return [
            'groups' => array_values($grouped),
            'ungrouped' => $ungrouped,
        ];
    }

    /**
     * @return array{ok: bool, mode?: 'created'|'updated', error?: string, bookmark?: array<string, mixed>}
     */
    public function saveBookmark(int $userId, string $name, string $url, ?int $groupId): array
    {
        $name = $this->truncate(trim($name), self::NAME_MAX);
        if ($name === '') {
            return ['ok' => false, 'error' => 'name_required'];
        }

        $url = $this->sanitizeUrl($url);
        if ($url === '') {
            return ['ok' => false, 'error' => 'url_required'];
        }

        $existing = $this->bookmarkRepository->findByUserAndUrl($userId, $url);

        if ($groupId !== null && $groupId > 0) {
            $group = $this->groupRepository->findById($groupId, $userId);
            if (!$group) {
                return ['ok' => false, 'error' => 'group_not_found'];
            }
        } else {
            $groupId = null;
        }

        if ($existing) {
            $bookmarkId = (int) ($existing['id'] ?? 0);
            if ($bookmarkId <= 0) {
                return ['ok' => false, 'error' => 'not_found'];
            }

            $existingGroupId = isset($existing['group_id']) && (int) $existing['group_id'] > 0
                ? (int) $existing['group_id']
                : null;

            $update = [
                'name' => $name,
                'group_id' => $groupId,
            ];

            if ($existingGroupId !== $groupId) {
                $update['sort_order'] = $groupId === null
                    ? $this->navigationRepository->nextRootSortOrder($userId)
                    : $this->bookmarkRepository->nextBookmarkSortOrder($userId, $groupId);
            }

            $existingUrl = trim((string) ($existing['url'] ?? ''));
            if ($existingUrl !== $url) {
                $update['url'] = $url;
            }

            if (!$this->bookmarkRepository->update($bookmarkId, $userId, $update)) {
                return ['ok' => false, 'error' => 'update_failed'];
            }

            return [
                'ok' => true,
                'mode' => 'updated',
                'bookmark' => [
                    'id' => $bookmarkId,
                    'name' => $name,
                    'url' => $url,
                    'group_id' => $groupId,
                ],
            ];
        }

        if ($this->bookmarkRepository->countByUser($userId) >= self::MAX_BOOKMARKS) {
            return ['ok' => false, 'error' => 'max_reached'];
        }

        $id = $this->bookmarkRepository->create([
            'user_id' => $userId,
            'group_id' => $groupId,
            'name' => $name,
            'url' => $url,
            'sort_order' => $groupId === null
                ? $this->navigationRepository->nextRootSortOrder($userId)
                : $this->bookmarkRepository->nextBookmarkSortOrder($userId, $groupId),
        ]);

        if ($id === false) {
            $raceExisting = $this->bookmarkRepository->findByUserAndUrl($userId, $url);
            if ($raceExisting) {
                $bookmarkId = (int) ($raceExisting['id'] ?? 0);
                $raceGroupId = isset($raceExisting['group_id']) && (int) $raceExisting['group_id'] > 0
                    ? (int) $raceExisting['group_id']
                    : null;
                $raceUpdate = [
                    'name' => $name,
                    'group_id' => $groupId,
                ];
                if ($raceGroupId !== $groupId) {
                    $raceUpdate['sort_order'] = $groupId === null
                        ? $this->navigationRepository->nextRootSortOrder($userId)
                        : $this->bookmarkRepository->nextBookmarkSortOrder($userId, $groupId);
                }
                if ($bookmarkId > 0 && $this->bookmarkRepository->update($bookmarkId, $userId, $raceUpdate)) {
                    return [
                        'ok' => true,
                        'mode' => 'updated',
                        'bookmark' => [
                            'id' => $bookmarkId,
                            'name' => $name,
                            'url' => $url,
                            'group_id' => $groupId,
                        ],
                    ];
                }
            }

            return ['ok' => false, 'error' => 'create_failed'];
        }

        return [
            'ok' => true,
            'mode' => 'created',
            'bookmark' => [
                'id' => $id,
                'name' => $name,
                'url' => $url,
                'group_id' => $groupId,
            ],
        ];
    }

    /**
     * @param array<string, mixed> $data
     * @return array{ok: bool, error?: string}
     */
    public function updateBookmark(int $userId, int $bookmarkId, array $data): array
    {
        $existing = $this->bookmarkRepository->findById($bookmarkId, $userId);
        if (!$existing) {
            return ['ok' => false, 'error' => 'not_found'];
        }

        $update = [];

        if (array_key_exists('name', $data)) {
            $name = $this->truncate(trim((string) $data['name']), self::NAME_MAX);
            if ($name === '') {
                return ['ok' => false, 'error' => 'name_required'];
            }
            $update['name'] = $name;
        }

        if (array_key_exists('group_id', $data)) {
            $groupId = $data['group_id'];
            $targetGroupId = null;
            if ($groupId !== null && $groupId !== '' && (int) $groupId > 0) {
                $group = $this->groupRepository->findById((int) $groupId, $userId);
                if (!$group) {
                    return ['ok' => false, 'error' => 'group_not_found'];
                }
                $targetGroupId = (int) $groupId;
                $update['group_id'] = $targetGroupId;
            } else {
                $update['group_id'] = null;
            }

            $existingGroupId = isset($existing['group_id']) && (int) $existing['group_id'] > 0
                ? (int) $existing['group_id']
                : null;
            if ($existingGroupId !== $targetGroupId) {
                $update['sort_order'] = $targetGroupId === null
                    ? $this->navigationRepository->nextRootSortOrder($userId)
                    : $this->bookmarkRepository->nextBookmarkSortOrder($userId, $targetGroupId);
            }
        }

        if ($update === []) {
            return ['ok' => true];
        }

        if (!$this->bookmarkRepository->update($bookmarkId, $userId, $update)) {
            return ['ok' => false, 'error' => 'update_failed'];
        }

        return ['ok' => true];
    }

    public function deleteBookmark(int $userId, int $bookmarkId): bool
    {
        return $this->bookmarkRepository->delete($bookmarkId, $userId);
    }

    /** @param list<array{id: int, sort_order: int}> $idOrderPairs */
    public function reorderBookmarks(int $userId, array $idOrderPairs): bool
    {
        return $this->bookmarkRepository->updateSortOrders($userId, $idOrderPairs);
    }

    /**
     * @return array{ok: bool, mode?: 'created'|'updated', error?: string, group?: array<string, mixed>}
     */
    public function saveGroup(int $userId, string $name, string $icon, ?int $groupId = null): array
    {
        $name = $this->truncate(trim($name), self::GROUP_NAME_MAX);
        if ($name === '') {
            return ['ok' => false, 'error' => 'name_required'];
        }
        $icon = $this->validateGroupIcon($icon);

        if ($groupId !== null && $groupId > 0) {
            $existing = $this->groupRepository->findById($groupId, $userId);
            if (!$existing) {
                return ['ok' => false, 'error' => 'not_found'];
            }
            if (!$this->groupRepository->update($groupId, $userId, ['name' => $name, 'icon' => $icon])) {
                return ['ok' => false, 'error' => 'update_failed'];
            }
            return ['ok' => true, 'mode' => 'updated', 'group' => ['id' => $groupId, 'name' => $name, 'icon' => $icon]];
        }

        if ($this->groupRepository->countByUser($userId) >= self::MAX_GROUPS) {
            return ['ok' => false, 'error' => 'max_reached'];
        }

        $id = $this->groupRepository->create([
            'user_id' => $userId,
            'name' => $name,
            'icon' => $icon,
            'sort_order' => $this->navigationRepository->nextRootSortOrder($userId),
        ]);

        if ($id === false) {
            return ['ok' => false, 'error' => 'create_failed'];
        }

        return ['ok' => true, 'mode' => 'created', 'group' => ['id' => $id, 'name' => $name, 'icon' => $icon]];
    }

    public function deleteGroup(int $userId, int $groupId): bool
    {
        $bookmarks = $this->bookmarkRepository->listByGroup($userId, $groupId);
        $nextSort = $this->navigationRepository->nextRootSortOrder($userId);

        foreach ($bookmarks as $bookmark) {
            $bookmarkId = (int) ($bookmark['id'] ?? 0);
            if ($bookmarkId <= 0) {
                continue;
            }
            if (!$this->bookmarkRepository->unsetGroupId($bookmarkId, $userId, $nextSort)) {
                return false;
            }
            $nextSort++;
        }

        return $this->groupRepository->delete($groupId, $userId);
    }

    /** @param list<array{id: int, sort_order: int}> $idOrderPairs */
    public function reorderGroups(int $userId, array $idOrderPairs): bool
    {
        return $this->groupRepository->updateSortOrders($userId, $idOrderPairs);
    }

    /** @param list<array{kind: 'group'|'bookmark', id: int, sort_order: int}> $rootOrderPairs */
    public function reorderRoot(int $userId, array $rootOrderPairs): bool
    {
        return $this->navigationRepository->updateRootSortOrders($userId, $rootOrderPairs);
    }

    /**
     * @param list<array{id: int, sort_order: int}> $items
     * @return array{ok: bool, error?: string}
     */
    public function reorderFromItems(int $userId, string $type, array $items): array
    {
        $allowedTypes = ['groups', 'bookmarks'];
        if (!in_array($type, $allowedTypes, true)) {
            return ['ok' => false, 'error' => 'invalid_type'];
        }

        if ($items === []) {
            return ['ok' => false, 'error' => 'invalid_items'];
        }

        $ok = $type === 'groups'
            ? $this->reorderGroups($userId, $items)
            : $this->reorderBookmarks($userId, $items);

        return $ok ? ['ok' => true] : ['ok' => false, 'error' => 'reorder_failed'];
    }

    /**
     * @param list<array{kind: 'group'|'bookmark', id: int, sort_order: int}> $rootPairs
     * @return array{ok: bool, error?: string}
     */
    public function reorderRootFromItems(int $userId, array $rootPairs): array
    {
        if ($rootPairs === []) {
            return ['ok' => false, 'error' => 'invalid_items'];
        }

        $ok = $this->reorderRoot($userId, $rootPairs);
        return $ok ? ['ok' => true] : ['ok' => false, 'error' => 'reorder_failed'];
    }

    private function sanitizeUrl(string $url): string
    {
        $url = BookmarkUrlNormalizer::canonicalizeRelative($url);
        if ($url === '') {
            return '';
        }

        if (mb_strlen($url) > self::URL_MAX) {
            $url = mb_substr($url, 0, self::URL_MAX);
        }

        return $url;
    }

    private function validateGroupIcon(string $icon): string
    {
        $icon = trim($icon);
        if (in_array($icon, self::ALLOWED_GROUP_ICONS, true)) {
            return $icon;
        }
        return 'bi-folder';
    }

    private function truncate(string $value, int $maxLength): string
    {
        if (mb_strlen($value) > $maxLength) {
            return mb_substr($value, 0, $maxLength);
        }
        return $value;
    }

}
