<?php

/*
 * This file is part of the VITICORE2 package.
 *
 * (c) CP Creation <web@cpcreation.fr>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace CpCreation\VitiCore\Tests\Cart\LegacyOrderImport;

use CpCreation\VitiCore\Cart\CartPricing;
use CpCreation\VitiCore\Cart\CartPricingLine;
use CpCreation\VitiCore\Cart\CartPromo;
use CpCreation\VitiCore\Cart\CartPromos;
use CpCreation\VitiCore\Cart\CartShipping;
use CpCreation\VitiCore\Cart\LegacyOrderImport\LegacyPricingSnapshotBuilder;
use PHPUnit\Framework\TestCase;

final class LegacyPricingSnapshotBuilderTest extends TestCase
{
    private LegacyPricingSnapshotBuilder $builder;

    protected function setUp(): void
    {
        $this->builder = new LegacyPricingSnapshotBuilder();
    }

    public function testBuildsAnExactTTCSnapshotWithAllLegacyDiscounts(): void
    {
        $snapshot = $this->builder->build(
            [
                'tax' => '0.20',
                'view_ht' => false,
                'shipping' => 1200,
                'shipping_name' => 'Transporteur historique',
                'promo' => 'ETE10',
                'promo_value_amount' => 500,
                'promo_value_percent' => '0.10',
                'promo_with_shipping_fees' => true,
                'degressive_value' => 300,
            ],
            [
                $this->line('item-b', 1500, 1, 'variant-b'),
                $this->line('item-a', 2500, 2, 'variant-a'),
            ],
        );

        self::assertSame(6250, $snapshot['legacyTotalTTC']);
        self::assertSame(CartPricing::class, $snapshot['cartPricing']['#type']);
        self::assertSame(6200, $snapshot['cartPricing']['subtotalTTC']);
        self::assertSame(300, $snapshot['cartPricing']['degressiveTTC']);
        self::assertSame(1150, $snapshot['cartPricing']['discountTTC']);
        self::assertSame(6250, $snapshot['cartPricing']['totalTTC']);
        self::assertSame(
            $snapshot['cartPricing']['totalTTC'] - $snapshot['cartPricing']['totalHT'],
            $snapshot['cartPricing']['totalTax'],
        );

        self::assertSame(CartShipping::class, $snapshot['cartShipping']['#type']);
        self::assertSame(1200, $snapshot['cartShipping']['amountTTC']);
        self::assertSame('Transporteur historique', $snapshot['cartShipping']['name']);

        self::assertSame(CartPromos::class, $snapshot['cartPromos']['#type']);
        self::assertCount(1, $snapshot['cartPromos']['items']);
        $promo = $snapshot['cartPromos']['items'][0];
        self::assertSame(CartPromo::class, $promo['#type']);
        self::assertSame('ETE10', $promo['code']);
        self::assertSame(500, $promo['amountTTC']);
        self::assertSame(0.1, $promo['percent']);
        self::assertTrue($promo['withShippingFees']);
        self::assertSame(1150, $promo['discountItemsTTC']);
        self::assertSame(0, $promo['discountShippingTTC']);

        $degressiveByItem = $this->degressiveByItem($snapshot['cartPricing']['lines']);
        self::assertSame(['item-a' => 231, 'item-b' => 69], $degressiveByItem);

        foreach ($snapshot['cartPricing']['lines'] as $line) {
            self::assertSame(CartPricingLine::class, $line['#type']);
            self::assertSame($line['netTTC'] - $line['netHT'], $line['tax']);
        }
    }

    public function testReproducesMasterHTRoundingAndAssignsItsResidualDeterministically(): void
    {
        $cart = [
            'tax' => '0.20',
            'view_ht' => '1',
            'shipping' => 100,
            'degressive_value' => 5,
        ];
        $lines = [
            $this->line('item-b', 101, 2, 'variant-b'),
            $this->line('item-a', 333, 3, 'variant-a'),
        ];

        $snapshot = $this->builder->build($cart, $lines);
        $reorderedSnapshot = $this->builder->build($cart, array_reverse($lines));

        // Master returned an int after round(1296 * 1.20, 2): 1555.2 was
        // therefore coerced to 1555 cents. Taxing each line separately only
        // yields 1554 cents, so the stable final line receives one cent.
        self::assertSame(1555, $snapshot['legacyTotalTTC']);
        self::assertSame(1555, $snapshot['cartPricing']['totalTTC']);
        self::assertSame(1296, $snapshot['cartPricing']['totalHT']);
        self::assertSame(259, $snapshot['cartPricing']['totalTax']);
        self::assertSame(120, $snapshot['cartShipping']['amountTTC']);
        self::assertSame(0, $snapshot['cartPricing']['discountTTC']);
        self::assertSame(6, $snapshot['cartPricing']['degressiveTTC']);
        self::assertSame(1435, $snapshot['cartPricing']['subtotalTTC']);
        self::assertSame(243, $this->pricingLineValuesByItem($snapshot['cartPricing']['lines'])['item-b']['totalTTC']);
        self::assertSame(
            $this->pricingLineValuesByItem($snapshot['cartPricing']['lines']),
            $this->pricingLineValuesByItem($reorderedSnapshot['cartPricing']['lines']),
        );
    }

    public function testBuildsEmptyPromosAndPreservesMetadataWithoutDiscount(): void
    {
        $snapshot = $this->builder->build(
            [
                'tax' => 0.2,
                'view_ht' => false,
                'shipping' => 0,
                'shipping_name' => null,
            ],
            [[
                'item_id' => 'item-1',
                'product_id' => 'product-1',
                'product_name' => 'Cuvée historique',
                'variant_id' => 'variant-1',
                'variant_full_name' => 'Cuvée historique - 75 cl',
                'type_id' => 'type-1',
                'type_name' => 'Bouteille',
                'unit_label' => '75 cl',
                'image_path' => '/legacy/cuvee.jpg',
                'quantity' => '2',
                'price' => '2400',
            ]],
        );

        self::assertSame([], $snapshot['cartPromos']['items']);
        self::assertSame(4800, $snapshot['legacyTotalTTC']);
        self::assertSame(4000, $snapshot['cartPricing']['totalHT']);
        $line = $snapshot['cartPricing']['lines'][0];
        self::assertSame('product-1', $line['productId']);
        self::assertSame('Cuvée historique', $line['productName']);
        self::assertSame('variant-1', $line['variantId']);
        self::assertSame('75 cl', $line['unitLabel']);
        self::assertSame('/legacy/cuvee.jpg', $line['imagePath']);
        self::assertSame(2400, $line['unitPriceTTC']);
        self::assertSame(2000, $line['unitPriceHT']);
    }

    /**
     * @return array{
     *     item_id: string,
     *     product_id: string,
     *     product_name: string,
     *     variant_id: string,
     *     variant_full_name: string,
     *     type_id: string,
     *     type_name: string,
     *     unit_label: string,
     *     image_path: null,
     *     quantity: int,
     *     price: int
     * }
     */
    private function line(string $itemId, int $price, int $quantity, string $variantId): array
    {
        return [
            'item_id' => $itemId,
            'product_id' => 'product-'.$itemId,
            'product_name' => 'Product '.$itemId,
            'variant_id' => $variantId,
            'variant_full_name' => 'Variant '.$itemId,
            'type_id' => 'type-'.$itemId,
            'type_name' => 'Type '.$itemId,
            'unit_label' => '75 cl',
            'image_path' => null,
            'quantity' => $quantity,
            'price' => $price,
        ];
    }

    /**
     * @param list<array<string, mixed>> $lines
     *
     * @return array<string, int>
     */
    private function degressiveByItem(array $lines): array
    {
        $values = [];

        foreach ($lines as $line) {
            $values[$line['itemId']] = $line['degressiveTTC'];
        }

        ksort($values);

        return $values;
    }

    /**
     * @param list<array<string, mixed>> $lines
     *
     * @return array<string, array{totalTTC: int, degressiveTTC: int, netTTC: int}>
     */
    private function pricingLineValuesByItem(array $lines): array
    {
        $values = [];

        foreach ($lines as $line) {
            $values[$line['itemId']] = [
                'totalTTC' => $line['totalTTC'],
                'degressiveTTC' => $line['degressiveTTC'],
                'netTTC' => $line['netTTC'],
            ];
        }

        ksort($values);

        return $values;
    }
}
