<?php

declare(strict_types=1);

final class MyronSiteSetupDefaultReplacementService
{
    public const CONFIRMATION_PHRASE = 'delete my defaults';
    public const STARTER_DEFAULT_BUNDLE_ID = 'starter.default';

    public function __construct(
        private readonly PDO $pdo,
        private readonly MyronSiteTypeRegistryRepository $siteTypes,
        private readonly MyronSiteSetupSelectionService $selectionService,
        private readonly MyronFooterDesignRepository $footerDesigns,
    ) {
    }

    public function assertConfirmed(string $confirmationPhrase): void
    {
        if (trim($confirmationPhrase) !== self::CONFIRMATION_PHRASE) {
            throw new InvalidArgumentException('Type "' . self::CONFIRMATION_PHRASE . '" to replace existing default pages.');
        }
    }

    /**
     * @return array<string, mixed>
     */
    public function replacePrimaryDefaults(string $previousPrimaryBundleId, string $nextPrimaryBundleId): array
    {
        $previousPrimary = trim($previousPrimaryBundleId);
        $nextPrimary = trim($nextPrimaryBundleId);
        if ($previousPrimary === '' && $nextPrimary === '') {
            return [
                'deletedPages' => [],
                'sourceTemplateIds' => [],
                'bundleIds' => [],
            ];
        }

        $bundleIds = $previousPrimary !== ''
            ? $this->primaryFamilyBundleIds($previousPrimary)
            : [self::STARTER_DEFAULT_BUNDLE_ID];
        $sourceTemplateIds = $previousPrimary !== ''
            ? $this->defaultTemplateIdsForBundles($bundleIds)
            : $this->starterDefaultTemplateIds();
        $deletedPages = [];

        foreach ($this->sourceLinkedPagesForTemplates($sourceTemplateIds) as $page) {
            $deletedPages[] = $this->deleteSourceLinkedDefaultPage($page);
        }

        if ($bundleIds !== []) {
            $placeholders = implode(', ', array_fill(0, count($bundleIds), '?'));
            $deleteSeeds = $this->pdo->prepare('DELETE FROM bundle_seed_records WHERE bundle_id IN (' . $placeholders . ')');
            $deleteSeeds->execute(array_values($bundleIds));
        }

        return [
            'deletedPages' => $deletedPages,
            'sourceTemplateIds' => $sourceTemplateIds,
            'bundleIds' => $bundleIds,
        ];
    }

    public function seedFooterForPrimaryBundle(string $primaryBundleId, string $updatedByUserId): string
    {
        return $this->footerDesigns->seedFromSiteType($primaryBundleId, $this->siteTypes, $updatedByUserId);
    }

    /**
     * @param list<string> $selectedSourceTemplateIds
     * @return array{deletedPages:list<array{id:string,title:string,path:string}>,deletedTemplates:list<array{id:string,name:string,sourceTemplateId:string}>,retainedTemplates:list<array{id:string,name:string,sourceTemplateId:string,reason:string}>}
     */
    public function pruneUnselectedSourceLinkedContent(array $selectedSourceTemplateIds): array
    {
        $selected = [];
        foreach ($selectedSourceTemplateIds as $templateId) {
            $normalized = trim((string) $templateId);
            if ($normalized !== '') {
                $selected[$normalized] = true;
            }
        }

        $unselectedPages = $this->unselectedSourceLinkedPages(array_keys($selected));
        $deletedPages = [];
        foreach ($unselectedPages as $page) {
            $deletedPages[] = $this->deleteSourceLinkedDefaultPage($page);
        }

        $deletedTemplates = [];
        $retainedTemplates = [];
        foreach ($this->unselectedRuntimeTemplates(array_keys($selected)) as $template) {
            $templateId = trim((string) ($template['id'] ?? ''));
            $sourceTemplateId = trim((string) ($template['source_template_id'] ?? ''));
            if ($templateId === '' || $sourceTemplateId === '') {
                continue;
            }

            if ($this->runtimeTemplatePageCount($templateId) > 0) {
                $retainedTemplates[] = [
                    'id' => $templateId,
                    'name' => (string) ($template['name'] ?? ''),
                    'sourceTemplateId' => $sourceTemplateId,
                    'reason' => 'Runtime template is still used by manual pages.',
                ];
                continue;
            }

            $this->deleteRuntimeTemplate($templateId);
            $deletedTemplates[] = [
                'id' => $templateId,
                'name' => (string) ($template['name'] ?? ''),
                'sourceTemplateId' => $sourceTemplateId,
            ];
        }

        return [
            'deletedPages' => $deletedPages,
            'deletedTemplates' => $deletedTemplates,
            'retainedTemplates' => $retainedTemplates,
        ];
    }

