<?php

declare(strict_types=1);

final class BlogCommentService
{
    /**
     * @var array<string, list<string>>
     */
    private array $allowedTransitions = [
        BlogCommentRepository::STATUS_PENDING => [
            BlogCommentRepository::STATUS_APPROVED,
            BlogCommentRepository::STATUS_REJECTED,
            BlogCommentRepository::STATUS_SPAM,
            BlogCommentRepository::STATUS_DELETED,
        ],
        BlogCommentRepository::STATUS_APPROVED => [
            BlogCommentRepository::STATUS_REJECTED,
            BlogCommentRepository::STATUS_SPAM,
            BlogCommentRepository::STATUS_DELETED,
        ],
        BlogCommentRepository::STATUS_REJECTED => [
            BlogCommentRepository::STATUS_PENDING,
            BlogCommentRepository::STATUS_DELETED,
        ],
        BlogCommentRepository::STATUS_SPAM => [
            BlogCommentRepository::STATUS_PENDING,
            BlogCommentRepository::STATUS_DELETED,
        ],
        BlogCommentRepository::STATUS_DELETED => [],
    ];

    public function __construct(
        private readonly BlogCommentRepository $comments,
        private readonly BlogCommentSettingsRepository $settings,
        private readonly BlogCommentModerationEventRepository $events,
    ) {
    }

    public function createPending(array $data): array
    {
        $normalized = $this->normalizeSubmission($data);
        $comment = $this->comments->createPending($normalized);
        $this->events->record([
            'comment_id' => (string) $comment['id'],
            'action' => 'created',
            'from_status' => '',
            'to_status' => BlogCommentRepository::STATUS_PENDING,
            'reason' => '',
            'actor_id' => $normalized['created_by'] ?? null,
        ]);
        $this->dispatchNotification($comment);
        return $comment;
    }

    public function transition(string $commentId, string $toStatus, ?string $actorId = null, string $reason = ''): array
    {
        $comment = $this->comments->findById($commentId);
        if ($comment === null) {
            throw new InvalidArgumentException('Comment not found.');
        }
        $fromStatus = $this->comments->normalizeStatus((string) $comment['status']);
        $toStatus = $this->comments->normalizeStatus($toStatus);
        if ($fromStatus === $toStatus) {
            return $comment;
        }
        if (!in_array($toStatus, $this->allowedTransitions[$fromStatus] ?? [], true)) {
            throw new InvalidArgumentException('Invalid comment status transition.');
        }
        $updated = $this->comments->transitionStatus($commentId, $toStatus, $actorId);
        $this->events->record([
            'comment_id' => $commentId,
            'action' => $toStatus,
            'from_status' => $fromStatus,
            'to_status' => $toStatus,
            'reason' => $reason,
            'actor_id' => $actorId,
        ]);
        return $updated;
    }

    public function find(string $commentId): ?array
    {
        return $this->comments->findById($commentId);
    }

    /**
     * @return list<array<string, mixed>>
     */
    public function listForPage(string $pageId, ?string $status = null, int $limit = 50, int $offset = 0): array
    {
        return $this->comments->listForPage($pageId, $status, $limit, $offset);
    }

    /**
     * @return list<array<string, mixed>>
     */
    public function listByStatus(string $status, int $limit = 50, int $offset = 0): array
    {
        return $this->comments->listByStatus($status, $limit, $offset);
    }

    /**
     * @return list<array<string, mixed>>
     */
    public function moderationQueue(?string $status = BlogCommentRepository::STATUS_PENDING, int $limit = 50, int $offset = 0): array
    {
        return $this->comments->listForModeration($status, $limit, $offset);
    }

    /**
     * @return array<string, int>
     */
    public function countByStatus(): array
    {
        return $this->comments->countByStatus();
    }

    /**
     * @return list<string>
     */
    public function availableTransitionsForStatus(string $status): array
    {
        $status = $this->comments->normalizeStatus($status);
        return $this->allowedTransitions[$status] ?? [];
    }

    public function countApprovedForPage(string $pageId): int
    {
        return $this->comments->countApprovedForPage($pageId);
    }

    public function publicPayload(array $comment): array
    {
        return $this->comments->publicPayload($comment);
    }

