<?php
/**
 * Entity Block Service
 * Frontend helper for loading and rendering entity blocks
 */
namespace Mtc\Modules\BlockRegistry\Services;

use Mtc\Modules\BlockRegistry\Models\EntityBlock;
use Mtc\Modules\BlockRegistry\Models\BlockRegistry;
use Mtc\Shop\Category;
use App\Http\Controllers\CategoryController;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use Twig\Environment;

class EntityBlockService
{
    /**
     * Twig environment for rendering templates
     */
    protected ?Environment $twig = null;

    /**
     * Set the Twig environment for rendering
     */
    public function setTwig(Environment $twig): self
    {
        $this->twig = $twig;
        return $this;
    }

    /**
     * Get the Twig environment (lazy load from app container if not set)
     */
    protected function getTwig(): ?Environment
    {
        if ($this->twig === null) {
            try {
                $this->twig = app('twig');
            } catch (\Exception $e) {
                Log::warning('Could not get Twig environment: ' . $e->getMessage());
                return null;
            }
        }
        return $this->twig;
    }

    /**
     * Get all entity blocks for any entity type (published only on frontend)
     */
    public function getBlocksForEntity(string $entityType, int $entityId): array
    {
        $entityBlocks = EntityBlock::forEntity($entityType, $entityId)
            ->published()
            ->withBlockData()
            ->ordered()
            ->get();

        // Resolve global references: swap in source data, filter out broken refs
        $resolved = $entityBlocks->filter(function ($entityBlock) {
            if ($entityBlock->isGlobalReference()) {
                $source = $entityBlock->sourceBlock;
                if (!$source || $source->isDraft()) {
                    return false;
                }
                // Use source block's data and block definition for formatting
                $entityBlock->data = $source->data;
                $entityBlock->setRelation('block', $source->block);
            }
            return true;
        });

        return $this->formatBlocksForTemplate($resolved);
    }

    /**
     * Format entity blocks for template use
     * Structure: block_slug => [ { field_name => value, ... }, ... ]
     */
    protected function formatBlocksForTemplate(Collection $entityBlocks): array
    {
        $result = [];

        foreach ($entityBlocks as $entityBlock) {
            if (!$entityBlock->block) {
                continue;
            }

            $blockSlug = $entityBlock->block->slug;
            $blockName = $entityBlock->block->name;
            $data = $entityBlock->data ?? [];

            // Build formatted data with field metadata
            $formattedData = $this->formatFieldValues($entityBlock->block->fields, $data)['formatted'];

            // Group by block slug (allows multiple instances of same block type)
            if (!isset($result[$blockSlug])) {
                $result[$blockSlug] = [];
            }
            $result[$blockSlug][] = $formattedData;

            // Also index by block name for convenience
            if ($blockSlug !== $blockName) {
                if (!isset($result[$blockName])) {
                    $result[$blockName] = [];
                }
                $result[$blockName][] = $formattedData;
            }
        }

        return $result;
    }

    /**
     * Get a single entity block by ID
     */
    public function getBlock(int $entityBlockId): ?array
    {
        $entityBlock = EntityBlock::with(['block', 'block.fields'])->find($entityBlockId);
        if (!$entityBlock) {
            return null;
        }

        $formatted = $this->formatBlocksForTemplate(collect([$entityBlock]));
        return $formatted[$entityBlock->block->slug][0] ?? null;
    }

    /**
     * Check if an entity has any blocks
     */
    public function hasBlocks(string $entityType, int $entityId): bool
    {
        return EntityBlock::forEntity($entityType, $entityId)->exists();
    }

    /**
     * Get raw entity block models for an entity
     */
    public function getRawBlocks(string $entityType, int $entityId): Collection
    {
        return EntityBlock::getBlocksForEntity($entityType, $entityId);
    }

    /**
     * Get count of blocks for an entity
     */
    public function getBlockCount(string $entityType, int $entityId): int
    {
        return EntityBlock::forEntity($entityType, $entityId)->count();
    }

