<?php

declare(strict_types=1);

namespace App\Services\SupportCenter;

use App\Enums\AdminRoleStatus;
use App\Enums\AdminUserStatus;
use App\Filament\Pages\HostSystemUpdateCenterPage;
use App\Models\DberpSystemUpdateNotice;
use App\Models\DberpUserPermissionGrant;
use App\Models\HostSystemUpdateCheck;
use App\Models\User;
use App\Services\DberpPermissionService;
use App\Support\DberpProductVersion;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Schema;

/**
 * 在官方更新检查成功后，将可用更新同步为本地按用户提醒（不访问远端 API）。
 */
class HostSystemUpdateNotificationSyncService
{
    private const NOTICE_KEY_PREFIX = 'system_update:';

    private const SUMMARY_MAX_LENGTH = 500;

    public function __construct(
        private readonly DberpProductVersion $productVersion,
    ) {}

    public function sync(HostSystemUpdateCheck $check): void
    {
        if (! $this->noticesTableExists()) {
            return;
        }

        if (! $check->update_available) {
            $this->resolveActiveNotices();

            return;
        }

        $noticeKey = $this->buildNoticeKey($check);

        if ($noticeKey === null) {
            return;
        }

        $payload = $this->buildNoticePayload($check, $noticeKey);
        $recipientIds = $this->resolveRecipientUserIds();

        foreach ($recipientIds as $userId) {
            $this->upsertNoticeForUser($userId, $payload);
        }
    }

    private function noticesTableExists(): bool
    {
        return Schema::hasTable('dberp_system_update_notices');
    }

    private function resolveActiveNotices(): void
    {
        DberpSystemUpdateNotice::query()
            ->active()
            ->update(['resolved_at' => now()->utc()]);
    }

    /**
     * @return list<int>
     */
    private function resolveRecipientUserIds(): array
    {
        $permission = DberpPermissionService::VIEW_HOST_SYSTEM_UPDATE;
        $guard = (string) config('dberp.admin.guard', 'web');
        $enabledStatus = AdminUserStatus::Enabled->value;
        $enabledRoleStatus = AdminRoleStatus::Enabled->value;

        $grantIds = DberpUserPermissionGrant::query()
            ->where('permission_code', $permission)
            ->pluck('user_id');

        $superAdminIds = User::query()
            ->where('users.status', $enabledStatus)
            ->join('core_user_roles', function ($join) use ($guard): void {
                $join->on('core_user_roles.user_id', '=', 'users.id')
                    ->where('core_user_roles.user_type', '=', User::class)
                    ->where('core_user_roles.guard_name', '=', $guard);
            })
            ->join('core_roles', 'core_roles.id', '=', 'core_user_roles.role_id')
            ->where('core_roles.status', $enabledRoleStatus)
            ->where('core_roles.is_super_admin', true)
            ->pluck('users.id');

        $rolePermissionIds = User::query()
            ->where('users.status', $enabledStatus)
            ->join('core_user_roles', function ($join) use ($guard): void {
                $join->on('core_user_roles.user_id', '=', 'users.id')
                    ->where('core_user_roles.user_type', '=', User::class)
                    ->where('core_user_roles.guard_name', '=', $guard);
            })
            ->join('core_roles', function ($join) use ($enabledRoleStatus): void {
                $join->on('core_roles.id', '=', 'core_user_roles.role_id')
                    ->where('core_roles.status', '=', $enabledRoleStatus);
            })
            ->join('core_role_permissions', 'core_role_permissions.role_id', '=', 'core_roles.id')
            ->join('core_permissions', 'core_permissions.id', '=', 'core_role_permissions.permission_id')
            ->where('core_permissions.name', $permission)
            ->pluck('users.id');

        return collect()
            ->merge($grantIds)
            ->merge($superAdminIds)
            ->merge($rolePermissionIds)
            ->unique()
            ->map(fn (mixed $id): int => (int) $id)
            ->filter(fn (int $id): bool => $id > 0)
            ->values()
            ->all();
    }

    /**
     * @param  array<string, mixed>  $payload
     */
    private function upsertNoticeForUser(int $userId, array $payload): void
    {
        $noticeKey = (string) $payload['notice_key'];

        $existing = DberpSystemUpdateNotice::query()
            ->where('user_id', $userId)
            ->where('notice_key', $noticeKey)
            ->first();

        if ($existing instanceof DberpSystemUpdateNotice && $existing->dismissed_at !== null) {
            return;
        }

        $attributes = [
            'package_id' => $payload['package_id'],
            'latest_version' => $payload['latest_version'],
            'latest_update_version' => $payload['latest_update_version'],
            'notice_level' => $payload['notice_level'],
            'title' => $payload['title'],
            'summary' => $payload['summary'],
            'action_url' => $payload['action_url'],
            'raw_payload' => $payload['raw_payload'],
            'resolved_at' => null,
        ];

        if ($existing instanceof DberpSystemUpdateNotice) {
            $existing->fill($attributes);
            $existing->save();

            return;
        }

        DberpSystemUpdateNotice::query()->create([
            'user_id' => $userId,
            'notice_key' => $noticeKey,
            ...$attributes,
        ]);
    }

