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

declare(strict_types=1);

namespace BookiFlex\Extensions\Onboarding\Service;

use BookiFlex\Extensions\Onboarding\Contract\OnboardingHandlerInterface;
use BookiFlex\Extensions\Onboarding\Contract\OnboardingPresetsProviderInterface;
use BookiFlex\Extensions\Onboarding\ValueObject\OnboardingConfiguration;
use BookiFlex\Extensions\Onboarding\ValueObject\OnboardingResult;
use BookiFlex\Extensions\Onboarding\ValueObject\AccommodationType;
use BookiFlex\Core\Service\CptCreator\CptCreatorInterface;
use BookiFlex\Core\Repository\CancellationPolicyRepository;
use BookiFlex\Core\Repository\PaymentTypeRepository;
use BookiFlex\Core\Admin\Settings\SettingsManager;
use BookiFlex\Core\Util\Iter;

defined( 'ABSPATH' ) || exit;

/**
 * Test Data Handler
 *
 * Handles Test Data onboarding mode by getting unified test data configuration
 * from the presets provider and delegating to QuickSetupHandler.handleUnified().
 *
 * This handler:
 * 1. Gets unified test data (accommodations + ratePlans) from version-specific presets provider
 * 2. Enriches rate plans with predefined payment types and cancellation policy IDs
 * 3. Saves currency setting
 * 4. Delegates to QuickSetupHandler.handleUnified() for processing
 *
 * @package BookiFlex\Extensions\Onboarding\Service
 * @since   1.0.0
 */
class TestDataHandler implements OnboardingHandlerInterface
{
    /**
     * TestDataHandler constructor
     *
     * @param OnboardingPresetsProviderInterface $presetsProvider         Presets provider
     * @param QuickSetupHandler                  $quickSetupHandler       Quick setup handler for delegation
     * @param CancellationPolicyRepository       $cancellationPolicyRepo  Cancellation policy repository
     * @param PaymentTypeRepository              $paymentTypeRepo         Payment type repository
     * @param SettingsManager                    $settings                Settings manager
     */
    public function __construct(
        private readonly OnboardingPresetsProviderInterface $presetsProvider,
        private readonly QuickSetupHandler $quickSetupHandler,
        private readonly CancellationPolicyRepository $cancellationPolicyRepo,
        private readonly PaymentTypeRepository $paymentTypeRepo,
        private readonly SettingsManager $settings,
    ) {
    }

    /**
     * Handle Test Data onboarding
     *
     * Gets unified test data configuration from presets provider,
     * enriches it with predefined entities, and delegates to QuickSetupHandler.handleUnified().
     *
     * @param  OnboardingConfiguration $config       Original onboarding configuration
     * @param  CptCreatorInterface     $cptCreator   CPT creator service
     * @param  OnboardingStateManager  $stateManager State manager
     * @return OnboardingResult Execution result with next steps
     * @throws \RuntimeException If test data creation fails
     */
    public function handle(
        OnboardingConfiguration $config,
        CptCreatorInterface $cptCreator,
        OnboardingStateManager $stateManager
    ): OnboardingResult {
        try {
            // 1. Get unified test data configuration (accommodations + ratePlans)
            $testData = $this->presetsProvider->getTestDataConfiguration();

            // 2. Save currency setting
            $this->saveCurrency($testData['currency']);

            // 3. Get predefined payment types and cancellation policy from database
            $predefinedData = $this->getPredefinedEntities();

            // 4. Build accommodations from test data
            $accommodationTypes = $this->buildAccommodationTypesFromTestData(
                $testData['accommodationTypes']
            );

            // 5. Enrich rate plans with predefined payment/cancellation data
            $ratePlans = $this->enrichRatePlansWithPredefinedData(
                $testData['ratePlans'],
                $predefinedData
            );

            // 6. Build unified data structure
            $unifiedData = [
                'accommodationTypes' => $accommodationTypes,
                'ratePlans' => $ratePlans,
            ];

            // 7. Delegate to unified handler
            return $this->quickSetupHandler->handleUnified(
                $unifiedData,
                $cptCreator,
                $stateManager,
                $config->mode
            );

        } catch (\Exception $e) {
            throw new \RuntimeException(
                sprintf(
                    'Test Data setup failed: %s',
                    $e->getMessage()
                ),
                0,
                $e
            );
        }
    }

