<?php

namespace App\Domain\Website\Services;

use App\Domain\Shared\Exceptions\DomainException;
use App\Domain\Website\Blocks\BlockField;
use App\Domain\Website\Blocks\BlockRegistry;
use App\Domain\Website\Blocks\BlockType;
use App\Domain\Website\Blocks\FieldType;
use App\Domain\Website\Models\WebsiteBlock;
use App\Domain\Website\Models\WebsiteMenuItem;
use App\Domain\Website\Models\WebsitePage;
use App\Domain\Website\Models\WebsiteSetting;
use App\Models\User;
use Illuminate\Support\Facades\DB;

/**
 * Import/export a whole site as JSON.
 *
 * Lets a finished design be snapshotted and re-applied to another academy, so
 * building a site once for one client makes it a reusable starting point for
 * the next. Import is explicit (artisan) and never runs during automatic
 * seeding, because every tenant boots from the same image.
 */
class WebsiteBlueprintService
{
    public function __construct(
        private readonly BlockRegistry $registry,
        private readonly WebsitePageService $pages,
    ) {}

    public const VERSION = 2;

    /** Blueprint versions this build can still read. */
    public const SUPPORTED_VERSIONS = [1, 2];

    /**
     * Presentation settings a blueprint carries.
     *
     * Deliberately a whitelist. Tracking identifiers (analytics, pixel) are
     * excluded even though they live on the same row: importing a design must
     * never silently start reporting one client's traffic into another
     * client's account.
     */
    public const PORTABLE_SETTINGS = [
        'template',
        'primary_color', 'secondary_color', 'accent_color', 'text_color',
        'background_color', 'surface_color', 'border_color', 'muted_text_color',
        'heading_font', 'body_font', 'font_size_scale', 'heading_weight',
        'line_height', 'letter_spacing', 'density', 'border_radius_preset',
        'card_style', 'section_divider', 'image_shape',
        'animations_enabled', 'animation_speed',
        'navbar_style', 'navbar_cta_text', 'navbar_cta_text_en', 'navbar_cta_link', 'navbar_show_social',
        'navbar_logo_position', 'navbar_secondary_logo_path', 'navbar_show_language',
        'site_title', 'site_title_en', 'site_description', 'site_description_en',
        'social_links', 'whatsapp_number', 'custom_css',
        'footer_columns', 'footer_blocks', 'footer_bottom_text', 'footer_style',
        'footer_powered_by', 'floating_elements', 'announcement_bar', 'popup_config',
    ];

    /** @return array<string,mixed> */
    public function export(): array
    {
        return [
            'version' => self::VERSION,
            'settings' => $this->exportSettings(),
            'menus' => $this->exportMenus(),
            'pages' => WebsitePage::with('blocks.childrenRecursive')
                ->orderBy('sort_order')
                ->get()
                ->map(fn (WebsitePage $page) => [
                    'slug' => $page->slug,
                    'title' => $page->title,
                    'title_en' => $page->title_en,
                    'meta_description' => $page->meta_description,
                    'meta_description_en' => $page->meta_description_en,
                    'layout' => $page->layout,
                    'is_homepage' => $page->is_homepage,
                    'is_published' => $page->is_published,
                    'sort_order' => $page->sort_order,
                    'blocks' => $page->blocks->map(fn ($b) => $this->exportBlock($b))->all(),
                ])->all(),
        ];
    }

    /** @return array<string,mixed> */
    private function exportSettings(): array
    {
        $settings = WebsiteSetting::first();

        if (! $settings) {
            return [];
        }

        return collect(self::PORTABLE_SETTINGS)
            ->mapWithKeys(fn (string $key) => [$key => $settings->{$key}])
            ->reject(fn ($value) => $value === null)
            ->all();
    }

    /**
     * Menus, with page links recorded by slug.
     *
     * A page id means nothing in another tenant's database, so an id-based
     * export would import a navigation full of links to whatever happened to
     * hold that id — or to nothing at all.
     *
     * @return array<string,mixed>
     */
    private function exportMenus(): array
    {
        return collect(WebsiteMenuService::KEYS)
            ->mapWithKeys(fn (string $key) => [
                $key => WebsiteMenuItem::query()
                    ->whereHas('menu', fn ($q) => $q->where('key', $key))
                    ->whereNull('parent_id')
                    ->with(['childrenRecursive.page', 'page'])
                    ->orderBy('sort_order')
                    ->get()
                    ->map(fn (WebsiteMenuItem $item) => $this->exportMenuItem($item))
                    ->all(),
            ])
            ->reject(fn (array $items) => $items === [])
            ->all();
    }

