<?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\Core\Util\Str;
use BookiFlex\Extensions\Onboarding\Contract\OnboardingHandlerInterface;
use BookiFlex\Extensions\Onboarding\Contract\OnboardingPresetsProviderInterface;
use BookiFlex\Extensions\Onboarding\ValueObject\OnboardingConfiguration;
use BookiFlex\Extensions\Onboarding\ValueObject\OnboardingMode;
use BookiFlex\Extensions\Onboarding\ValueObject\OnboardingResult;
use BookiFlex\Extensions\Onboarding\ValueObject\NextStep;
use BookiFlex\Extensions\Onboarding\ValueObject\AccommodationType;
use BookiFlex\Extensions\Onboarding\ValueObject\WeeklyPricing;
use BookiFlex\Extensions\Onboarding\Factory\DateUnitPriceFactory;
use BookiFlex\Core\Service\CptCreator\CptCreatorInterface;
use BookiFlex\Core\Service\DateUnit\DateUnitManager;
use BookiFlex\Core\Service\DateUnit\DTO\UpdateDateUnits as UpdateDateUnitsDTO;
use BookiFlex\Core\Admin\Settings\SettingsManager;
use BookiFlex\Core\Model\RatePlan\BoardType;
use BookiFlex\Core\Model\AccommodationType\BookingUnitTypeInterface;

defined( 'ABSPATH' ) || exit;

/**
 * Quick Setup Handler
 *
 * Handles Quick Setup onboarding mode by creating:
 * - AccommodationType entities with pricing
 * - RatePlan entity
 * - DateUnit pricing for 1 year forward
 * - Currency setting
 *
 * Uses CptCreator::createBatch() for atomic CPT creation.
 *
 * @package BookiFlex\Extensions\Onboarding\Service
 * @since   1.0.0
 */
class QuickSetupHandler implements OnboardingHandlerInterface
{
    /**
     * QuickSetupHandler constructor
     *
     * @param OnboardingPresetsProviderInterface $presetsProvider Presets provider
     * @param DateUnitManager                    $dateUnitManager DateUnit update service
     * @param DateUnitPriceFactory               $priceFactory    Price factory
     * @param SettingsManager                    $settings        Settings manager
     */
    public function __construct(
        private readonly OnboardingPresetsProviderInterface $presetsProvider,
        private readonly DateUnitManager $dateUnitManager,
        private readonly DateUnitPriceFactory $priceFactory,
        private readonly SettingsManager $settings,
    ) {
    }