    public function settings(): array
    {
        return $this->settings->get();
    }

    public function updateSettings(array $data, ?string $actorId = null): array
    {
        return $this->settings->update($data, $actorId);
    }

    public function publicSubmissionFormKey(string $pageId): string
    {
        $pageId = preg_replace('/[^A-Za-z0-9_-]+/', '-', trim($pageId)) ?: 'page';
        return 'blog-comment:' . $pageId;
    }

    public function canAcceptPublicSubmissionForPage(array $page): bool
    {
        $settings = $this->settings();
        if (empty($settings['comments_enabled']) || !$this->isPublishedBlogPost($page)) {
            return false;
        }

        $override = $this->commentsOverrideForPage($page);
        if ($override === 'enabled') {
            return true;
        }
        if ($override === 'disabled') {
            return false;
        }

        return !empty($settings['default_post_comments_enabled']);
    }

    public function canDisplayPublicCommentsForPage(array $page): bool
    {
        $settings = $this->settings();
        if (empty($settings['comments_enabled'])) {
            return false;
        }

        return $this->isPublishedBlogPost($page);
    }

    private function isPublishedBlogPost(array $page): bool
    {
        $jsonLdType = trim((string) ($page['jsonld_type'] ?? ''));
        if ($jsonLdType === 'BlogPosting') {
            return true;
        }

        foreach (['source_template_id', 'template_source_template_id', 'template_key'] as $field) {
            $value = strtolower(trim((string) ($page[$field] ?? '')));
            if ($value === '') {
                continue;
            }
            if (str_contains($value, 'post-single')) {
                return true;
            }
        }

        return false;
    }

    /**
     * @param array<string, mixed> $page
     */
    private function commentsOverrideForPage(array $page): string
    {
        $content = is_array($page['content'] ?? null) ? $page['content'] : [];
        $candidate = $page['blog_comments_enabled_override']
            ?? $content['blog_comments_enabled_override']
            ?? $content['comments_enabled_override']
            ?? '';
        $normalized = strtolower(trim((string) $candidate));

        return in_array($normalized, ['enabled', 'disabled'], true) ? $normalized : 'default';
    }

    private function normalizeSubmission(array $data): array
    {
        $pageId = trim((string) ($data['page_id'] ?? ''));
        $name = $this->singleLine((string) ($data['author_name'] ?? ''));
        $email = myronNormalizeEmail((string) ($data['author_email'] ?? ''));
        $body = $this->plainText((string) ($data['body'] ?? ''));

        if ($pageId === '') {
            throw new InvalidArgumentException('Comment page is required.');
        }
        if ($name === '') {
            throw new InvalidArgumentException('Comment author name is required.');
        }
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException('A valid commenter email is required.');
        }
        if ($body === '') {
            throw new InvalidArgumentException('Comment body is required.');
        }

        return [
            'page_id' => $pageId,
            'page_version_id' => trim((string) ($data['page_version_id'] ?? '')),
            'author_name' => substr($name, 0, 160),
            'author_email' => substr($email, 0, 255),
            'body' => $body,
            'submitted_at' => (string) ($data['submitted_at'] ?? myronNow()),
            'source_url' => (string) ($data['source_url'] ?? ''),
            'ip_hash' => (string) ($data['ip_hash'] ?? ''),
            'user_agent' => (string) ($data['user_agent'] ?? ''),
            'created_by' => (string) ($data['created_by'] ?? ''),
        ];
    }

    private function singleLine(string $value): string
    {
        return trim(preg_replace('/\s+/', ' ', strip_tags($value)) ?? '');
    }

    private function plainText(string $value): string
    {
        $normalized = str_replace(["\r\n", "\r"], "\n", strip_tags($value));
        $normalized = preg_replace("/\n{3,}/", "\n\n", $normalized) ?? '';
        return trim($normalized);
    }

    private function dispatchNotification(array $comment): void
    {
        if (!function_exists('myronBlogCommentNotificationDispatcher')) {
            return;
        }

        try {
            myronBlogCommentNotificationDispatcher()->dispatch($comment);
        } catch (Throwable) {
        }
    }
}
