<?php

declare(strict_types=1);

namespace CpCreation\VitiCore\Tests\Commerce\Catalog;

use CpCreation\VitiCore\Commerce\Catalog\Api\Resource\CatalogProductStatus;
use CpCreation\VitiCore\Commerce\Catalog\Api\Resource\CatalogValidation;
use CpCreation\VitiCore\Commerce\Catalog\Api\Resource\CatalogValidationIssue;
use CpCreation\VitiCore\Commerce\Catalog\CatalogPublishabilityResolver;
use CpCreation\VitiCore\Product\Model\CommerceProductType;
use CpCreation\VitiCore\Product\Model\Product;
use CpCreation\VitiCore\Product\Model\ProductCategory;
use CpCreation\VitiCore\Product\Model\ProductPriceGroup;
use PHPUnit\Framework\TestCase;

final class CatalogPublishabilityResolverTest extends TestCase
{
    public function testAProductRestrictedToAPrivatePriceGroupIsNotPublishableButIsNotADraft(): void
    {
        $category = $this->exportedCategory();
        $product = new Product();
        $product->setCategory($category);
        $privateGroup = new ProductPriceGroup();
        $privateGroup->setName('VIP');
        $product->addGroup($privateGroup);

        $publishability = new CatalogPublishabilityResolver()->resolve(
            $product,
            $category,
            $this->productStatus(published: true, orderable: true),
            new CatalogValidation(true, []),
        );

        self::assertSame('not_publishable', $publishability->status);
        self::assertSame(
            [['code' => 'price_group_restricted', 'path' => null]],
            json_decode(json_encode($publishability->reasons, \JSON_THROW_ON_ERROR), true, flags: \JSON_THROW_ON_ERROR),
        );
    }

    public function testDisabledCategoryExportMakesTheProductNotPublishable(): void
    {
        $category = new ProductCategory();
        $product = $this->productInDefaultGroup($category);

        $publishability = new CatalogPublishabilityResolver()->resolve(
            $product,
            $category,
            $this->productStatus(published: true, orderable: true),
            new CatalogValidation(true, []),
        );

        self::assertSame('not_publishable', $publishability->status);
        self::assertSame('category_export_disabled', $publishability->reasons[0]->code);
        self::assertSame('compliance.catalog_export_enabled', $publishability->reasons[0]->path);
    }

    public function testValidationErrorsBecomeExplicitNonPublishabilityReasons(): void
    {
        $category = $this->exportedCategory();
        $product = $this->productInDefaultGroup($category);

        $publishability = new CatalogPublishabilityResolver()->resolve(
            $product,
            $category,
            $this->productStatus(published: true, orderable: false),
            new CatalogValidation(false, [
                new CatalogValidationIssue('age_restriction_missing', 'compliance.age_restriction'),
                new CatalogValidationIssue('optional_translation_missing', 'localizations.en', 'warning'),
            ]),
        );

        self::assertSame('not_publishable', $publishability->status);
        self::assertSame(['age_restriction_missing'], array_map(
            static fn($reason): string => $reason->code,
            $publishability->reasons,
        ));
    }

    public function testInvalidValidationFailsClosedWithoutDetailedErrors(): void
    {
        $category = $this->exportedCategory();
        $product = $this->productInDefaultGroup($category);

        $publishability = new CatalogPublishabilityResolver()->resolve(
            $product,
            $category,
            $this->productStatus(published: true, orderable: true),
            new CatalogValidation(false, []),
        );

        self::assertSame('not_publishable', $publishability->status);
        self::assertSame('catalog_validation_failed', $publishability->reasons[0]->code);
        self::assertSame('validation.valid', $publishability->reasons[0]->path);
    }

    public function testTemporaryUnavailabilityDoesNotPreventPublication(): void
    {
        $category = $this->exportedCategory();
        $product = $this->productInDefaultGroup($category);

        $publishability = new CatalogPublishabilityResolver()->resolve(
            $product,
            $category,
            $this->productStatus(published: true, orderable: false, reasons: ['product_sold_out', 'no_orderable_variant']),
            new CatalogValidation(true, []),
        );

        self::assertSame('publishable', $publishability->status);
        self::assertSame([], $publishability->reasons);
    }

    public function testCompliantAlcoholRemainsGenericallyPublishableForChannelSpecificFiltering(): void
    {
        $category = $this->exportedCategory();
        $category->setCommerceProductType(CommerceProductType::ALCOHOL);
        $category->setMinimumPurchaseAge(18);
        $product = $this->productInDefaultGroup($category);

        $publishability = new CatalogPublishabilityResolver()->resolve(
            $product,
            $category,
            $this->productStatus(published: true, orderable: true),
            new CatalogValidation(true, []),
        );

        self::assertSame('publishable', $publishability->status);
        self::assertSame([], $publishability->reasons);
    }

    private function exportedCategory(): ProductCategory
    {
        $category = new ProductCategory();
        $category->setCommerceExportEnabled(true);

        return $category;
    }

    private function productInDefaultGroup(ProductCategory $category): Product
    {
        $group = new ProductPriceGroup();
        $group->setName('Default');
        $group->setByDefault(true);
        $product = new Product();
        $product->setCategory($category);
        $product->addGroup($group);

        return $product;
    }

    /** @param list<string> $reasons */
    private function productStatus(bool $published, bool $orderable, array $reasons = []): CatalogProductStatus
    {
        return new CatalogProductStatus(
            published: $published,
            orderable: $orderable,
            price_visible: true,
            temporarily_paused: false,
            reasons: $reasons,
        );
    }
}