    /**
     * Get predefined payment types and cancellation policy IDs from database
     *
     * These should have been created by OnboardingInitService during initialization.
     * Also builds key-to-index mappings from preset configurations for reference resolution.
     *
     * @return array{paymentTypeIds: int[], paymentTypeKeyMapping: array, cancellationPolicyIds: int[], cancellationPolicyKeyMapping: array}
     * @throws \RuntimeException If predefined entities not found
     */
    private function getPredefinedEntities(): array
    {
        // Get preset configurations to build key mappings
        $cancellationPolicyPresets = $this->presetsProvider->getCancellationPolicies();
        $paymentTypePresets = $this->presetsProvider->getPaymentTypes();

        // Build key-to-index mappings
        $cancellationKeyMapping = $this->buildKeyMapping($cancellationPolicyPresets, 'cancellation_key');
        $paymentKeyMapping = $this->buildKeyMapping($paymentTypePresets, 'payment_key');

        // Get existing cancellation policies
        $policies = Iter::toArray($this->cancellationPolicyRepo->findAll());
        $policies = array_filter($policies, fn($p) => $p->isActive());

        if (empty($policies)) {
            throw new \RuntimeException(
                'No cancellation policies found. Please run onboarding initialization first.'
            );
        }

        // Get existing payment types
        $paymentTypes = Iter::toArray($this->paymentTypeRepo->findAll());
        $paymentTypes = array_filter($paymentTypes, fn($t) => $t->isActive());

        if (empty($paymentTypes)) {
            throw new \RuntimeException(
                'No payment types found. Please run onboarding initialization first.'
            );
        }

        // Extract IDs (maintain order from database query = preset order)
        $paymentTypeIds = array_map(fn($pt) => $pt->getId(), $paymentTypes);
        $cancellationPolicyIds = array_map(fn($cp) => $cp->getId(), $policies);

        return [
            'paymentTypeIds' => $paymentTypeIds,
            'paymentTypeKeyMapping' => $paymentKeyMapping,
            'cancellationPolicyIds' => $cancellationPolicyIds,
            'cancellationPolicyKeyMapping' => $cancellationKeyMapping,
        ];
    }

    /**
     * Save currency setting
     *
     * @param  string $currency ISO 4217 currency code
     * @return void
     */
    private function saveCurrency(string $currency): void
    {
        $this->settings->setValueDirect(SettingsManager::DEFAULT_CURRENCY, $currency);
    }

    /**
     * Build AccommodationType value objects from test data arrays
     *
     * @param  array<int, array<string, mixed>> $testDataAccommodations Test data accommodations
     * @return AccommodationType[] Array of AccommodationType objects
     */
    private function buildAccommodationTypesFromTestData(array $testDataAccommodations): array
    {
        return array_map(
            fn(array $accommodation): AccommodationType => new AccommodationType(
                name: $accommodation['name'],
                mainPlaces: $accommodation['mainPlaces'],
                unitType: $accommodation['unitType'],
                availableUnits: $accommodation['availableUnits'],
                description: $accommodation['description'] ?? null,
                amenities: $accommodation['amenities'] ?? null,
                address: $accommodation['address'] ?? null,
            ),
            $testDataAccommodations
        );
    }