    private function exportMenuItem(WebsiteMenuItem $item): array
    {
        return [
            'label' => $item->label,
            'label_en' => $item->label_en,
            'link_type' => $item->link_type,
            'page_slug' => $item->page?->slug,
            'url' => $item->url,
            'anchor' => $item->anchor,
            'route_name' => $item->route_name,
            'icon' => $item->icon,
            'open_in_new_tab' => $item->open_in_new_tab,
            'is_visible' => $item->is_visible,
            'highlight' => $item->highlight,
            'children' => $item->children->map(fn ($c) => $this->exportMenuItem($c))->all(),
        ];
    }

    private function exportBlock(WebsiteBlock $block): array
    {
        return [
            'type' => $block->type,
            'variant' => $block->variant,
            'slot' => $block->slot,
            'is_enabled' => $block->is_enabled,
            'data' => $block->data ?? [],
            'style' => $block->style ?? [],
            'children' => $block->children->map(fn ($c) => $this->exportBlock($c))->all(),
        ];
    }

    /**
     * Imports a blueprint for the current academy.
     *
     * @param  bool  $replace  drop existing pages with the same slug first
     * @return array{pages:int, blocks:int, skipped:array<string>}
     */
    public function import(array $blueprint, User $actor, bool $replace = false): array
    {
        if (! in_array($blueprint['version'] ?? null, self::SUPPORTED_VERSIONS, true)) {
            throw new DomainException('إصدار ملف التصميم غير مدعوم.');
        }

        $stats = ['pages' => 0, 'blocks' => 0, 'menus' => 0, 'settings' => false, 'skipped' => []];

        DB::transaction(function () use ($blueprint, $actor, $replace, &$stats) {
            foreach ($blueprint['pages'] ?? [] as $pageData) {
                $slug = $pageData['slug'] ?? null;

                if (! $slug) {
                    continue;
                }

                $existing = WebsitePage::where('slug', $slug)->first();

                if ($existing && ! $replace) {
                    $stats['skipped'][] = $slug;

                    continue;
                }

                if ($existing) {
                    WebsiteBlock::where('website_page_id', $existing->id)->delete();
                    $existing->forceDelete();
                }

                $page = $this->pages->create([
                    'slug' => $slug,
                    'title' => $pageData['title'] ?? null,
                    'title_en' => $pageData['title_en'] ?? null,
                    'meta_description' => $pageData['meta_description'] ?? null,
                    'meta_description_en' => $pageData['meta_description_en'] ?? null,
                    'layout' => $pageData['layout'] ?? 'default',
                    'is_published' => $pageData['is_published'] ?? false,
                    'sort_order' => $pageData['sort_order'] ?? 0,
                ], $actor);

                foreach ($pageData['blocks'] ?? [] as $i => $blockData) {
                    $stats['blocks'] += $this->importBlock($blockData, $page->id, null, $i, $stats);
                }

                if ($pageData['is_homepage'] ?? false) {
                    $this->pages->makeHomepage($page);
                }

                $stats['pages']++;
            }

            // Settings and menus land after the pages, because a menu item that
            // points at a page can only be resolved once that page exists.
            if (! empty($blueprint['settings'])) {
                $this->importSettings($blueprint['settings']);
                $stats['settings'] = true;
            }

            if (! empty($blueprint['menus'])) {
                $stats['menus'] = $this->importMenus($blueprint['menus'], $replace);
            }
        });

        return $stats;
    }

    /** Applies only the whitelisted presentation keys the blueprint carries. */
    private function importSettings(array $settings): void
    {
        $academy = app('current_academy');
        $row = WebsiteSetting::firstOrCreate(['academy_id' => $academy->id]);

        $row->fill(collect($settings)
            ->only(self::PORTABLE_SETTINGS)
            ->all());

        $row->save();
    }

    /**
     * Rebuilds the named menus from the blueprint.
     *
     * With $replace the menu is emptied first; without it a menu that already
     * has items is left alone, so an import cannot silently duplicate a
     * navigation the client has since edited by hand.
     */
    private function importMenus(array $menus, bool $replace): int
    {
        $service = app(WebsiteMenuService::class);
        $pageIds = WebsitePage::pluck('id', 'slug');
        $created = 0;

        foreach ($menus as $key => $items) {
            if (! in_array($key, WebsiteMenuService::KEYS, true) || ! is_array($items)) {
                continue;
            }

            $menu = $service->getOrCreate($key);
            $existing = WebsiteMenuItem::where('website_menu_id', $menu->id)->exists();

            if ($existing && ! $replace) {
                continue;
            }

            WebsiteMenuItem::where('website_menu_id', $menu->id)->delete();

            $created += $this->importMenuItems($items, $menu->id, null, $pageIds);
        }

        return $created;
    }