    /**
     * @return list<string>
     */
    private function primaryFamilyBundleIds(string $primaryBundleId): array
    {
        $primary = trim($primaryBundleId);
        if ($primary === '') {
            return [];
        }

        $ids = [$primary => true];
        foreach ($this->selectionService->descendantBundleIdsForPrimary($primary) as $bundleId) {
            $ids[$bundleId] = true;
        }

        $bundleIds = array_keys($ids);
        sort($bundleIds, SORT_STRING);

        return $bundleIds;
    }

    /**
     * @return list<string>
     */
    private function starterDefaultTemplateIds(): array
    {
        return [
            'template.about.default.v1',
            'template.home.default.v1',
        ];
    }

    /**
     * @param list<string> $bundleIds
     * @return list<string>
     */
    private function defaultTemplateIdsForBundles(array $bundleIds): array
    {
        $templateIds = [];
        foreach ($bundleIds as $bundleId) {
            $record = $this->siteTypes->findSiteTypeById($bundleId);
            if (!is_array($record)) {
                continue;
            }
            foreach ($this->selectionService->templateIdsForBundle($record, true) as $templateId) {
                $templateIds[$templateId] = true;
            }
        }

        $ids = array_keys($templateIds);
        sort($ids, SORT_STRING);

        return $ids;
    }

    /**
     * @param list<string> $sourceTemplateIds
     * @return list<array<string, mixed>>
     */
    private function sourceLinkedPagesForTemplates(array $sourceTemplateIds): array
    {
        if ($sourceTemplateIds === []) {
            return [];
        }

        $placeholders = implode(', ', array_fill(0, count($sourceTemplateIds), '?'));
        $statement = $this->pdo->prepare(
            'SELECT *
             FROM pages
             WHERE source_template_id IN (' . $placeholders . ')
             ORDER BY CASE WHEN path = \'/\' THEN 0 ELSE 1 END, path ASC'
        );
        $statement->execute(array_values($sourceTemplateIds));

        $pages = [];
        foreach ($statement->fetchAll() as $row) {
            if (is_array($row)) {
                $pages[] = $row;
            }
        }

        return $pages;
    }

    /**
     * @param list<string> $selectedSourceTemplateIds
     * @return list<array<string, mixed>>
     */
    private function unselectedSourceLinkedPages(array $selectedSourceTemplateIds): array
    {
        [$where, $params] = $this->unselectedSourceTemplateWhere($selectedSourceTemplateIds);
        $statement = $this->pdo->prepare(
            'SELECT *
             FROM pages
             WHERE ' . $where . '
             ORDER BY CASE WHEN path = \'/\' THEN 0 ELSE 1 END, path ASC'
        );
        $statement->execute($params);

        $pages = [];
        foreach ($statement->fetchAll() as $row) {
            if (is_array($row)) {
                $pages[] = $row;
            }
        }

        return $pages;
    }