    /**
     * @return array<string, mixed>
     */
    private function buildNoticePayload(HostSystemUpdateCheck $check, string $noticeKey): array
    {
        $latestVersion = $this->nullableString($check->latest_version);

        return [
            'notice_key' => $noticeKey,
            'package_id' => $this->nullableString($check->package_id),
            'latest_version' => $latestVersion,
            'latest_update_version' => $check->latest_update_version !== null
                ? (int) $check->latest_update_version
                : null,
            'notice_level' => DberpSystemUpdateNotice::NOTICE_LEVEL_NORMAL,
            'title' => (string) __('dberp.system_update_notice.title', [
                'product' => $this->productVersion->publicProductName(),
                'version' => $latestVersion ?? (string) __('dberp.system_update_center.values.unknown_latest'),
            ]),
            'summary' => $this->buildSummary($check),
            'action_url' => HostSystemUpdateCenterPage::getUrl(panel: 'admin'),
            'raw_payload' => $this->buildRawPayload($check, $noticeKey),
        ];
    }

    /**
     * @return array<string, mixed>
     */
    private function buildRawPayload(HostSystemUpdateCheck $check, string $noticeKey): array
    {
        return [
            'notice_key' => $noticeKey,
            'package_id' => $check->package_id,
            'latest_version' => $check->latest_version,
            'latest_update_version' => $check->latest_update_version,
            'last_checked_at' => $check->last_checked_at instanceof Carbon
                ? $check->last_checked_at->toIso8601String()
                : null,
        ];
    }

    private function buildSummary(HostSystemUpdateCheck $check): string
    {
        $fromReleaseNotes = $this->releaseNotesSummary($check->release_notes);

        if ($fromReleaseNotes !== null) {
            return $fromReleaseNotes;
        }

        $fromRisk = $this->riskSummary($check->requirements);

        if ($fromRisk !== null) {
            return $fromRisk;
        }

        return (string) __('dberp.system_update_notice.fallback_summary');
    }

    /**
     * @param  array<string, mixed>|list<mixed>|null  $releaseNotes
     */
    private function releaseNotesSummary(?array $releaseNotes): ?string
    {
        if ($releaseNotes === null || $releaseNotes === []) {
            return null;
        }

        foreach (['zh', 'zh_CN', 'zh-CN', 'cn'] as $localeKey) {
            if (isset($releaseNotes[$localeKey]) && is_string($releaseNotes[$localeKey])) {
                $text = trim($releaseNotes[$localeKey]);

                if ($text !== '') {
                    return $this->truncateText($text);
                }
            }
        }

        foreach ($releaseNotes as $value) {
            if (is_string($value) && trim($value) !== '') {
                return $this->truncateText(trim($value));
            }
        }

        return null;
    }

    /**
     * @param  array<string, mixed>|null  $requirements
     */
    private function riskSummary(?array $requirements): ?string
    {
        if ($requirements === null) {
            return null;
        }

        foreach (['update_risk_summary', 'risk_summary'] as $key) {
            $value = $requirements[$key] ?? null;

            if (is_string($value) && trim($value) !== '') {
                return $this->truncateText(trim($value));
            }
        }

        return null;
    }

    private function buildNoticeKey(HostSystemUpdateCheck $check): ?string
    {
        $packageId = $this->nullableString($check->package_id);

        if ($packageId !== null) {
            return self::NOTICE_KEY_PREFIX.$packageId;
        }

        if ($check->latest_update_version !== null && (int) $check->latest_update_version > 0) {
            return self::NOTICE_KEY_PREFIX.(string) (int) $check->latest_update_version;
        }

        $latestVersion = $this->nullableString($check->latest_version);

        if ($latestVersion !== null) {
            return self::NOTICE_KEY_PREFIX.$latestVersion;
        }

        return null;
    }

    private function truncateText(string $text): string
    {
        if (mb_strlen($text) <= self::SUMMARY_MAX_LENGTH) {
            return $text;
        }

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

    private function nullableString(mixed $value): ?string
    {
        if (! is_string($value)) {
            return null;
        }

        $trimmed = trim($value);

        return $trimmed === '' ? null : $trimmed;
    }
}
