<?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\OnboardingPresetsProviderInterface;
use BookiFlex\Extensions\Onboarding\Factory\CurrencyFactory;
use BookiFlex\Core\Repository\CancellationPolicyRepository;
use BookiFlex\Core\Repository\PaymentTypeRepository;
use BookiFlex\Core\Service\CptCreator\CptCreatorInterface;
use BookiFlex\Core\Util\Iter;

defined( 'ABSPATH' ) || exit;

/**
 * Onboarding Initialization Service
 *
 * Handles the initialization of onboarding wizard by:
 * 1. Checking for existing cancellation policies and payment types via repositories
 * 2. Creating predefined presets if none exist (from plugin-specific provider)
 * 3. Fetching available currencies
 * 4. Returning aggregated initialization data with domain objects
 *
 * @package BookiFlex\Extensions\Onboarding\Service
 * @since   1.0.0
 */
class OnboardingInitService
{
    /**
     * OnboardingInitService constructor
     *
     * @param OnboardingPresetsProviderInterface $presetsProvider         Provides version-specific presets
     * @param CurrencyFactory                    $currencyFactory         Currency factory
     * @param CptCreatorInterface                $cptCreator              CPT creator for batch operations
     * @param CancellationPolicyRepository       $cancellationPolicyRepo  Cancellation policy repository
     * @param PaymentTypeRepository              $paymentTypeRepo         Payment type repository
     */
    public function __construct(
        private readonly OnboardingPresetsProviderInterface $presetsProvider,
        private readonly CurrencyFactory $currencyFactory,
        private readonly CptCreatorInterface $cptCreator,
        private readonly CancellationPolicyRepository $cancellationPolicyRepo,
        private readonly PaymentTypeRepository $paymentTypeRepo,
    ) {
    }

    /**
     * Initialize onboarding data
     *
     * Performs the following operations:
     * - Checks if cancellation policies exist via repository, creates presets if needed
     * - Checks if payment types exist via repository, creates presets if needed
     * - Fetches all available currencies
     * - Fetches available accommodation types from provider
     *
     * @return array{currencies: \BookiFlex\Extensions\Onboarding\ValueObject\Currency[], cancellationPolicies: \BookiFlex\Core\Model\CancellationPolicy\CancellationPolicy[], paymentTypes: \BookiFlex\Core\Model\PaymentType\PaymentType[], accommodationTypes: array<int, array{value: string, label: string}>}
     * @throws \RuntimeException If preset creation fails
     */
    public function initialize(): array
    {
        $cancellationPolicies = $this->ensureCancellationPoliciesExist();
        $paymentTypes = $this->ensurePaymentTypesExist();

        return [
            'currencies' => $this->currencyFactory->getAllCurrencies(),
            'cancellationPolicies' => $cancellationPolicies,
            'paymentTypes' => $paymentTypes,
            'bookingPresets' => $this->buildBookingPresets($cancellationPolicies, $paymentTypes),
            'accommodationTypes' => $this->presetsProvider->getAccommodationTypes(),
        ];
    }

    /**
     * Build booking presets with resolved DB IDs
     *
     * Maps cancellation policy keys and payment type keys to their actual
     * database IDs by matching the preset definitions order with created records.
     *
     * @param  \BookiFlex\Core\Model\CancellationPolicy\CancellationPolicy[] $cancellationPolicies Created/existing policies
     * @param  \BookiFlex\Core\Model\PaymentType\PaymentType[]               $paymentTypes         Created/existing payment types
     * @return array<int, array<string, mixed>> Booking presets with resolved IDs
     */
    private function buildBookingPresets(array $cancellationPolicies, array $paymentTypes): array
    {
        // Build key → ID maps from preset definitions + DB records (positional match)
        $cpKeyToId = $this->buildKeyToIdMap(
            $this->presetsProvider->getCancellationPolicies(),
            $cancellationPolicies,
            'cancellation_key'
        );

        $ptKeyToId = $this->buildKeyToIdMap(
            $this->presetsProvider->getPaymentTypes(),
            $paymentTypes,
            'payment_key'
        );

        // Hydrate booking presets with resolved IDs
        $presets = $this->presetsProvider->getBookingPresets();
        $result = [];

        foreach ($presets as $preset) {
            $cpId = $cpKeyToId[$preset['cancellationPolicyKey']] ?? null;
            $ptId = $ptKeyToId[$preset['paymentTypeKey']] ?? null;

            // Skip presets where we can't resolve both IDs
            if ($cpId === null || $ptId === null) {
                continue;
            }

            $result[] = [
                'id' => $preset['id'],
                'title' => $preset['title'],
                'shortDescription' => $preset['shortDescription'],
                'recommendation' => $preset['recommendation'],
                'popular' => $preset['popular'],
                'cancellationPolicyId' => $cpId,
                'paymentTypeId' => $ptId,
            ];
        }

        return $result;
    }

