<?php
/**
 * BookiFlex Core
 *
 * @package    BookiFlex
 * @subpackage Core
 * @author     BookiFlex
 * @copyright  Copyright (c) 2025 BookiFlex
 * @license    GPL-2.0-or-later
 *
 * @link       https://bookiflex.com
 */


declare(strict_types=1);
namespace BookiFlex\Core\Service\Aggregator;

use BookiFlex\Core\Service\Aggregator\ValueObject\AggregatedSummary;
use Brick\Money\Money;
use Vaened\PriceEngine\Modifier;
use Vaened\PriceEngine\Summary;

defined( 'ABSPATH' ) || exit;

/**
 * Aggregator for multiple Cashier/Summary objects
 * Allows combining, transforming and analyzing financial data
 * from various sources (reservation, accommodation days, extra services)
 */
class SummaryAggregator
{
    private string $currency;
    private array $summaries = [];
    private array $metadata = [];

    public function __construct(string $currency)
    {
        $this->currency = $currency;
    }

    /**
     * Add Summary with context
     *
     * @param Summary $summary
     * @param string $type Source type (reservation, accommodation, extra, etc.)
     * @param string $scope Scope (item, daily, total)
     * @param array $context Additional context (date, itemId, etc.)
     * @return SummaryAggregator
     */
    public function addSummary(Summary $summary, string $type, string $scope = 'item', array $context = []): self
    {
        $this->summaries[] = [
            'summary' => $summary,
            'type' => $type,
            'scope' => $scope,
            'context' => $context
        ];

        return $this;
    }

    /**
     * Add multiple Summaries of one type
     */
    public function addMultiple(array $summaries, string $type, string $scope = 'item'): self
    {
        foreach ($summaries as $key => $summary) {
            if ($summary instanceof Summary) {
                $context = is_string($key) ? ['id' => $key] : ['index' => $key];
                $this->addSummary($summary, $type, $scope, $context);
            }
        }

        return $this;
    }

    /**
     * Set metadata for aggregation
     */
    public function setMetadata(array $metadata): self
    {
        $this->metadata = $metadata;
        return $this;
    }

    /**
     * Get complete aggregated summary
     */
    public function aggregate(): AggregatedSummary
    {
        $result = new AggregatedSummary($this->currency);

        // Base amounts
        $subtotal = Money::zero($this->currency);
        $total = Money::zero($this->currency);

        // Aggregated modifiers
        $aggregatedModifiers = [
            'taxes' => [],
            'charges' => [],
            'discounts' => []
        ];

        // Breakdown by sources
        $breakdown = [];

        foreach ($this->summaries as $entry) {
            $summary = $entry['summary'];
            $type = $entry['type'];
            $scope = $entry['scope'];
            $context = $entry['context'];

            // Sum base values
            $subtotal = $subtotal->plus($summary->subtotal()->net());
            $total = $total->plus($summary->total());

            // Create breakdown key
            $breakdownKey = $this->generateBreakdownKey($type, $scope, $context);

            if (!isset($breakdown[$breakdownKey])) {
                $breakdown[$breakdownKey] = [
                    'type' => $type,
                    'scope' => $scope,
                    'context' => $context,
                    'subtotal' => Money::zero($this->currency),
                    'total' => Money::zero($this->currency),
                    'modifiers' => [
                        'taxes' => Money::zero($this->currency),
                        'charges' => Money::zero($this->currency),
                        'discounts' => Money::zero($this->currency)
                    ],
                    'items' => []
                ];
            }

            // Update breakdown
            $breakdown[$breakdownKey]['subtotal'] = $breakdown[$breakdownKey]['subtotal']->plus($summary->subtotal()->net());
            $breakdown[$breakdownKey]['total'] = $breakdown[$breakdownKey]['total']->plus($summary->total());

            // Process modifiers
            $this->processModifiers($summary->taxes(), 'taxes', $aggregatedModifiers, $breakdown[$breakdownKey], $type, $scope, $context);
            $this->processModifiers($summary->charges(), 'charges', $aggregatedModifiers, $breakdown[$breakdownKey], $type, $scope, $context);
            $this->processModifiers($summary->discounts(), 'discounts', $aggregatedModifiers, $breakdown[$breakdownKey], $type, $scope, $context);

            // Save item details
            $breakdown[$breakdownKey]['items'][] = [
                'context' => $context,
                'subtotal' => $summary->subtotal()->net()->getMinorAmount()->toInt(),
                'total' => $summary->total()->getMinorAmount()->toInt()
            ];
        }

        // Fill result
        $result->setSubtotal($subtotal);
        $result->setTotal($total);
        $result->setModifiers($this->finalizeModifiers($aggregatedModifiers));
        $result->setBreakdown($breakdown);
        $result->setMetadata($this->metadata);

        return $result;
    }