    /**
     * Render a single entity block using its linked template
     * Returns HTML string or null if no template configured
     */
    public function renderBlock(EntityBlock $entityBlock, array $extraContext = []): ?string
    {
        // Handle global references: resolve to source block
        if ($entityBlock->isGlobalReference()) {
            $source = $entityBlock->sourceBlock;
            if (!$source || $source->isDraft()) {
                return null;
            }
            $source->loadMissing(['block.fields']);
            return $this->renderBlock($source, $extraContext);
        }

        $block = $entityBlock->block;

        if (!$block || !$block->hasTemplate()) {
            return null;
        }

        $twig = $this->getTwig();
        if (!$twig) {
            return null;
        }

        $templatePath = $block->getTemplatePath();

        // Prepare data in the format templates expect
        $templateData = $this->prepareBlockDataForTemplate($entityBlock);

        // Merge any extra context (e.g. class overrides) from the caller
        if (!empty($extraContext)) {
            $templateData = array_merge($templateData, $extraContext);
        }

        try {
            return $twig->render($templatePath, $templateData);
        } catch (\Exception $e) {
            Log::error("Failed to render block template {$templatePath}: " . $e->getMessage());
            return null;
        }
    }

    /**
     * Render all entity blocks for an entity that have templates configured
     * Returns concatenated HTML of all rendered blocks (published only)
     */
    public function renderEntityBlocks(string $entityType, int $entityId): string
    {
        $cacheKey = self::cacheKey($entityType, $entityId);
        $ttl = config('cache.block_registry.ttl', 3600);

        if (config('cache.block_registry.enabled', true)) {
            $cached = Cache::get($cacheKey);
            if ($cached !== null) {
                return $cached;
            }
        }

        $entityBlocks = EntityBlock::forEntity($entityType, $entityId)
            ->published()
            ->withBlockData()
            ->ordered()
            ->get();

        $extraContext = $this->resolveEntityContext($entityType, $entityId);
        $output = '';

        foreach ($entityBlocks as $entityBlock) {
            $rendered = $this->renderBlock($entityBlock, $extraContext);
            if ($rendered !== null) {
                $output .= $rendered;
            }
        }

        if (config('cache.block_registry.enabled', true)) {
            Cache::put($cacheKey, $output, $ttl);
        }

        return $output;
    }

    /**
     * Resolve entity object from type and ID for block template context
     */
    protected function resolveEntityContext(string $entityType, int $entityId): array
    {
        $context = [
            'entity_type' => $entityType,
            'entity_id' => $entityId,
        ];

        switch ($entityType) {
            case 'category':
                $category = Category::find($entityId);
                if ($category) {
                    $context['category'] = $category;
                    $context['items_doctor'] = CategoryController::getDisplayItems($category, ['doctor']);
                    $context['items_standard'] = CategoryController::getDisplayItems($category, ['general', 'pharmacy']);
                }
                break;
        }

        return $context;
    }

    /**
     * Render entity blocks of a specific block type (published only)
     */
    public function renderBlocksByType(string $entityType, int $entityId, string $blockSlug): string
    {
        $entityBlocks = EntityBlock::forEntity($entityType, $entityId)
            ->published()
            ->whereHas('block', function ($q) use ($blockSlug) {
                $q->where('slug', $blockSlug);
            })
            ->withBlockData()
            ->ordered()
            ->get();

        $output = '';

        foreach ($entityBlocks as $entityBlock) {
            $rendered = $this->renderBlock($entityBlock);
            if ($rendered !== null) {
                $output .= $rendered;
            }
        }

        return $output;
    }

    /**
     * Prepare block data in the format templates expect
     * Templates typically access: block_data['Title']['value'] or p.pagedata['Block'][0]['Field']['value']
     */
    protected function prepareBlockDataForTemplate(EntityBlock $entityBlock): array
    {
        $block = $entityBlock->block;
        $data = $entityBlock->data ?? [];

        // Build formatted data matching existing template expectations
        $fieldValues = $this->formatFieldValues($block->fields, $data);
        $formattedFields = $fieldValues['formatted'];
        $simpleFields = $fieldValues['simple'];

        // Build slides array for slideshow-type templates
        // Maps repeater data to the format slideshow.twig expects
        $slides = $this->buildSlidesFromRepeater($formattedFields, $simpleFields);

        // Return multiple access patterns for flexibility
        return [
            // The block definition
            'block' => $block,
            'block_name' => $block->name,
            'block_slug' => $block->slug,

            // Primary data access
            'block_data' => $formattedFields,
            'fields' => $simpleFields,

            // Slides array for slideshow templates
            'slides' => $slides,

            // Pagedata-compatible nested format
            'p' => [
                'pagedata' => [
                    $block->name => [$formattedFields],
                    $block->slug => [$formattedFields],
                ]
            ],
        ];
    }