    /**
     * Enrich rate plans with predefined payment types and cancellation policy IDs
     *
     * Resolves payment type keys and cancellation policy keys from preset configuration
     * to actual database IDs. Falls back to first item if keys not specified.
     *
     * @param  array $ratePlans      Rate plans from test data configuration
     * @param  array $predefinedData Predefined entity IDs and key mappings
     * @return array Enriched rate plans with resolved IDs
     * @throws \RuntimeException If referenced key not found
     */
    private function enrichRatePlansWithPredefinedData(array $ratePlans, array $predefinedData): array
    {
        return array_map(
            function (array $ratePlan) use ($predefinedData): array {
                // Resolve payment type keys to IDs
                if (isset($ratePlan['paymentTypeKeys'])) {
                    $ratePlan['paymentTypeIds'] = $this->resolvePaymentTypeKeys(
                        $ratePlan['paymentTypeKeys'],
                        $predefinedData['paymentTypeKeyMapping'],
                        $predefinedData['paymentTypeIds']
                    );
                } else {
                    // Default: use FIRST payment type (not all)
                    $ratePlan['paymentTypeIds'] = [$predefinedData['paymentTypeIds'][0]];
                }

                // Resolve cancellation policy key to ID
                if (isset($ratePlan['cancellationPolicyKey'])) {
                    $ratePlan['cancellationPolicyId'] = $this->resolveCancellationPolicyKey(
                        $ratePlan['cancellationPolicyKey'],
                        $predefinedData['cancellationPolicyKeyMapping'],
                        $predefinedData['cancellationPolicyIds']
                    );
                } else {
                    // Default: use FIRST cancellation policy
                    $ratePlan['cancellationPolicyId'] = $predefinedData['cancellationPolicyIds'][0];
                }

                return $ratePlan;
            },
            $ratePlans
        );
    }

    /**
     * Build key-to-index mapping from preset arrays
     *
     * @param  array  $presets  Array of preset configurations
     * @param  string $keyField Name of the key field (e.g., 'payment_key', 'cancellation_key')
     * @return array Map of key => array_index
     */
    private function buildKeyMapping(array $presets, string $keyField): array
    {
        $mapping = [];

        foreach ($presets as $index => $preset) {
            if (isset($preset[$keyField])) {
                $mapping[$preset[$keyField]] = $index;
            }
        }

        return $mapping;
    }

    /**
     * Resolve payment type keys to database IDs
     *
     * @param  array $keys         Payment type keys from preset config
     * @param  array $keyMapping   Map of key => preset_array_index
     * @param  array $createdIds   Array of created IDs (indexed by preset order)
     * @return int[] Array of payment type IDs
     * @throws \RuntimeException If key not found
     */
    private function resolvePaymentTypeKeys(array $keys, array $keyMapping, array $createdIds): array
    {
        $resolvedIds = [];

        foreach ($keys as $key) {
            if (!isset($keyMapping[$key])) {
                throw new \RuntimeException(
                    sprintf(
                        'Payment type key "%1$s" not found in presets. Available keys: %2$s',
                        $key,
                        implode(', ', array_keys($keyMapping))
                    )
                );
            }

            $presetIndex = $keyMapping[$key];

            if (!isset($createdIds[$presetIndex])) {
                throw new \RuntimeException(
                    sprintf(
                        'Payment type with key "%s" was not created successfully',
                        $key
                    )
                );
            }

            $resolvedIds[] = $createdIds[$presetIndex];
        }

        return $resolvedIds;
    }

    /**
     * Resolve cancellation policy key to database ID
     *
     * @param  string $key         Cancellation policy key from preset config
     * @param  array  $keyMapping  Map of key => preset_array_index
     * @param  array  $createdIds  Array of created IDs (indexed by preset order)
     * @return int Cancellation policy ID
     * @throws \RuntimeException If key not found
     */
    private function resolveCancellationPolicyKey(string $key, array $keyMapping, array $createdIds): int
    {
        if (!isset($keyMapping[$key])) {
            throw new \RuntimeException(
                sprintf(
                    'Cancellation policy key "%1$s" not found in presets. Available keys: %2$s',
                    $key,
                    implode(', ', array_keys($keyMapping))
                )
            );
        }

        $presetIndex = $keyMapping[$key];

        if (!isset($createdIds[$presetIndex])) {
            throw new \RuntimeException(
                sprintf(
                    'Cancellation policy with key "%s" was not created successfully',
                    $key
                )
            );
        }

        return $createdIds[$presetIndex];
    }
}