    /**
     * Process modifiers
     */
    private function processModifiers(
        \Vaened\PriceEngine\Modifiers $modifiers,
        string $modifierType,
        array &$aggregated,
        array &$breakdownEntry,
        string $sourceType,
        string $scope,
        array $context
    ): void {
        $totalAmount = $modifiers->total();
        $breakdownEntry['modifiers'][$modifierType] = $breakdownEntry['modifiers'][$modifierType]->plus($totalAmount);

        /** @var Modifier $modifier */
        foreach ($modifiers->items() as $modifier) {
            $key = $this->generateModifierKey($modifier);

            if (!isset($aggregated[$modifierType][$key])) {
                $aggregated[$modifierType][$key] = [
                    'code' => $modifier->code(),
                    'type' => $modifierType,
                    'adjustmentType' => $modifier->type()->value,
                    'adjustmentMode' => $modifier->mode()->value,
                    'value' => $modifier->value(),
                    'amount' => Money::zero($this->currency),
                    'sources' => [],
                    'occurrences' => 0
                ];
            }

            // Sum amount
            $aggregated[$modifierType][$key]['amount'] =
                $aggregated[$modifierType][$key]['amount']->plus($modifier->amount());

            // Add source information
            $sourceKey = $this->generateSourceKey($sourceType, $scope, $context);
            if (!isset($aggregated[$modifierType][$key]['sources'][$sourceKey])) {
                $aggregated[$modifierType][$key]['sources'][$sourceKey] = [
                    'type' => $sourceType,
                    'scope' => $scope,
                    'context' => $context,
                    'amount' => Money::zero($this->currency),
                    'count' => 0
                ];
            }

            $aggregated[$modifierType][$key]['sources'][$sourceKey]['amount'] =
                $aggregated[$modifierType][$key]['sources'][$sourceKey]['amount']->plus($modifier->amount());
            $aggregated[$modifierType][$key]['sources'][$sourceKey]['count']++;
            $aggregated[$modifierType][$key]['occurrences']++;
        }
    }

    /**
     * Finalize modifiers
     */
    private function finalizeModifiers(array $aggregated): array
    {
        $result = [];

        foreach ($aggregated as $type => $modifiers) {
            foreach ($modifiers as $modifier) {
                $result[] = array_merge($modifier, [
                    'amount' => $modifier['amount']->getMinorAmount()->toInt(),
                    'sources' => array_map(function($source) {
                        return array_merge($source, [
                            'amount' => $source['amount']->getMinorAmount()->toInt()
                        ]);
                    }, $modifier['sources'])
                ]);
            }
        }

        // Sorting
        usort($result, function($a, $b) {
            $typeOrder = ['taxes' => 1, 'charges' => 2, 'discounts' => 3];
            $typeCompare = ($typeOrder[$a['type']] ?? 99) - ($typeOrder[$b['type']] ?? 99);

            if ($typeCompare !== 0) {
                return $typeCompare;
            }

            return strcmp($a['code'], $b['code']);
        });

        return $result;
    }

    /**
     * Generate keys
     */
    private function generateModifierKey(Modifier $modifier): string
    {
        return implode('_', [
            $modifier->code(),
            $modifier->type()->value,
            $modifier->mode()->value
        ]);
    }

    private function generateSourceKey(string $type, string $scope, array $context): string
    {
        $parts = [$type, $scope];

        if (isset($context['date'])) {
            $parts[] = $context['date'];
        }
        if (isset($context['id'])) {
            $parts[] = $context['id'];
        }

        return implode(':', $parts);
    }

    private function generateBreakdownKey(string $type, string $scope, array $context): string
    {
        if ($scope === 'daily' && isset($context['date'])) {
            return $type . '_' . $context['date'];
        }

        return $type . '_' . $scope;
    }
}