    /**
     * Format field values from a block's field definitions and data
     * Returns both 'formatted' (with metadata) and 'simple' (raw values) arrays
     */
    private function formatFieldValues(Collection $fields, array $data): array
    {
        $formatted = [];
        $simple = [];

        foreach ($fields as $field) {
            $fieldName = $field->name;
            $rawValue = $data[$fieldName] ?? $field->default_value ?? '';

            if ($field->type === 'image' && is_string($rawValue) && str_starts_with($rawValue, '{')) {
                $imageData = json_decode($rawValue, true);
                if (is_array($imageData)) {
                    $formatted[$fieldName] = $this->formatImageField($imageData, $field);
                    $simple[$fieldName] = $imageData['url'] ?? '';
                    continue;
                }
            }

            if ($field->type === 'file' && is_string($rawValue) && str_starts_with($rawValue, '{')) {
                $fileData = json_decode($rawValue, true);
                if (is_array($fileData)) {
                    // Unwrap multi-encoded url values (from prior double-encoding bug)
                    $url = $fileData['url'] ?? '';
                    while (is_string($url) && str_starts_with($url, '{')) {
                        $nested = json_decode($url, true);
                        if (is_array($nested) && isset($nested['url'])) {
                            $url = $nested['url'];
                        } else {
                            break;
                        }
                    }
                    $fileData['url'] = $url;

                    $formatted[$fieldName] = array_merge($fileData, [
                        'value' => $url,
                        'type' => $field->type,
                        'label' => $field->label ?? $field->name,
                        'required' => $field->required,
                    ]);
                    $simple[$fieldName] = $url;
                    continue;
                }
            }

            if ($field->type === 'link' && is_string($rawValue) && str_starts_with($rawValue, '{')) {
                $linkData = json_decode($rawValue, true);
                if (is_array($linkData)) {
                    $formatted[$fieldName] = [
                        'value' => $linkData['url'] ?? '',
                        'url' => $linkData['url'] ?? '',
                        'title' => $linkData['title'] ?? '',
                        'target' => $linkData['target'] ?? '',
                        'type' => $field->type,
                        'label' => $field->label ?? $field->name,
                        'required' => $field->required,
                    ];
                    $simple[$fieldName] = $linkData;
                    continue;
                }
            }

            if ($field->type === 'repeater' && is_string($rawValue) && str_starts_with($rawValue, '[')) {
                $result = $this->formatRepeaterField($rawValue, $field);
                if ($result !== null) {
                    $formatted[$fieldName] = $result;
                    $simple[$fieldName] = $result['rows'];
                    continue;
                }
            }

            if ($field->type === 'page_selector') {
                $settings = is_string($field->settings) ? json_decode($field->settings, true) : ($field->settings ?? []);
                $psConfig = $settings['page_selector_settings'] ?? [];
                $mode = $psConfig['mode'] ?? 'manual';

                if ($mode === 'auto') {
                    $containerId = (int) $rawValue ?: null;
                    $limit = $psConfig['limit'] ?? 8;
                    $pages = $this->fetchPagesForAutoMode($containerId, $limit);
                    $formatted[$fieldName] = [
                        'value' => $pages,
                        'pages' => $pages,
                        'mode' => 'auto',
                        'type' => $field->type,
                        'label' => $field->label ?? $field->name,
                    ];
                    $simple[$fieldName] = $pages;
                } else {
                    $pageId = (int) $rawValue;
                    $pageData = $pageId ? $this->resolvePageData($pageId) : null;
                    $formatted[$fieldName] = [
                        'value' => $pageId,
                        'page_id' => $pageId,
                        'page' => $pageData,
                        'mode' => 'manual',
                        'type' => $field->type,
                        'label' => $field->label ?? $field->name,
                    ];
                    $simple[$fieldName] = $pageData;
                }
                continue;
            }

            $formatted[$fieldName] = $this->formatStandardField($rawValue, $field);
            $simple[$fieldName] = $rawValue;
        }

        return ['formatted' => $formatted, 'simple' => $simple];
    }

    /**
     * Format an image field from decoded JSON data
     */
    private function formatImageField(array $imageData, $field): array
    {
        $result = [
            'value' => $imageData['url'] ?? '',
            'url' => $imageData['url'] ?? '',
            'alt' => $imageData['alt'] ?? '',
            'crop' => $imageData['crop'] ?? null,
            'srcset' => $imageData['srcset'] ?? '',
            'processed' => $imageData['processed'] ?? [],
            'original_width' => $imageData['original_width'] ?? null,
            'original_height' => $imageData['original_height'] ?? null,
        ];

        // Add field metadata when a field definition is provided
        if (is_object($field)) {
            $result['type'] = $field->type;
            $result['label'] = $field->label ?? $field->name;
            $result['required'] = $field->required;
        }

        return $result;
    }

