"""Magento setup artifacts for collection landing category attributes.

Magento core REST exposes GET for category attributes only — POST
``/V1/categories/attributes`` is not a valid route. Attributes must be
installed via a Magento data patch (or admin/module setup).
"""

from __future__ import annotations

from typing import Any, Dict, List

from db.collection_landing_schema import (
    COLLECTION_LANDING_FIELDS,
    field_label,
    magento_attribute_code,
)

MAGENTO_MODULE_VENDOR = "PieHS"
MAGENTO_ATTRIBUTE_GROUP = f"{MAGENTO_MODULE_VENDOR} Collection Landing"
MAGENTO_SETUP_PATCH_CLASS = f"Add{MAGENTO_MODULE_VENDOR}CollectionLandingAttributes"
MAGENTO_MODULE_CODE = f"{MAGENTO_MODULE_VENDOR}_CollectionLanding"
MAGENTO_MODULE_DEPLOY_ROOT = f"app/code/{MAGENTO_MODULE_VENDOR}/CollectionLanding/"
MAGENTO_SETUP_PATCH_RELATIVE_PATH = (
    f"{MAGENTO_MODULE_DEPLOY_ROOT}Setup/Patch/Data/"
    f"{MAGENTO_SETUP_PATCH_CLASS}.php"
)


def magento_eav_type(frontend_input: str) -> str:
    if frontend_input == "boolean":
        return "int"
    if frontend_input == "textarea":
        return "text"
    return "varchar"


def magento_attribute_eav_config(field: Dict[str, str]) -> Dict[str, Any]:
    frontend_input = field["magento_input"]
    config: Dict[str, Any] = {
        "type": magento_eav_type(frontend_input),
        "label": field_label(field["key"]),
        "input": frontend_input,
    }
    if frontend_input == "boolean":
        config["source"] = "Magento\\Eav\\Model\\Entity\\Attribute\\Source\\Boolean"
    if frontend_input == "textarea":
        config["wysiwyg_enabled"] = False
        config["is_html_allowed_on_front"] = True
    return config


def build_magento_category_attributes_data_patch() -> str:
    """Return PHP DataPatch source that creates all hs_* category attributes."""
    attribute_blocks: List[str] = []
    for field in COLLECTION_LANDING_FIELDS:
        code = magento_attribute_code(field["key"])
        eav = magento_attribute_eav_config(field)
        extra_lines: List[str] = []
        for key, value in eav.items():
            if isinstance(value, bool):
                php_value = "true" if value else "false"
            else:
                php_value = f"'{value}'"
            extra_lines.append(f"                    '{key}' => {php_value},")
        attribute_blocks.append(
            "            "
            + repr(code)
            + " => [\n"
            + "\n".join(extra_lines)
            + "\n                ],"
        )

    attributes_php = "\n".join(attribute_blocks)
    return f"""<?php
declare(strict_types=1);

namespace {MAGENTO_MODULE_VENDOR}\\CollectionLanding\\Setup\\Patch\\Data;

use Magento\\Catalog\\Model\\Category;
use Magento\\Eav\\Model\\Entity\\Attribute\\ScopedAttributeInterface;
use Magento\\Eav\\Setup\\EavSetupFactory;
use Magento\\Framework\\Setup\\ModuleDataSetupInterface;
use Magento\\Framework\\Setup\\Patch\\DataPatchInterface;

class {MAGENTO_SETUP_PATCH_CLASS} implements DataPatchInterface
{{
    private const ATTRIBUTE_GROUP = '{MAGENTO_ATTRIBUTE_GROUP}';

    public function __construct(
        private readonly ModuleDataSetupInterface $moduleDataSetup,
        private readonly EavSetupFactory $eavSetupFactory,
    ) {{
    }}

    public function apply(): void
    {{
        $eavSetup = $this->eavSetupFactory->create(['setup' => $this->moduleDataSetup]);
        $attributes = [
{attributes_php}
        ];

        foreach ($attributes as $code => $config) {{
            if ($eavSetup->getAttributeId(Category::ENTITY, $code)) {{
                continue;
            }}
            $eavSetup->addAttribute(
                Category::ENTITY,
                $code,
                array_merge(
                    [
                        'required' => false,
                        'sort_order' => 100,
                        'global' => ScopedAttributeInterface::SCOPE_STORE,
                        'visible' => true,
                        'group' => self::ATTRIBUTE_GROUP,
                        'is_used_in_grid' => false,
                        'is_visible_in_grid' => false,
                        'is_filterable_in_grid' => false,
                    ],
                    $config
                )
            );
        }}
    }}

    public static function getDependencies(): array
    {{
        return [];
    }}

    public function getAliases(): array
    {{
        return [];
    }}
}}
"""


