<?php

declare(strict_types=1);

final class MyronQualityReportService
{
    private readonly MyronQualityRuleRegistry $rules;

    public function __construct(
        private readonly PDO $pdo,
        ?MyronQualityRuleRegistry $rules = null,
    ) {
        $this->rules = $rules ?? new MyronQualityRuleRegistry();
    }

    /**
     * @param array<string, mixed> $input
     * @return array<string, mixed>
     */
    public function reportForPageInput(array $input): array
    {
        $target = [
            'type' => 'page-input',
            'id' => trim((string) ($input['pageId'] ?? 'new')),
        ];

        $templateId = trim((string) ($input['templateId'] ?? ''));
        $title = trim((string) ($input['title'] ?? ''));
        $slug = myronNormalizeSlug(trim((string) ($input['slug'] ?? '')) !== '' ? (string) $input['slug'] : $title);
        $content = is_array($input['content'] ?? null) ? $input['content'] : [];

        $results = [];
        $results[] = $this->result('page.exists', true, 'Page input is available.');
        $results[] = $this->result('template.current_version', $this->templateHasCurrentVersion($templateId), 'Template has a current version.');
        $results[] = $this->result('page.title', $title !== '', 'Page title is present.');
        $results[] = $this->result('page.slug', $slug !== '', 'Page slug can be generated.');
        $results[] = $this->result('content.json', $this->contentEncodes($content), 'Page content can be encoded as JSON.');
        $results[] = $this->result('content.headline', trim((string) ($content['headline'] ?? '')) !== '', 'Page headline is present.');
        $results[] = $this->result('content.body', trim((string) ($content['body'] ?? '')) !== '', 'Page body content is present.');

        return $this->report($target, $results);
    }

    /**
     * @return array<string, mixed>
     */
    public function reportForPage(string $pageId): array
    {
        $target = [
            'type' => 'page',
            'id' => trim($pageId),
        ];

        $statement = $this->pdo->prepare(
            'SELECT
                pages.id,
                pages.title,
                pages.slug,
                pages.path,
                pages.current_draft_version_id,
                pages.current_published_version_id,
                page_templates.current_version_id,
                COALESCE(draft.content_json, published.content_json, \'\') AS content_json
             FROM pages
             LEFT JOIN page_templates ON page_templates.id = pages.template_id
             LEFT JOIN page_versions AS draft ON draft.id = pages.current_draft_version_id
             LEFT JOIN page_versions AS published ON published.id = pages.current_published_version_id
             WHERE pages.id = :page_id
             LIMIT 1'
        );
        $statement->execute([':page_id' => trim($pageId)]);
        $row = $statement->fetch();

        if (!is_array($row)) {
            return $this->report($target, [
                $this->result('page.exists', false, 'Page record is missing.'),
            ]);
        }

        $content = [];
        $contentJson = (string) ($row['content_json'] ?? '');
        if ($contentJson !== '') {
            try {
                $decoded = json_decode($contentJson, true, 512, JSON_THROW_ON_ERROR);
                $content = is_array($decoded) ? $decoded : [];
            } catch (Throwable) {
                $content = [];
            }
        }

        return $this->report($target, [
            $this->result('page.exists', true, 'Page record exists.'),
            $this->result('template.current_version', trim((string) ($row['current_version_id'] ?? '')) !== '', 'Template has a current version.'),
            $this->result('page.title', trim((string) ($row['title'] ?? '')) !== '', 'Page title is present.'),
            $this->result('page.slug', trim((string) ($row['slug'] ?? '')) !== '' && trim((string) ($row['path'] ?? '')) !== '', 'Page slug/path is present.'),
            $this->result('content.json', $contentJson !== '' && $content !== [], 'Page content can be decoded.'),
            $this->result('content.headline', trim((string) ($content['headline'] ?? '')) !== '', 'Page headline is present.'),
            $this->result('content.body', trim((string) ($content['body'] ?? '')) !== '', 'Page body content is present.'),
        ]);
    }

    /**
     * @param array<string, mixed> $input
     */
    public function assertPublishableInput(array $input): void
    {
        $report = $this->reportForPageInput($input);
        if ((string) ($report['status'] ?? '') !== 'block') {
            return;
        }

        $messages = [];
        foreach (($report['results'] ?? []) as $result) {
            if (!is_array($result) || (string) ($result['status'] ?? '') !== 'block') {
                continue;
            }

            $messages[] = (string) ($result['message'] ?? $result['ruleId'] ?? 'Publish blocked.');
        }

        throw new RuntimeException('Publish blocked: ' . implode(' ', $messages));
    }

    /**
     * @param list<array<string, mixed>> $results
     * @return array<string, mixed>
     */
    private function report(array $target, array $results): array
    {
        $status = 'ready';
        foreach ($results as $result) {
            if ((string) ($result['status'] ?? '') === 'block') {
                $status = 'block';
                break;
            }

            if ((string) ($result['status'] ?? '') === 'warn') {
                $status = 'warn';
            }
        }

        return [
            'target' => $target,
            'status' => $status,
            'generatedAt' => myronNow(),
            'results' => $results,
        ];
    }

    /**
     * @return array<string, mixed>
     */
    private function result(string $ruleId, bool $passes, string $passMessage): array
    {
        $rule = $this->rule($ruleId);
        $severity = (string) ($rule['severity'] ?? 'warn');

        return [
            'ruleId' => $ruleId,
            'severity' => $severity,
            'status' => $passes ? 'pass' : $severity,
            'message' => $passes ? $passMessage : (string) ($rule['message'] ?? $ruleId),
            'remediation' => $passes ? '' : (string) ($rule['remediation'] ?? ''),
        ];
    }

    /**
     * @return array<string, string>
     */
    private function rule(string $ruleId): array
    {
        foreach ($this->rules->rules() as $rule) {
            if ((string) $rule['id'] === $ruleId) {
                return $rule;
            }
        }

        return ['id' => $ruleId, 'severity' => 'warn', 'message' => $ruleId, 'remediation' => ''];
    }

    private function templateHasCurrentVersion(string $templateId): bool
    {
        if ($templateId === '') {
            return false;
        }

        $statement = $this->pdo->prepare(
            'SELECT current_version_id
             FROM page_templates
             WHERE id = :template_id
             LIMIT 1'
        );
        $statement->execute([':template_id' => $templateId]);
        $versionId = $statement->fetchColumn();

        return is_string($versionId) && trim($versionId) !== '';
    }

    /**
     * @param array<string, mixed> $content
     */
    private function contentEncodes(array $content): bool
    {
        try {
            json_encode($content, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES);
            return true;
        } catch (Throwable) {
            return false;
        }
    }
}