    /**
     * Format a repeater field from raw JSON string
     * Returns null if the JSON is invalid
     */
    private function formatRepeaterField(string $rawValue, $field): ?array
    {
        $repeaterData = json_decode($rawValue, true);
        if (!is_array($repeaterData)) {
            return null;
        }

        $formattedRows = [];
        foreach ($repeaterData as $row) {
            $formattedRow = [];
            foreach ($row as $subFieldName => $subFieldValue) {
                if (is_array($subFieldValue) && isset($subFieldValue['url']) && isset($subFieldValue['title'])) {
                    // Link sub-field
                    $formattedRow[$subFieldName] = [
                        'value' => $subFieldValue['url'] ?? '',
                        'url' => $subFieldValue['url'] ?? '',
                        'title' => $subFieldValue['title'] ?? '',
                        'target' => $subFieldValue['target'] ?? '',
                    ];
                } elseif (is_array($subFieldValue) && isset($subFieldValue['url'])) {
                    // Image/file sub-field
                    $formattedRow[$subFieldName] = [
                        'value' => $subFieldValue['url'] ?? '',
                        'url' => $subFieldValue['url'] ?? '',
                        'alt' => $subFieldValue['alt'] ?? '',
                        'crop' => $subFieldValue['crop'] ?? null,
                        'srcset' => $subFieldValue['srcset'] ?? '',
                        'processed' => $subFieldValue['processed'] ?? [],
                        'original_width' => $subFieldValue['original_width'] ?? null,
                        'original_height' => $subFieldValue['original_height'] ?? null,
                    ];
                } elseif (is_numeric($subFieldValue) && $this->isPageSelectorSubField($field, $subFieldName)) {
                    // Page selector sub-field — resolve to full page data
                    $pageData = $this->resolvePageData((int) $subFieldValue);
                    $formattedRow[$subFieldName] = [
                        'value' => (int) $subFieldValue,
                        'page_id' => (int) $subFieldValue,
                        'page' => $pageData,
                    ];
                } else {
                    $formattedRow[$subFieldName] = [
                        'value' => $subFieldValue,
                    ];
                }
            }
            $formattedRows[] = $formattedRow;
        }

        return [
            'rows' => $formattedRows,
            'count' => count($formattedRows),
            'type' => $field->type,
            'label' => $field->label ?? $field->name,
        ];
    }

    /**
     * Check if a sub-field is a page_selector type
     */
    private function isPageSelectorSubField($field, string $subFieldName): bool
    {
        // Try eager-loaded relation first
        if ($field->relationLoaded('subFields')) {
            return $field->subFields->contains(function ($sf) use ($subFieldName) {
                return $sf->name === $subFieldName && $sf->type === 'page_selector';
            });
        }

        // Fall back to a query if not eager-loaded
        return $field->subFields()
            ->where('name', $subFieldName)
            ->where('type', 'page_selector')
            ->exists();
    }

    /**
     * Resolve a page ID to full page data including pagedata
     */
    private function resolvePageData(int $pageId): ?array
    {
        try {
            $page = new \Page($pageId);
            if (!$page->id) {
                return null;
            }
            return [
                'id' => $page->id,
                'title' => $page->title,
                'url' => $page->url,
                'pagedata' => $page->pagedata ?? [],
            ];
        } catch (\Exception $e) {
            Log::warning("BlockRegistry: Failed to resolve page {$pageId}: " . $e->getMessage());
            return null;
        }
    }

    /**
     * Fetch pages from a listing container for auto mode page_selector
     */
    private function fetchPagesForAutoMode(?int $containerId, int $limit): array
    {
        if (!$containerId) {
            return [];
        }
        try {
            $childIds = \Mtc\Cms\Models\Page::query()
                ->where('sub_id', $containerId)
                ->where('type', 'default')
                ->where('published', 1)
                ->orderByDesc('id')
                ->limit($limit)
                ->pluck('id')
                ->toArray();

            $pages = [];
            foreach ($childIds as $id) {
                $page = new \Page($id);
                if ($page->id) {
                    $pages[] = [
                        'id' => $page->id,
                        'title' => $page->title,
                        'url' => $page->url,
                        'pagedata' => $page->pagedata ?? [],
                    ];
                }
            }
            return $pages;
        } catch (\Exception $e) {
            Log::warning("BlockRegistry: Failed to fetch auto pages for container {$containerId}: " . $e->getMessage());
            return [];
        }
    }