    private function importMenuItems(array $items, int $menuId, ?int $parentId, $pageIds): int
    {
        $created = 0;

        foreach (array_values($items) as $i => $data) {
            if (! is_array($data)) {
                continue;
            }

            $pageId = isset($data['page_slug']) ? ($pageIds[$data['page_slug']] ?? null) : null;
            $linkType = $data['link_type'] ?? 'none';

            // A page link whose target did not come with the blueprint would
            // render as a dead entry; demote it to a plain label instead.
            if ($linkType === 'page' && ! $pageId) {
                $linkType = 'none';
            }

            $item = WebsiteMenuItem::create([
                'website_menu_id' => $menuId,
                'parent_id' => $parentId,
                'label' => $data['label'] ?? null,
                'label_en' => $data['label_en'] ?? null,
                'link_type' => $linkType,
                'website_page_id' => $linkType === 'page' ? $pageId : null,
                'url' => $linkType === 'url' ? safe_url($data['url'] ?? null) : null,
                'anchor' => $linkType === 'anchor' ? ($data['anchor'] ?? null) : null,
                'route_name' => $linkType === 'route' ? ($data['route_name'] ?? null) : null,
                'icon' => $data['icon'] ?? null,
                'open_in_new_tab' => (bool) ($data['open_in_new_tab'] ?? false),
                'is_visible' => (bool) ($data['is_visible'] ?? true),
                'highlight' => (bool) ($data['highlight'] ?? false),
                'sort_order' => $i,
            ]);

            $created++;

            if (! empty($data['children']) && is_array($data['children'])) {
                $created += $this->importMenuItems($data['children'], $menuId, $item->id, $pageIds);
            }
        }

        return $created;
    }

    /**
     * Strips unsafe link targets from imported content.
     *
     * A blueprint is a file, so it bypasses the form validation that normally
     * constrains link fields. Without this an imported design could carry a
     * `javascript:` URL straight into a rendered href.
     */
    private function sanitizeData(BlockType $definition, array $data): array
    {
        foreach ($definition->fields() as $field) {
            if (! array_key_exists($field->key, $data)) {
                continue;
            }

            if ($field->type === FieldType::Link) {
                $data[$field->key] = safe_url(is_string($data[$field->key]) ? $data[$field->key] : null);

                continue;
            }

            if ($field->type === FieldType::Repeater && is_array($data[$field->key])) {
                foreach ($data[$field->key] as $i => $row) {
                    if (! is_array($row)) {
                        continue;
                    }

                    foreach ($field->fields as $sub) {
                        if ($sub->type === FieldType::Link && array_key_exists($sub->key, $row)) {
                            $data[$field->key][$i][$sub->key] = safe_url(
                                is_string($row[$sub->key]) ? $row[$sub->key] : null,
                            );
                        }
                    }
                }
            }
        }

        return $data;
    }

    private function importBlock(array $data, int $pageId, ?int $parentId, int $order, array &$stats): int
    {
        $type = $data['type'] ?? null;

        // An unknown type means the blueprint came from a build with a block we
        // do not have. Skip it rather than aborting the whole import.
        if (! $type || ! $this->registry->has($type)) {
            $stats['skipped'][] = "block:{$type}";

            return 0;
        }

        $definition = $this->registry->resolve($type);
        $variants = array_keys($definition->variants());
        $variant = $data['variant'] ?? null;

        $block = WebsiteBlock::create([
            'website_page_id' => $pageId,
            'parent_id' => $parentId,
            'slot' => $data['slot'] ?? 'default',
            'type' => $type,
            'variant' => in_array($variant, $variants, true) ? $variant : ($variants[0] ?? 'default'),
            'sort_order' => $order,
            'is_enabled' => $data['is_enabled'] ?? true,
            'data' => $this->sanitizeData($definition, array_replace_recursive(
                $definition->defaultData(),
                $data['data'] ?? [],
            )),
            'style' => $data['style'] ?? [],
        ]);

        $count = 1;

        foreach ($data['children'] ?? [] as $i => $child) {
            $count += $this->importBlock($child, $pageId, $block->id, $i, $stats);
        }

        return $count;
    }
}