    /**
     * Build a key → ID map from preset definitions and created DB records
     *
     * Presets and DB records are in the same order (createBatch preserves order).
     *
     * @param  array<int, array<string, mixed>> $presetDefinitions Preset definitions with keys
     * @param  object[]                         $dbRecords         DB records with getId()
     * @param  string                           $keyField          Field name containing the key in presets
     * @return array<string, int> Map of key → DB ID
     */
    private function buildKeyToIdMap(array $presetDefinitions, array $dbRecords, string $keyField): array
    {
        $map = [];
        $dbRecords = array_values($dbRecords);

        foreach ($presetDefinitions as $index => $preset) {
            if (isset($dbRecords[$index]) && isset($preset[$keyField])) {
                $map[$preset[$keyField]] = $dbRecords[$index]->getId();
            }
        }

        return $map;
    }

    /**
     * Ensure cancellation policies exist in database
     *
     * Checks if any active cancellation policies exist via repository.
     * If none found, creates predefined presets from provider.
     *
     * @return \BookiFlex\Core\Model\CancellationPolicy\CancellationPolicy[] Array of CancellationPolicy domain objects
     * @throws \RuntimeException If creation fails
     */
    private function ensureCancellationPoliciesExist(): array
    {
        // Get existing policies from repository
        $existingPolicies = Iter::toArray($this->cancellationPolicyRepo->findAll());
        $existingPolicies = array_filter($existingPolicies, fn($p) => $p->isActive());

        // If policies already exist, return them
        if (!empty($existingPolicies)) {
            return $existingPolicies;
        }

        // No policies found - create from presets
        $presets = $this->presetsProvider->getCancellationPolicies();

        if (empty($presets)) {
            // No presets to create, return empty array
            return [];
        }

        // Create policies using CptCreator batch operation
        $createdIds = $this->cptCreator->createBatch([
            'cancellation_policies' => $presets,
        ]);

        // Fetch created policies from repository by IDs
        return Iter::toArray($this->cancellationPolicyRepo->findAll([
            'ids' => $createdIds['cancellation_policy_ids'],
            'onlyActive' => false, // We just created them, they might not be fully indexed yet
        ]));
    }

    /**
     * Ensure payment types exist in database
     *
     * Checks if any active payment types exist via repository.
     * If none found, creates predefined presets from provider.
     *
     * @return \BookiFlex\Core\Model\PaymentType\PaymentType[] Array of PaymentType domain objects
     * @throws \RuntimeException If creation fails
     */
    private function ensurePaymentTypesExist(): array
    {
        // Get existing payment types from repository
        $existingTypes = Iter::toArray($this->paymentTypeRepo->findAll());
        $existingTypes = array_filter($existingTypes, fn($t) => $t->isActive());

        // If payment types already exist, return them
        if (!empty($existingTypes)) {
            return $existingTypes;
        }

        // No payment types found - create from presets
        $presets = $this->presetsProvider->getPaymentTypes();

        if (empty($presets)) {
            // No presets to create, return empty array
            return [];
        }

        // Create payment types using CptCreator batch operation
        $createdIds = $this->cptCreator->createBatch([
            'payment_types' => $presets,
        ]);

        // Fetch created payment types from repository by IDs
        return Iter::toArray($this->paymentTypeRepo->findAll([
            'ids' => $createdIds['payment_type_ids'],
            'onlyActive' => false, // We just created them, they might not be fully indexed yet
        ]));
    }

}