    /**
     * Format a standard (non-image, non-repeater) field
     */
    private function formatStandardField($rawValue, $field): array
    {
        return [
            'value' => $rawValue,
            'type' => $field->type,
            'label' => $field->label ?? $field->name,
            'required' => $field->required,
        ];
    }

    /**
     * Build slides array from repeater field data
     * Converts repeater rows to the format slideshow.twig expects
     */
    protected function buildSlidesFromRepeater(array $formattedFields, array $simpleFields): array
    {
        $slides = [];

        // Look for repeater fields and convert to slides format
        foreach ($formattedFields as $fieldName => $fieldData) {
            if (isset($fieldData['type']) && $fieldData['type'] === 'repeater' && isset($fieldData['rows'])) {
                foreach ($fieldData['rows'] as $row) {
                    $slide = [];

                    // Map common field names to slideshow expected keys
                    foreach ($row as $subFieldName => $subFieldData) {
                        $value = $subFieldData['value'] ?? '';
                        $lowerName = strtolower($subFieldName);

                        // Map field names to slideshow expected properties
                        if (in_array($lowerName, ['image', 'img', 'photo', 'picture'])) {
                            $slide['image'] = $value;
                            if (isset($subFieldData['alt'])) {
                                $slide['image_alt'] = $subFieldData['alt'];
                            }
                            // Pass through full image data for responsive_image()
                            if (isset($subFieldData['srcset']) && !empty($subFieldData['srcset'])) {
                                $slide['image_data'] = $subFieldData;
                            }
                        } elseif (in_array($lowerName, ['title', 'heading', 'headline'])) {
                            $slide['title'] = $value;
                        } elseif (in_array($lowerName, ['description', 'desc', 'content', 'text', 'body'])) {
                            $slide['description'] = $value;
                        } elseif (in_array($lowerName, ['link', 'url', 'href'])) {
                            $slide['link'] = $value;
                        } elseif (in_array($lowerName, ['link_text', 'linktext', 'button', 'button_text', 'cta'])) {
                            $slide['link_text'] = $value;
                        } else {
                            // Pass through any other fields with original name
                            $slide[$subFieldName] = $value;
                        }
                    }

                    if (!empty($slide)) {
                        $slides[] = $slide;
                    }
                }

                // Only process the first repeater field as slides
                break;
            }
        }

        return $slides;
    }

    /**
     * Get all global blocks with reference counts
     */
    public function getGlobalBlocks(): Collection
    {
        return EntityBlock::global()
            ->with(['block', 'block.fields', 'block.category'])
            ->withCount('references')
            ->ordered()
            ->get();
    }

    /**
     * Add a global block reference to an entity (page, item, etc.)
     */
    public function addGlobalBlockReference(int $globalBlockId, string $entityType, int $entityId): EntityBlock
    {
        $globalBlock = EntityBlock::findOrFail($globalBlockId);

        return EntityBlock::create([
            'entity_type' => $entityType,
            'entity_id' => $entityId,
            'block_id' => $globalBlock->block_id,
            'source_block_id' => $globalBlock->id,
            'status' => EntityBlock::STATUS_PUBLISHED,
        ]);
    }

    /**
     * Get entity blocks with their rendered HTML where templates exist
     */
    public function getBlocksWithRendering(string $entityType, int $entityId): array
    {
        $entityBlocks = EntityBlock::forEntity($entityType, $entityId)
            ->withBlockData()
            ->ordered()
            ->get();

        $result = [];

        foreach ($entityBlocks as $entityBlock) {
            $rendered = $this->renderBlock($entityBlock);

            $result[] = [
                'id' => $entityBlock->id,
                'block_id' => $entityBlock->block_id,
                'block_name' => $entityBlock->block->name,
                'block_slug' => $entityBlock->block->slug,
                'template' => $entityBlock->block->template,
                'has_template' => $entityBlock->block->hasTemplate(),
                'data' => $entityBlock->data,
                'rendered_html' => $rendered,
            ];
        }

        return $result;
    }

    /**
     * Build the cache key for an entity's rendered blocks
     */
    public static function cacheKey(string $entityType, int $entityId): string
    {
        return "block_html:{$entityType}:{$entityId}";
    }

    /**
     * Clear the rendered block cache for an entity
     */
    public static function clearCache(string $entityType, int $entityId): void
    {
        Cache::forget(self::cacheKey($entityType, $entityId));
    }
}