def build_magento_collection_landing_module_files() -> Dict[str, str]:
    """Relative paths under module root → file contents."""
    registration = f"""<?php
use Magento\\Framework\\Component\\ComponentRegistrar;

ComponentRegistrar::register(
    ComponentRegistrar::MODULE,
    '{MAGENTO_MODULE_CODE}',
    __DIR__
);
"""
    module_xml = f"""<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="{MAGENTO_MODULE_CODE}" setup_version="1.0.0">
        <sequence>
            <module name="Magento_Catalog"/>
            <module name="Magento_Eav"/>
            <module name="Magento_LayeredNavigation"/>
        </sequence>
    </module>
</config>
"""
    webapi_xml = """<?xml version="1.0"?>
<routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Webapi:etc/webapi.xsd">
    <route url="/V1/piehs/collection-media" method="POST">
        <service class="PieHS\\CollectionLanding\\Api\\CollectionMediaManagementInterface" method="upload"/>
        <resources><resource ref="Magento_Catalog::categories"/></resources>
    </route>
</routes>
"""
    di_xml = """<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <preference for="PieHS\\CollectionLanding\\Api\\CollectionMediaManagementInterface"
                type="PieHS\\CollectionLanding\\Model\\CollectionMediaManagement"/>
    <type name="Magento\\Catalog\\Model\\Layer\\FilterList">
        <plugin name="piehs_collection_landing_filter_list"
                type="PieHS\\CollectionLanding\\Plugin\\Catalog\\Layer\\FilterListPlugin"/>
    </type>
    <type name="Amasty\\Shopby\\Model\\Layer\\FilterList">
        <plugin name="piehs_collection_landing_amasty_filter_list"
                type="PieHS\\CollectionLanding\\Plugin\\Catalog\\Layer\\FilterListPlugin"/>
    </type>
    <type name="Magento\\Catalog\\Model\\Layer">
        <plugin name="piehs_collection_landing_intersection_category_filter"
                type="PieHS\\CollectionLanding\\Plugin\\Catalog\\Layer\\IntersectionCategoryFilterPlugin"/>
    </type>
    <type name="Magento\\LayeredNavigation\\Block\\Navigation\\State">
        <plugin name="piehs_collection_landing_layer_state"
                type="PieHS\\CollectionLanding\\Plugin\\Layer\\StatePlugin"/>
    </type>
    <type name="Amasty\\Shopby\\Block\\Navigation\\State">
        <plugin name="piehs_collection_landing_amasty_layer_state"
                type="PieHS\\CollectionLanding\\Plugin\\Layer\\StatePlugin"/>
    </type>
</config>
"""
    frontend_di_xml = """<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\\Framework\\App\\RouterList">
        <arguments>
            <argument name="routerList" xsi:type="array">
                <item name="piehs_collection_intersection" xsi:type="array">
                    <item name="class" xsi:type="string">PieHS\\CollectionLanding\\Controller\\Router\\Intersection</item>
                    <item name="disable" xsi:type="boolean">false</item>
                    <item name="sortOrder" xsi:type="string">30</item>
                </item>
            </argument>
        </arguments>
    </type>
</config>
"""
    media_interface = r"""<?php
declare(strict_types=1);

namespace PieHS\CollectionLanding\Api;

interface CollectionMediaManagementInterface
{
    /**
     * Store a collection image or PDF under Magento pub/media.
     *
     * @param string $filename
     * @param string $mimeType
     * @param string $base64Content
     * @return string Public Magento media URL
     */
    public function upload(string $filename, string $mimeType, string $base64Content): string;
}
"""
    media_model = r"""<?php
declare(strict_types=1);

namespace PieHS\CollectionLanding\Model;

use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Filesystem;
use Magento\Store\Model\StoreManagerInterface;
use PieHS\CollectionLanding\Api\CollectionMediaManagementInterface;

class CollectionMediaManagement implements CollectionMediaManagementInterface
{
    private const MEDIA_DIR = 'piehs/collections';
    private const ALLOWED_MIME = [
        'image/jpeg' => 'jpg',
        'image/png' => 'png',
        'image/gif' => 'gif',
        'image/webp' => 'webp',
        'application/pdf' => 'pdf',
    ];

    public function __construct(
        private readonly Filesystem $filesystem,
        private readonly StoreManagerInterface $storeManager
    ) {
    }

    public function upload(string $filename, string $mimeType, string $base64Content): string
    {
        $mimeType = strtolower(trim($mimeType));
        if (!isset(self::ALLOWED_MIME[$mimeType])) {
            throw new LocalizedException(__('Unsupported collection media type: %1', $mimeType));
        }
        $content = base64_decode($base64Content, true);
        if ($content === false || $content === '') {
            throw new LocalizedException(__('Collection media content is empty or invalid.'));
        }
        if (strlen($content) > 25 * 1024 * 1024) {
            throw new LocalizedException(__('Collection media must not exceed 25 MB.'));
        }

        $base = pathinfo($filename, PATHINFO_FILENAME);
        $base = preg_replace('/[^a-zA-Z0-9._-]+/', '-', $base) ?: 'asset';
        $base = trim($base, '-._') ?: 'asset';
        $safeName = substr($base, 0, 100) . '.' . self::ALLOWED_MIME[$mimeType];
        $relativePath = self::MEDIA_DIR . '/' . $safeName;
        $media = $this->filesystem->getDirectoryWrite(DirectoryList::MEDIA);
        $media->create(self::MEDIA_DIR);
        if (!$media->isExist($relativePath)) {
            $media->writeFile($relativePath, $content);
        }

        return rtrim($this->storeManager->getStore()->getBaseUrl(\Magento\Framework\UrlInterface::URL_TYPE_MEDIA), '/')
            . '/' . $relativePath;
    }
}
"""
    filter_list_plugin = r"""<?php
declare(strict_types=1);

namespace PieHS\CollectionLanding\Plugin\Catalog\Layer;

use Magento\Catalog\Model\Layer;

/**
 * Warm the product collection before facet rendering so IntersectionCategoryFilterPlugin
 * participates in layered-nav counts.
 *
 * Full FilterListPlugin (hide category chips, shopping facets, etc.) lives in the
 * Magento repos (HSMageDSCopy / HSMageDSSGit). This bootstrap stub only ensures
 * intersection AND-filters affect facet counts.
 */
class FilterListPlugin
{
    public function beforeGetFilters($subject, Layer $layer): array
    {
        $layer->getProductCollection();
        return [$layer];
    }
}
"""
    intersection_category_filter_plugin = r"""<?php
declare(strict_types=1);

namespace PieHS\CollectionLanding\Plugin\Catalog\Layer;

use Magento\Catalog\Model\Layer;
use Magento\Catalog\Model\ResourceModel\Product\Collection;
use Magento\Framework\App\RequestInterface;
use Magento\Framework\Registry;
use PieHS\CollectionLanding\Model\ManagedCategory;

/**
 * Ensures SEO intersection URLs apply taxonomy category membership as a layered
 * AND filter on the collection category product set.
 *
 * Uses dedicated hs_intersection_category_id (not native cat/category_ids) so
 * Magento/Amasty category state is not hijacked.
 */
class IntersectionCategoryFilterPlugin
{
    private const PARAM = 'hs_intersection_category_id';
    private const FLAG = 'piehs_intersection_category_applied';

    public function __construct(
        private readonly RequestInterface $request,
        private readonly Registry $registry,
        private readonly ManagedCategory $managedCategory
    ) {
    }

    /**
     * @param Layer $subject
     * @param Collection $result
     * @return Collection
     */
    public function afterGetProductCollection(Layer $subject, $result)
    {
        if (!$result instanceof Collection) {
            return $result;
        }
        if ($result->hasFlag(self::FLAG)) {
            return $result;
        }
        if (!$this->managedCategory->isManaged($this->registry->registry('current_category'))) {
            return $result;
        }

        $filterCategoryId = $this->resolveIntersectionCategoryId();
        $pageCategoryId = (int) $this->request->getParam('id');
        if ($filterCategoryId <= 0 || $filterCategoryId === $pageCategoryId) {
            $result->setFlag(self::FLAG, true);
            return $result;
        }

        $result->addCategoriesFilter(['in' => [$filterCategoryId]]);
        $result->setFlag(self::FLAG, true);

        return $result;
    }

    private function resolveIntersectionCategoryId(): int
    {
        foreach ([self::PARAM, 'cat', 'category_ids', 'category'] as $param) {
            $raw = trim((string) $this->request->getParam($param, ''));
            if ($raw === '') {
                continue;
            }
            $first = trim(explode(',', $raw)[0]);
            if ($first !== '' && ctype_digit($first)) {
                return (int) $first;
            }
        }

        return 0;
    }
}
"""
    state_plugin = r"""<?php
declare(strict_types=1);

namespace PieHS\CollectionLanding\Plugin\Layer;

use Magento\Framework\Registry;
use PieHS\CollectionLanding\Model\ManagedCategory;

/**
 * Hide active-filter chips that duplicate collection-landing / intersection context
 * (including hs_intersection_category_id) so shoppers don't see a bogus Category pill.
 */
class StatePlugin
{
    /** @var string[] */
    private const HIDDEN_REQUEST_VARS = [
        'hs_intersection_category_id',
        'cat',
        'category',
        'category_ids',
        'brand_category',
    ];

    public function __construct(
        private readonly Registry $registry,
        private readonly ManagedCategory $managedCategory
    ) {
    }

    /**
     * @param mixed $subject
     * @param array<int, mixed> $filters
     * @return array<int, mixed>
     */
    public function afterGetActiveFilters(mixed $subject, array $filters): array
    {
        if (!$this->managedCategory->isManaged($this->registry->registry('current_category'))) {
            return $filters;
        }

        return array_values(array_filter(
            $filters,
            static function ($filter): bool {
                if (!is_object($filter) || !method_exists($filter, 'getFilter')) {
                    return true;
                }

                $layerFilter = $filter->getFilter();
                if (!is_object($layerFilter) || !method_exists($layerFilter, 'getRequestVar')) {
                    return true;
                }

                return !in_array((string) $layerFilter->getRequestVar(), self::HIDDEN_REQUEST_VARS, true);
            }
        ));
    }
}
"""
    intersection_router = r"""<?php
declare(strict_types=1);

namespace PieHS\CollectionLanding\Controller\Router;

use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory;
use Magento\Framework\App\Action\Forward;
use Magento\Framework\App\ActionFactory;
use Magento\Framework\App\RequestInterface;
use Magento\Framework\App\RouterInterface;
use Magento\Framework\Serialize\Serializer\Json;
use Magento\Store\Model\StoreManagerInterface;

class Intersection implements RouterInterface
{
    private const FIELD = 'hs_shopping_intersections';
    private const INTERSECTION_CATEGORY_PARAM = 'hs_intersection_category_id';

    public function __construct(
        private readonly ActionFactory $actionFactory,
        private readonly CollectionFactory $categoryCollectionFactory,
        private readonly Json $json,
        private readonly StoreManagerInterface $storeManager
    ) {
    }

    public function match(RequestInterface $request)
    {
        $path = trim((string)$request->getPathInfo(), '/');
        if ($path === '' || !str_ends_with($path, '.html')) {
            return null;
        }
        $seoPath = preg_replace('/\.html$/', '', $path);
        if (!$seoPath || substr_count($seoPath, '/') < 2) {
            return null;
        }

        $collection = $this->categoryCollectionFactory->create();
        $collection->setStoreId((int)$this->storeManager->getStore()->getId());
        $collection->addAttributeToSelect(['name', self::FIELD]);
        $collection->addAttributeToFilter(self::FIELD, ['like' => '%' . $seoPath . '%']);
        $collection->setPageSize(25);

        foreach ($collection as $category) {
            $raw = (string)$category->getData(self::FIELD);
            if ($raw === '') {
                continue;
            }
            try {
                $items = $this->json->unserialize($raw);
            } catch (\Throwable $e) {
                continue;
            }
            if (!is_array($items)) {
                continue;
            }
            foreach ($items as $item) {
                if (!is_array($item) || (string)($item['seo_path'] ?? '') !== $seoPath) {
                    continue;
                }
                $filterType = (string)($item['filter_type'] ?? '');
                $filterAttribute = (string)($item['filter_attribute'] ?? '');
                $filterValue = (string)($item['filter_value'] ?? '');
                $filterCategoryId = $this->resolveFilterCategoryId($item);
                // Shopping/category intersections: collection page + taxonomy layered filter.
                $useCategoryFilter = $filterType === 'category'
                    || $filterAttribute === 'category_ids'
                    || $filterCategoryId > 0;
                if ($useCategoryFilter && $filterCategoryId > 0) {
                    $request->setParam(self::INTERSECTION_CATEGORY_PARAM, (string)$filterCategoryId);
                } elseif ($filterAttribute !== '' && $filterValue !== '' && $filterAttribute !== 'category_ids') {
                    $request->setParam($filterAttribute, $filterValue);
                }
                $request->setModuleName('catalog')
                    ->setControllerName('category')
                    ->setActionName('view')
                    ->setParam('id', (int)$category->getId());
                return $this->actionFactory->create(Forward::class);
            }
        }

        return null;
    }

    private function resolveFilterCategoryId(array $item): int
    {
        $categoryId = (string)($item['filter_category_id'] ?? '');
        if ($categoryId !== '' && ctype_digit($categoryId)) {
            return (int)$categoryId;
        }

        $path = trim((string)($item['filter_category_path'] ?? ''), '/');
        if ($path === '') {
            return 0;
        }

        $collection = $this->categoryCollectionFactory->create();
        $collection->setStoreId((int)$this->storeManager->getStore()->getId());
        $collection->addAttributeToSelect(['hs_taxonomy_path_slug']);
        $collection->addAttributeToFilter('is_active', 1);
        $collection->addAttributeToFilter('hs_taxonomy_path_slug', $path);
        $collection->setPageSize(1);
        $category = $collection->getFirstItem();

        return $category->getId() ? (int)$category->getId() : 0;
    }
}
"""
    # Full category-filters.js (AMD widget bound via shopping.phtml data-mage-init)
    # lives only in the Magento repos — do not emit a stub that would overwrite it.
    requirejs_config = """var config = {
    config: {
        mixins: {
            'Mageplaza_LayeredNavigation/js/action/submit-filter': {
                'PieHS_CollectionLanding/js/submit-filter-mixin': true
            }
        }
    }
};
"""
    managed_category = r"""<?php
declare(strict_types=1);

namespace PieHS\CollectionLanding\Model;

use Magento\Catalog\Model\Category;

/**
 * PieHS collection/hub/taxonomy pages only.
 *
 * Do not infer "managed" from category-tree shape (active children / siblings) —
 * that incorrectly classifies ordinary Magento PLPs.
 */
final class ManagedCategory
{
    public function isManaged(?Category $category): bool
    {
        if (!$category instanceof Category) {
            return false;
        }

        return (bool) $category->getData('hs_is_collection')
            || (bool) $category->getData('hs_has_collections')
            || trim((string) $category->getData('hs_taxonomy_path_slug')) !== '';
    }
}
"""
    submit_filter_mixin = r"""define([
    'jquery'
], function ($) {
    'use strict';

    return function (submitFilterAction) {
        return function (submitUrl, isChangeUrl, method) {
            if ($('body').hasClass('piehs-managed-category') && typeof isChangeUrl === 'undefined') {
                isChangeUrl = true;
            }

            return submitFilterAction(submitUrl, isChangeUrl, method);
        };
    };
});
"""
    return {
        "registration.php": registration,
        "etc/module.xml": module_xml,
        "etc/webapi.xml": webapi_xml,
        "etc/di.xml": di_xml,
        "etc/frontend/di.xml": frontend_di_xml,
        "Api/CollectionMediaManagementInterface.php": media_interface,
        "Model/CollectionMediaManagement.php": media_model,
        "Model/ManagedCategory.php": managed_category,
        "Controller/Router/Intersection.php": intersection_router,
        "Plugin/Catalog/Layer/FilterListPlugin.php": filter_list_plugin,
        "Plugin/Catalog/Layer/IntersectionCategoryFilterPlugin.php": intersection_category_filter_plugin,
        "Plugin/Layer/StatePlugin.php": state_plugin,
        "view/frontend/requirejs-config.js": requirejs_config,
        "view/frontend/web/js/submit-filter-mixin.js": submit_filter_mixin,
        f"Setup/Patch/Data/{MAGENTO_SETUP_PATCH_CLASS}.php": build_magento_category_attributes_data_patch(),
    }


