<?php

declare(strict_types=1);

final class MyronBundleDisableService
{
    public function __construct(
        private readonly PDO $pdo,
        private readonly MyronSiteTypeRegistryRepository $siteTypes,
        private readonly MyronBundleActivationRepository $bundles,
        private readonly MyronBundleImpactAnalysisService $impact,
        private readonly DisabledPageRedirectsRepository $redirects,
        private readonly MyronRedirectFallbackValidator $fallbacks,
    ) {
    }

    /**
     * @param array<string, mixed> $redirectMap
     * @return array<string, mixed>
     */
    public function disable(string $bundleId, array $redirectMap, MyronActionCallerContext $caller, bool $simulateFailureAfterPages = false): array
    {
        $bundleId = trim($bundleId);
        $this->assertDisableAllowed($bundleId);
        $impact = $this->impact->analyze($bundleId);
        $ownedPages = is_array($impact['pages_owned'] ?? null) ? $impact['pages_owned'] : [];
        $publishedPages = array_values(array_filter(
            $ownedPages,
            static fn (array $page): bool => (string) ($page['publish_state'] ?? '') === 'published'
        ));
        $disabledSlugs = array_values(array_map(static fn (array $page): string => myronNormalizePath((string) ($page['slug'] ?? '')), $publishedPages));
        $validatedRedirects = $this->fallbacks->validateMap($redirectMap, $disabledSlugs);
        $redirectEntries = [];
        foreach ($publishedPages as $page) {
            $slug = myronNormalizePath((string) ($page['slug'] ?? ''));
            $redirectEntries[] = [
                'slug' => $slug,
                'bundle_id' => $bundleId,
                'fallback_url' => $validatedRedirects['per_slug_overrides'][$slug] ?? $validatedRedirects['default_fallback_url'],
            ];
        }

        $pageIds = array_values(array_map(static fn (array $page): string => (string) ($page['page_id'] ?? ''), $ownedPages));
        $pageIds = array_values(array_filter($pageIds, static fn (string $id): bool => $id !== ''));
        $beforeState = $this->bundles->findBundleState($bundleId);
        $menuItemsRemoved = is_array($impact['menu_items_affected'] ?? null) ? $impact['menu_items_affected'] : [];
        $auditId = myronGenerateId('baa');

        $this->pdo->beginTransaction();
        try {
            $payload = $impact;
            $payload['bundle_id'] = $bundleId;
            $payload['redirect_map'] = $validatedRedirects;
            $payload['redirects_created'] = $redirectEntries;
            $payload['menu_items_removed'] = $menuItemsRemoved;
            $payload['collision_resolution'] = null;
            $payload['schema_version'] = 1;

            $this->insertAudit($auditId, (string) $caller->callerId, 'disable', $this->bundles->getPrimaryBundleId(), $beforeState ?? [], $payload);

            if ($pageIds !== []) {
                $placeholders = implode(',', array_fill(0, count($pageIds), '?'));
                $statement = $this->pdo->prepare(
                    'UPDATE pages
                     SET disabled_by_bundle_removal_at = ?,
                         updated_at = ?
                     WHERE id IN (' . $placeholders . ')
                       AND (disabled_by_bundle_removal_at IS NULL OR disabled_by_bundle_removal_at = \'\')'
                );
                $now = myronNow();
                $statement->execute(array_merge([$now, $now], $pageIds));
            }

            if ($simulateFailureAfterPages) {
                throw new RuntimeException('Simulated disable failure after page update.');
            }

            if ($pageIds !== []) {
                $placeholders = implode(',', array_fill(0, count($pageIds), '?'));
                $statement = $this->pdo->prepare('DELETE FROM menu_items WHERE page_id IN (' . $placeholders . ')');
                $statement->execute($pageIds);
            }

            $this->redirects->insertBatch($redirectEntries, $auditId);
            $statement = $this->pdo->prepare(
                'UPDATE bundle_activation_state
                 SET is_enabled = 0,
                     updated_at = :updated_at
                 WHERE bundle_id = :bundle_id
                   AND is_enabled = 1'
            );
            $statement->execute([':updated_at' => myronNow(), ':bundle_id' => $bundleId]);
            if ($statement->rowCount() !== 1) {
                throw new RuntimeException('Bundle state changed while disabling. Try again.');
            }

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

        if ($pageIds !== [] && function_exists('myronSearchIndexer')) {
            myronSearchIndexer()->removePagesByIds($pageIds);
        }

        return [
            'audit_id' => $auditId,
            'summary' => [
                'pages_disabled' => count($pageIds),
                'menu_items_removed' => count($menuItemsRemoved),
                'redirects_created' => count($redirectEntries),
            ],
            'impact' => $impact,
        ];
    }

    private function assertDisableAllowed(string $bundleId): void
    {
        if ($bundleId === '' || !is_array($this->siteTypes->findSiteTypeById($bundleId))) {
            throw new InvalidArgumentException('Bundle not found.');
        }
        $state = $this->bundles->findBundleState($bundleId);
        if (!is_array($state)) {
            throw new RuntimeException('Bundle is not active.');
        }
        if (($state['is_primary'] ?? false) === true) {
            throw new DomainException('Primary bundle changes require Super Admin via Site Setup.');
        }
        if (($state['is_enabled'] ?? false) !== true) {
            throw new RuntimeException('Bundle is already disabled.');
        }
    }

    /**
     * @param array<string, mixed>|list<array<string, mixed>> $before
     * @param array<string, mixed>|list<array<string, mixed>> $after
     */
    private function insertAudit(string $id, string $actorId, string $context, ?string $primaryBundleId, array $before, array $after): void
    {
        $statement = $this->pdo->prepare(
            'INSERT INTO bundle_activation_audit (
                id, actor_id, context, primary_bundle_id, before_json, after_json, created_at
             ) VALUES (
                :id, :actor_id, :context, :primary_bundle_id, :before_json, :after_json, :created_at
             )'
        );
        $statement->execute([
            ':id' => $id,
            ':actor_id' => $actorId,
            ':context' => $context,
            ':primary_bundle_id' => $primaryBundleId,
            ':before_json' => json_encode($before, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES),
            ':after_json' => json_encode($after, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES),
            ':created_at' => myronNow(),
        ]);
    }
}