    /**
     * Handle Quick Setup onboarding
     *
     * Transforms API format to unified format and delegates to handleUnified().
     *
     * @param  OnboardingConfiguration $config       Onboarding configuration
     * @param  CptCreatorInterface     $cptCreator   CPT creator service
     * @param  OnboardingStateManager  $stateManager State manager
     * @return OnboardingResult Execution result with next steps
     * @throws \RuntimeException If entity creation fails
     */
    public function handle(
        OnboardingConfiguration $config,
        CptCreatorInterface $cptCreator,
        OnboardingStateManager $stateManager
    ): OnboardingResult {
        try {
            // 1. Save currency setting
            $this->saveCurrency($config->currency);

            // 2. Save company information
            $this->saveCompanyInfo($config);

            // 3. Transform API format to unified format
            $unifiedData = $this->transformToUnifiedFormat($config);

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

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

    /**
     * Transform API format to unified format
     *
     * Extracts weeklyPricing from accommodations and creates a unified rate plan structure.
     *
     * @param  OnboardingConfiguration $config API configuration
     * @return array{accommodationTypes: AccommodationType[], ratePlans: array} Unified data structure
     */
    private function transformToUnifiedFormat(OnboardingConfiguration $config): array
    {
        $accommodations = [];
        $weeklyPricingMap = [];

        // Extract pricing from API accommodations
        foreach ($config->accommodationTypes as $index => $acc) {
            // Create accommodation WITHOUT pricing
            $accommodations[] = new AccommodationType(
                name: $acc->name,
                mainPlaces: $acc->mainPlaces,
                unitType: $acc->unitType,
                availableUnits: $acc->availableUnits,
                description: $acc->description,
                amenities: $acc->amenities,
	            address:  $acc->address,
            );

            // Save pricing separately for rate plan
            $weeklyPricingMap[$index] = $acc->weeklyPricing->toArray();
        }

        // Create single rate plan for all accommodations
        $ratePlans = [
            [
                'title' => __('Base Rate Plan', 'bookiflex'),
                'shortCode' => 'BASE',
                'boardType' => BoardType::ROOM_ONLY,
                'accommodationIndices' => range(0, count($accommodations) - 1),
                'weeklyPricing' => $weeklyPricingMap,
                'paymentTypeIds' => $config->paymentTypeIds,
                'cancellationPolicyId' => $config->cancellationPolicyId,
            ],
        ];

        return [
            'accommodationTypes' => $accommodations,
            'ratePlans' => $ratePlans,
        ];
    }

    /**
     * Handle unified data format (shared logic for Quick Setup and Test Data)
     *
     * @param  array                   $unifiedData  Unified data structure
     * @param  CptCreatorInterface     $cptCreator   CPT creator service
     * @param  OnboardingStateManager  $stateManager State manager
     * @param  OnboardingMode          $mode         Onboarding mode
     * @return OnboardingResult Execution result with next steps
     * @throws \RuntimeException If entity creation fails
     */
    public function handleUnified(
        array $unifiedData,
        CptCreatorInterface $cptCreator,
        OnboardingStateManager $stateManager,
        OnboardingMode $mode
    ): OnboardingResult {
        // 1. Prepare batch data from unified format
        $batchData = $this->prepareBatchDataFromUnified($unifiedData);

        // 2. Create all CPT entities
        $createdIds = $cptCreator->createBatch($batchData);

        // 3. Setup pricing for all rate plans
        $this->setupPricingFromUnified($unifiedData, $createdIds);

        // 4. Build next steps
        $nextSteps = $this->buildNextSteps(
            $this->presetsProvider->getNextStepsForMode($mode)
        );

        return new OnboardingResult(
            success: true,
            mode: $mode,
            nextSteps: $nextSteps
        );
    }

    /**
     * Save currency setting
     *
     * @param  string|null $currency ISO 4217 currency code
     * @return void
     * @throws \RuntimeException If currency is null
     */
    private function saveCurrency(?string $currency): void
    {
        if ($currency === null) {
            throw new \RuntimeException('Currency is required');
        }

        $this->settings->setValueDirect(SettingsManager::DEFAULT_CURRENCY, $currency);
    }

    /**
     * Save company information settings
     *
     * @param  OnboardingConfiguration $config Onboarding configuration
     * @return void
     */
    private function saveCompanyInfo(OnboardingConfiguration $config): void
    {
        if ($config->companyName !== null) {
            $this->settings->setValueDirect(SettingsManager::NAME, $config->companyName);
        }

        if ($config->companyAddress !== null) {
            $this->settings->setValueDirect(SettingsManager::ADDRESS, $config->companyAddress);
        }

        if ($config->companyPhone !== null) {
            $this->settings->setValueDirect(SettingsManager::PHONE, $config->companyPhone);
        }

        if ($config->companyEmail !== null) {
            $this->settings->setValueDirect(SettingsManager::EMAIL, $config->companyEmail);
        }
    }

    /**
     * Prepare batch data from unified format
     *
     * Converts unified format (accommodations + ratePlans) to CptCreator batch format.
     *
     * @param  array $unifiedData Unified data structure
     * @return array<string, array> Batch data structure for CptCreator::createBatch()
     */
    private function prepareBatchDataFromUnified(array $unifiedData): array
    {
        // Prepare accommodation types data
        $accommodationTypes = array_map(
            fn(AccommodationType $acc) => $this->prepareAccommodationData($acc),
            $unifiedData['accommodationTypes']
        );

        // Prepare rate plans data
        $ratePlans = array_map(
            fn(array $ratePlan) => $this->prepareRatePlanDataFromUnified($ratePlan, $accommodationTypes),
            $unifiedData['ratePlans']
        );

        return [
            'accommodation_types' => $accommodationTypes,
            'rate_plans' => $ratePlans,
        ];
    }

    /**
     * Prepare rate plan data from unified format
     *
     * @param  array $ratePlan         Rate plan configuration from unified format
     * @param  array $accommodationTypes All accommodation types data
     * @return array<string, mixed> Data array for batch rate plan
     */
    private function prepareRatePlanDataFromUnified(array $ratePlan, array $accommodationTypes): array
    {
        // Get description for board type
        $boardType = $ratePlan['boardType'];
        $descriptions = $this->presetsProvider->getDescriptionsByBoardType($boardType->value);
        $description = !empty($descriptions) ? $descriptions[array_rand($descriptions)] : null;

        // Build result array
        $result = [
            'title' => $ratePlan['title'],
            'excerpt' => $description,
            'status' => 'publish',
            'board_type' => $boardType->value,
            'short_code' => $ratePlan['shortCode'],
            'accommodation_types' => $ratePlan['accommodationIndices'], // Batch indices
        ];

        // Add payment types if present
        if (isset($ratePlan['paymentTypeIds'])) {
            $result['payment_types'] = ['existing' => $ratePlan['paymentTypeIds']];
        }

        // Add cancellation policy if present
        if (isset($ratePlan['cancellationPolicyId'])) {
            $result['cancellation_policy'] = ['existing' => $ratePlan['cancellationPolicyId']];
        }

        return $result;
    }

    /**
     * Setup DateUnit pricing from unified format
     *
     * Creates pricing for each rate plan and its accommodations.
     * Makes separate calls for each day of week to properly apply different prices per day.
     *
     * @param  array $unifiedData Unified data structure
     * @param  array $createdIds  Created entity IDs from CptCreator::createBatch()
     * @return void
     * @throws \RuntimeException|\BookiFlex\Core\Exception\InvalidArgumentException If pricing setup fails
     */
    private function setupPricingFromUnified(array $unifiedData, array $createdIds): void
    {
        $days = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'];
        $accommodationTypes = $unifiedData['accommodationTypes'];

        foreach ($unifiedData['ratePlans'] as $ratePlanIndex => $ratePlan) {
            $ratePlanId = $createdIds['rate_plan_ids'][$ratePlanIndex];
            $weeklyPricingMap = $ratePlan['weeklyPricing'];

            // For each accommodation in this rate plan
            foreach ($ratePlan['accommodationIndices'] as $accommodationIndex) {
                $accommodationId = $createdIds['accommodation_type_ids'][$accommodationIndex];
                $accommodation = $accommodationTypes[$accommodationIndex];

                // Get pricing for this accommodation
                $weeklyPricing = WeeklyPricing::fromArray($weeklyPricingMap[$accommodationIndex]);

                // For each day of week, make a separate call with that day's price
                foreach ($days as $day) {
                    $dto = new UpdateDateUnitsDTO(
                        ratePlanId: $ratePlanId,
                        accommodationTypeId: $accommodationId,
                        dates: $this->priceFactory->buildDateRangeForDay($day),
                        restrictions: [
                            'stopSale' => false,
                        ],
                        prices: $this->priceFactory->buildPricesForDay(
                            $weeklyPricing,
                            $accommodation->mainPlaces,
                            $day
                        )
                    );

                    $this->dateUnitManager->handle($dto);
                }
            }
        }
    }

    /**
     * Build NextStep value objects from preset data
     *
     * @param  array<int, array<string, string>> $presetSteps Preset next steps
     * @return NextStep[] Array of NextStep objects
     */
    private function buildNextSteps(array $presetSteps): array
    {
        $steps = [];

        foreach ($presetSteps as $step) {
            if (isset($step['title'], $step['description'], $step['icon'])) {
                $steps[] = new NextStep(
                    title: $step['title'],
                    description: $step['description'],
                    icon: $step['icon']
                );
            }
        }

        return $steps;
    }

    /**
     * Prepare accommodation data for CptCreator
     *
     * Mixes in description and amenities from presets provider if not provided by user.
     *
     * @param  AccommodationType $accommodation Accommodation value object
     * @return array<string, mixed> Data array for CptCreatorInterface::createAccommodationType()
     */
    private function prepareAccommodationData(AccommodationType $accommodation): array
    {
        // Get description: use user-provided or random from presets
        $description = $accommodation->description;
        if ($description === null) {
            $descriptions = $this->presetsProvider->getDescriptionsByUnitType($accommodation->unitType);
            $description = !empty($descriptions) ? $descriptions[array_rand($descriptions)] : null;
        }

        // Get amenities: use user-provided or full list from presets
        $amenities = $accommodation->amenities;
        if ($amenities === null) {
            $amenities = $this->presetsProvider->getAmenitiesByUnitType($accommodation->unitType);
        }

        // Convert amenities array to comma-separated string for key_amenities field
        $amenitiesString = !empty($amenities) ? implode(", ", $amenities) : null;

        return [
            'title' => $accommodation->name,
            'excerpt' => $description,
            'status' => 'publish',
            'short_code' => Str::generateCode($accommodation->name),
            'accommodation_category' => BookingUnitTypeInterface::RENTAL_UNIT,  // Always 'unit'
            'accommodation_available_units' => 1,  // Always 1
            'key_amenities' => $amenitiesString,
            'main_sleeping_capacity' => $accommodation->mainPlaces,
            'main_sleeping_allowed_occupancy' => range(1, $accommodation->mainPlaces),
            'address' => $accommodation->address,
        ];
    }
}