def magento_collection_landing_module_deploy_instructions() -> Dict[str, Any]:
    return {
        "module_code": MAGENTO_MODULE_CODE,
        "deploy_root": MAGENTO_MODULE_DEPLOY_ROOT,
        "media_upload_route": "/V1/piehs/collection-media",
        "required_media_api_files": [
            "etc/webapi.xml",
            "etc/di.xml",
            "etc/frontend/di.xml",
            "Api/CollectionMediaManagementInterface.php",
            "Model/CollectionMediaManagement.php",
            "Controller/Router/Intersection.php",
        ],
        "source_of_truth": (
            "HSMageDSCopy app/code/PieHS/CollectionLanding — test there, then sync to HSMageDSSGit"
        ),
        "frontend_files_in_git_repo": [
            "view/frontend/layout/catalog_category_view.xml",
            "view/frontend/layout/catalog_category_view_piehs_landing.xml",
            "view/frontend/layout/catalog_category_view_piehs_collection.xml",
            "view/frontend/layout/catalog_category_view_piehs_hub.xml",
            "view/frontend/templates/category/landing.phtml",
            "view/frontend/templates/category/shopping.phtml",
            "view/frontend/requirejs-config.js",
            "view/frontend/web/js/category-filters.js",
            "view/frontend/web/js/submit-filter-mixin.js",
            "view/frontend/web/js/hub-filters.js",
            "view/frontend/web/js/managed-layer-filters.js",
            "view/frontend/web/css/source/_module.less",
            "etc/frontend/events.xml",
            "etc/frontend/di.xml",
            "Observer/AddLayoutHandles.php",
            "Controller/Router/Intersection.php",
            "Setup/Patch/Data/AddShoppingIntersectionsAttribute.php",
            "Plugin/Catalog/Layer/FilterListPlugin.php",
            "Plugin/Catalog/Layer/IntersectionCategoryFilterPlugin.php",
            "Plugin/Layer/StatePlugin.php",
            "Model/ManagedCategory.php",
        ],
        "commands": [
            f"bin/magento module:enable {MAGENTO_MODULE_CODE}",
            "bin/magento setup:upgrade",
            "bin/magento setup:di:compile",
            "bin/magento setup:static-content:deploy -f",
            "bin/magento cache:flush",
        ],
    }