    /**
     * @param list<string> $selectedSourceTemplateIds
     * @return list<array<string, mixed>>
     */
    private function unselectedRuntimeTemplates(array $selectedSourceTemplateIds): array
    {
        [$where, $params] = $this->unselectedSourceTemplateWhere($selectedSourceTemplateIds);
        $statement = $this->pdo->prepare(
            'SELECT *
             FROM page_templates
             WHERE ' . $where . '
             ORDER BY name ASC, source_template_id ASC'
        );
        $statement->execute($params);

        $templates = [];
        foreach ($statement->fetchAll() as $row) {
            if (is_array($row)) {
                $templates[] = $row;
            }
        }

        return $templates;
    }

    /**
     * @param list<string> $selectedSourceTemplateIds
     * @return array{0:string,1:array<string, string>}
     */
    private function unselectedSourceTemplateWhere(array $selectedSourceTemplateIds): array
    {
        $where = "source_template_id IS NOT NULL AND TRIM(source_template_id) <> ''";
        $params = [];
        if ($selectedSourceTemplateIds === []) {
            return [$where, $params];
        }

        $placeholders = [];
        foreach ($selectedSourceTemplateIds as $index => $templateId) {
            $param = ':selected_source_' . $index;
            $placeholders[] = $param;
            $params[$param] = $templateId;
        }

        return [$where . ' AND source_template_id NOT IN (' . implode(', ', $placeholders) . ')', $params];
    }

    private function runtimeTemplatePageCount(string $templateId): int
    {
        $statement = $this->pdo->prepare('SELECT COUNT(*) FROM pages WHERE template_id = :template_id');
        $statement->execute([':template_id' => trim($templateId)]);

        return (int) $statement->fetchColumn();
    }

    private function deleteRuntimeTemplate(string $templateId): void
    {
        $normalized = trim($templateId);
        if ($normalized === '') {
            return;
        }

        $this->pdo->beginTransaction();

        try {
            $deleteVersions = $this->pdo->prepare('DELETE FROM page_template_versions WHERE template_id = :template_id');
            $deleteVersions->execute([':template_id' => $normalized]);

            $deleteTemplate = $this->pdo->prepare('DELETE FROM page_templates WHERE id = :template_id');
            $deleteTemplate->execute([':template_id' => $normalized]);

            $this->pdo->commit();
        } catch (Throwable $exception) {
            $this->pdo->rollBack();
            throw $exception;
        }
    }

    /**
     * @param array<string, mixed> $page
     * @return array{id:string,title:string,path:string}
     */
    private function deleteSourceLinkedDefaultPage(array $page): array
    {
        $pageId = trim((string) ($page['id'] ?? ''));
        if ($pageId === '') {
            throw new InvalidArgumentException('Cannot replace a default page without a page id.');
        }
        if (trim((string) ($page['source_template_id'] ?? '')) === '') {
            throw new InvalidArgumentException('Only source-linked default pages can be replaced.');
        }

        $childCountStatement = $this->pdo->prepare('SELECT COUNT(*) FROM pages WHERE parent_page_id = :page_id');
        $childCountStatement->execute([':page_id' => $pageId]);
        if ((int) $childCountStatement->fetchColumn() > 0) {
            throw new InvalidArgumentException('Default page `' . (string) ($page['title'] ?? $pageId) . '` has child pages and cannot be replaced until those pages are moved.');
        }

        $this->pdo->beginTransaction();

        try {
            $deleteMenuItems = $this->pdo->prepare('DELETE FROM menu_items WHERE page_id = :page_id');
            $deleteMenuItems->execute([':page_id' => $pageId]);

            $deleteVersions = $this->pdo->prepare('DELETE FROM page_versions WHERE page_id = :page_id');
            $deleteVersions->execute([':page_id' => $pageId]);

            $deletePage = $this->pdo->prepare('DELETE FROM pages WHERE id = :page_id');
            $deletePage->execute([':page_id' => $pageId]);

            $this->pdo->commit();
        } catch (Throwable $exception) {
            $this->pdo->rollBack();
            throw $exception;
        }

        return [
            'id' => $pageId,
            'title' => (string) ($page['title'] ?? ''),
            'path' => (string) ($page['path'] ?? ''),
        ];
    }
}
