<?php

namespace App\Observers\Hotel;

use App\Models\Hotel\Room;
use App\Models\Hotel\RoomType;
use App\Models\Hotel\Season;

use App\Enums\Hotel\CycleType;

class SeasonObserver
{
  //? CRUD Functions
    public function created(Season $season): void
    {
      //* Update Room Types (Add created season to RoomTypes that don't use default season)
        RoomType::where('tr_seasons->using_default', false)
          ->chunkById(25, function ($chunk) use ($season) { // Process data in batches of 25 RoomTypes
            foreach ($chunk as $room_type) {
              // Extract & Parse Data
                $room_type_configuration = $this->formatRoomTypeConfig($room_type->tr_seasons);
                $room_type_pax_configuration = $this->formatRoomTypePaxConfig($room_type->tr_pax_seasons);
                $room_type_express_configuration = $this->formatRoomTypeExpressConfig($room_type->tr_express_seasons);

              // Add New Season
                $shared_data = [ 'season_id' => $season->id, 'season_name' => $season->s_name ];
                $durations = $this->extractPackageDurations($room_type_express_configuration['other_seasons']);

                $room_type_configuration['other_seasons'][] = [ // Season
                    ...$shared_data, 'season_price' => 0.0,
                ];
                $room_type_pax_configuration['other_seasons'][] = [ // Pax
                    ...$shared_data, 'season_pax_pricing' => $this->setDefaultPaxPricing($room_type->pax_room_limit),
                ];
                $room_type_express_configuration['other_seasons'][] = [ // Express
                    ...$shared_data, 'season_express_pricing' => $this->setDefaultExpressPricing($durations, $room_type->pax_room_limit),
                ];

              // Update Record
                $room_type->tr_seasons = $room_type_configuration;
                $room_type->tr_pax_seasons = $room_type_pax_configuration;
                $room_type->tr_express_seasons = $room_type_express_configuration;
                $room_type->saveQuietly(); // Avoid Launch Other Observers
            }
        });

      //* Update Rooms (Add created season to Rooms that don't use default season)
        Room::chunkById(25, function ($chunk) use ($season) { // Process data in batches of 25 Rooms
            foreach ($chunk as $room) {
              // Extract & Parse Data
                $room_configuration = $this->formatRoomConfig($room->room_prices);
                $room_pax_configuration = $this->formatRoomPaxConfig($room->room_pax_prices);
                $room_express_configuration = $this->formatRoomExpressConfig($room->room_express_prices);

                if (!$this->isRoomUsingDefaultSeason($room_configuration)) {
                  // Add New Season
                    $shared_data = [
                        'season_id'    => $season->id,
                        'season_name'  => $season->s_name,
                        'is_cyclic'    => $season->is_cyclic,
                        'cycle_type'   => $season->cycle_type,
                        'periods'      => data_get($season->s_periods, 'periods', []),
                    ];
                    $durations = $this->extractPackageDurations($room_express_configuration['room_prices']);

                    $room_configuration['room_prices'][] = [ // Season
                        ...$shared_data, 'season_price' => 0.0,
                    ];
                    $room_pax_configuration['room_prices'][] = [ // Pax
                        ...$shared_data, 'season_pax_pricing' => $this->setDefaultPaxPricing($room->roomType->pax_room_limit),
                    ];
                    $room_express_configuration['room_prices'][] = [ // Express
                        ...$shared_data,
                        'season_express_pricing' => $this->setDefaultExpressPricing($durations, $room->roomType->pax_room_limit),
                    ];

                  // Update Record
                    $room->room_prices = $room_configuration;
                    $room->room_pax_prices = $room_pax_configuration;
                    $room->room_express_prices = $room_express_configuration;
                    $room->saveQuietly(); // Avoid Launch Other Observers
                }
            }
        });
    }

    public function updated(Season $season): void
    {
      //* Update Room Types (Sync new season data on RoomTypes that don't use default season)
        RoomType::where('tr_seasons->using_default', false)
          ->chunkById(200, function ($chunk) use ($season) { // Process data in batches of 25 RoomTypes
            foreach ($chunk as $room_type) {
              // Extract & Parse Data
                $room_type_configuration = $this->formatRoomTypeConfig($room_type->tr_seasons);
                $room_type_pax_configuration = $this->formatRoomTypePaxConfig($room_type->tr_pax_seasons);
                $room_type_express_configuration = $this->formatRoomTypeExpressConfig($room_type->tr_express_seasons);

              // Update Season
                // Season
                $room_type_configuration['other_seasons'] = array_map(function ($s) use ($season) {
                    if ((int) data_get($s, 'season_id') === (int) $season->id)
                        $s['season_name'] = $season->s_name;

                    return $s;
                }, $room_type_configuration['other_seasons']);

                // Pax
                $room_type_pax_configuration['other_seasons'] = array_map(function ($s) use ($season) {
                    if ((int) data_get($s, 'season_id') === (int) $season->id)
                        $s['season_name'] = $season->s_name;

                    return $s;
                }, $room_type_pax_configuration['other_seasons']);

                // Express
                $room_type_express_configuration['other_seasons'] = array_map(function ($s) use ($season) {
                    if ((int) data_get($s, 'season_id') === (int) $season->id)
                        $s['season_name'] = $season->s_name;

                    return $s;
                }, $room_type_express_configuration['other_seasons']);

              // Update Record
                $room_type->tr_seasons = $room_type_configuration;
                $room_type->tr_pax_seasons = $room_type_pax_configuration;
                $room_type->tr_express_seasons = $room_type_express_configuration;
                $room_type->saveQuietly(); // Avoid Launch Other Observers
            }
        });

      //* Update Rooms (Sync new season data on Rooms that don't use default season)
        $newPeriods = data_get($season->s_periods, 'periods', []);
        Room::chunkById(200, function ($chunk) use ($season, $newPeriods) { // Process data in batches of 25 Rooms
            foreach ($chunk as $room) {
              // Extract & Parse Data
                $room_configuration = $this->formatRoomConfig($room->room_prices);
                $room_pax_configuration = $this->formatRoomPaxConfig($room->room_pax_prices);
                $room_express_configuration = $this->formatRoomExpressConfig($room->room_express_prices);

                if (!$this->isRoomUsingDefaultSeason($room_configuration)) {
                  // Update Season
                    // Season
                    $room_configuration['room_prices'] = array_map(function ($s) use ($season, $newPeriods) {
                        if ((int) data_get($s, 'season_id') === (int) $season->id) {
                            $s['season_name'] = $season->s_name;
                            $s['is_cyclic']   = $season->is_cyclic;
                            $s['cycle_type']  = $season->cycle_type;
                            $s['periods']     = $newPeriods;
                        }
                        return $s;
                    }, $room_configuration['room_prices']);

                    // Pax
                    $room_pax_configuration['room_prices'] = array_map(function ($s) use ($season, $newPeriods) {
                        if ((int) data_get($s, 'season_id') === (int) $season->id) {
                            $s['season_name'] = $season->s_name;
                            $s['is_cyclic']   = $season->is_cyclic;
                            $s['cycle_type']  = $season->cycle_type;
                            $s['periods']     = $newPeriods;

                        }
                        return $s;
                    }, $room_pax_configuration['room_prices']);

                    // Express
                    $room_express_configuration['room_prices'] = array_map(function ($s) use ($season, $newPeriods) {
                        if ((int) data_get($s, 'season_id') === (int) $season->id) {
                            $s['season_name'] = $season->s_name;
                            $s['is_cyclic']   = $season->is_cyclic;
                            $s['cycle_type']  = $season->cycle_type;
                            $s['periods']     = $newPeriods;

                        }
                        return $s;
                    }, $room_express_configuration['room_prices']);

                  // Update Record
                    $room->room_prices = $room_configuration;
                    $room->room_pax_prices = $room_pax_configuration;
                    $room->room_express_prices = $room_express_configuration;
                    $room->saveQuietly(); // Avoid Launch Other Observers
                }
            }
        });
    }

    public function deleting(Season $season): void
    {
        // Avoid Delete Default season
        if (in_array($season->id, [1]))
            throw new \Exception("Default season cannot be Deleted.");
    }

    public function deleted(Season $season): void
    {
        // Shared Data
        $remaining_seasons = Season::count();
        $only_default_season_remains = ($remaining_seasons <= 1); // Is the default season the only one left?

      //* Update Room Types (Remove deleted season from RoomTypes that don't use default season)
        RoomType::where('tr_seasons->using_default', false)
            ->chunkById(25, function ($chunk) use ($season, $only_default_season_remains) { // Process data in batches of 25 RoomTypes
                foreach ($chunk as $room_type) {
                  // Extract & Parse Data
                    $room_type_configuration = $this->formatRoomTypeConfig($room_type->tr_seasons);
                    $room_type_pax_configuration = $this->formatRoomTypePaxConfig($room_type->tr_pax_seasons);
                    $room_type_express_configuration = $this->formatRoomTypeExpressConfig($room_type->tr_express_seasons);

                  // Delete Season
                    if ($only_default_season_remains) { // Reset RT Configuration and assign Default Season
                      // Season
                        $room_type_configuration['using_default'] = true;
                        $room_type_configuration['default_season_price'] = 0.0;
                        $room_type_configuration['other_seasons'] = [];

                      // PAX
                        $room_type_pax_configuration['using_default'] = true;
                        $room_type_pax_configuration['default_season_pax_pricing'] = $this->setDefaultPaxPricing($room_type->pax_room_limit);
                        $room_type_pax_configuration['other_seasons'] = [];

                      // Express
                        $durations = $this->extractPackageDurations($room_type_express_configuration['other_seasons']);

                        $room_type_express_configuration['using_default'] = true;
                        $room_type_express_configuration['default_season_express_pricing'] = $this->setDefaultExpressPricing($durations, $room_type->pax_room_limit);
                        $room_type_express_configuration['other_seasons'] = [];
                    } else { // Remove deleted season from RT Configuration
                      // Season
                        $room_type_configuration['other_seasons'] = array_values(array_filter(
                            $room_type_configuration['other_seasons'],
                            fn ($s) => (int) data_get($s, 'season_id') !== (int) $season->id
                        ));

                      // PAX
                        $room_type_pax_configuration['other_seasons'] = array_values(array_filter(
                            $room_type_pax_configuration['other_seasons'],
                            fn ($s) => (int) data_get($s, 'season_id') !== (int) $season->id
                        ));

                      // Express
                        $room_type_express_configuration['other_seasons'] = array_values(array_filter(
                            $room_type_express_configuration['other_seasons'],
                            fn ($s) => (int) data_get($s, 'season_id') !== (int) $season->id
                        ));
                    }

                  // Update Record
                    $room_type->tr_seasons = $room_type_configuration;
                    $room_type->tr_pax_seasons = $room_type_pax_configuration;
                    $room_type->tr_express_seasons = $room_type_express_configuration;
                    $room_type->saveQuietly(); // Avoid Launch Other Observers
                }
            });

      //* Update Rooms (Remove deleted season from Rooms that don't use default season)
        $default_season = Season::find(1); // puede ser null en datos corruptos, asumimos que existe
        $default_periods = data_get($default_season->s_periods, 'periods', []);
        $default_name = $default_season->s_name;
        $default_is_cyclic = $default_season->is_cyclic;
        $default_cycle_type = $default_season->cycle_type;

        Room::chunkById(25, function ($chunk) use ($season, $only_default_season_remains, $default_is_cyclic, $default_cycle_type, $default_periods, $default_name) { // Process data in batches of 25 Rooms
            foreach ($chunk as $room) {
              // Extract & Parse Data
                $room_configuration = $this->formatRoomConfig($room->room_prices);
                $room_pax_configuration = $this->formatRoomPaxConfig($room->room_pax_prices);
                $room_express_configuration = $this->formatRoomExpressConfig($room->room_express_prices);

                $durations = $this->extractPackageDurations($room_express_configuration['room_prices']);

              // Delete Season
                // Si ya está en única por defecto, no hacemos nada
                if ($this->isRoomUsingDefaultSeason($room_configuration))
                    continue;

                if ($only_default_season_remains) {
                    // Reset Room Configuration and assign Default Season
                    $shared_data = [
                        'season_id'    => 1,
                        'season_name'  => $default_name,
                        'is_cyclic'    => $default_is_cyclic,
                        'cycle_type'   => $default_cycle_type,
                        'periods'      => $default_periods,
                    ];

                    $room_configuration['room_prices'] = [[ // Season
                        ...$shared_data, 'season_price' => 0.0,
                    ]];

                    $room_pax_configuration['room_prices'] = [[ // Pax
                        ...$shared_data, 'season_pax_pricing' => $this->setDefaultPaxPricing($room->roomType->pax_room_limit),
                    ]];

                    $room_express_configuration['room_prices'] = [[ // Express
                        ...$shared_data, 'season_express_pricing' => $this->setDefaultExpressPricing($durations, $room->roomType->pax_room_limit),
                    ]];
                } else {
                    // Remove deleted season from Room Configuration
                    $room_configuration['room_prices'] = array_values(array_filter( // Season
                        $room_configuration['room_prices'],
                        fn ($s) => (int) data_get($s, 'season_id') !== (int) $season->id
                    ));

                    $room_pax_configuration['room_prices'] = array_values(array_filter( // Pax
                        $room_pax_configuration['room_prices'],
                        fn ($s) => (int) data_get($s, 'season_id') !== (int) $season->id
                    ));

                    $room_express_configuration['room_prices'] = array_values(array_filter( // Express
                        $room_express_configuration['room_prices'],
                        fn ($s) => (int) data_get($s, 'season_id') !== (int) $season->id
                    ));

                    // If for some reason Room Configuration it is empty, it defaults as a safeguard.
                    $shared_data = [
                        'season_id'    => 1,
                        'season_name'  => $default_name,
                        'is_cyclic'    => $default_is_cyclic,
                        'cycle_type'   => $default_cycle_type,
                        'periods'      => $default_periods,
                    ];

                    if (empty($room_configuration['room_prices'])) { // Season
                        $room_configuration['room_prices'][] = [
                            ...$shared_data, 'season_price' => 0.0,
                        ];
                    }

                    if (empty($room_pax_configuration['room_prices'])) { // Pax
                        $room_pax_configuration['room_prices'][] = [
                            ...$shared_data, 'season_pax_pricing' => $this->setDefaultPaxPricing($room->roomType->pax_room_limit),
                        ];
                    }

                    if (empty($room_express_configuration['room_prices'])) { // Express
                        $room_express_configuration['room_prices'][] = [
                            ...$shared_data,
                            'season_express_pricing' => $this->setDefaultExpressPricing($durations, $room->roomType->pax_room_limit),
                        ];
                    }
                }

              // Update Record
                $room->room_prices = $room_configuration;
                $room->room_pax_prices = $room_pax_configuration;
                $room->room_express_prices = $room_express_configuration;
                $room->saveQuietly(); // Avoid Launch Other Observers
            }
        });
    }

  //? Support Functions
    //* Formatters
    /** Normalize tr_seasons on a standard structure */
    private function formatRoomTypeConfig($value): array
    {
        $room_type_configuration = is_array($value) ? $value : (array) $value;
        $room_type_configuration['using_default']        = (bool) data_get($room_type_configuration, 'using_default', false);
        $room_type_configuration['default_season_price'] = (float) data_get($room_type_configuration, 'default_season_price', 0.0);
        $room_type_configuration['other_seasons']        = array_values((array) data_get($room_type_configuration, 'other_seasons', []));
        return $room_type_configuration;
    }

    /** Normalize room_prices on a standard structure */
    private function formatRoomConfig($value): array
    {
        $room_configuration = is_array($value) ? $value : (array) $value;
        $room_configuration['using_default_prices']  = (bool) data_get($room_configuration, 'using_default_prices', true);
        $room_configuration['using_default_periods'] = (bool) data_get($room_configuration, 'using_default_periods', true);
        $room_configuration['room_prices']           = array_values((array) data_get($room_configuration, 'room_prices', []));
        return $room_configuration;
    }

    /** Normalize tr_pax_seasons on a standard structure */
    private function formatRoomTypePaxConfig($value): array
    {
        $config = is_array($value) ? $value : (array) $value;
        $config['using_default']                = (bool) data_get($config, 'using_default', false);
        $config['default_season_pax_pricing']   = (array) data_get($config, 'default_season_pax_pricing', []);
        $config['other_seasons']                = array_values((array) data_get($config, 'other_seasons', []));
        return $config;
    }

    /** Normalize room_pax_prices on a standard structure */
    private function formatRoomPaxConfig($value): array
    {
        $config = is_array($value) ? $value : (array) $value;
        $config['using_default_prices']  = (bool) data_get($config, 'using_default_prices', true);
        $config['using_default_periods'] = (bool) data_get($config, 'using_default_periods', true);
        $config['room_prices']           = array_values((array) data_get($config, 'room_prices', []));
        return $config;
    }

    /** Normalize tr_express_seasons on a standard structure */
    private function formatRoomTypeExpressConfig($value): array
    {
        $config = is_array($value) ? $value : (array) $value;

        $config['using_default'] = (bool) data_get($config, 'using_default', true);
        $config['default_season_express_pricing'] = (array) data_get($config, 'default_season_express_pricing', []);
        $config['other_seasons'] = array_values((array) data_get($config, 'other_seasons', []));

        return $config;
    }

    /** Normalize room_express_prices on a standard structure */
    private function formatRoomExpressConfig($value): array
    {
        $config = is_array($value) ? $value : (array) $value;

        $config['using_default_prices']  = (bool) data_get($config, 'using_default_prices', true);
        $config['using_default_periods'] = (bool) data_get($config, 'using_default_periods', true);
        $config['room_prices'] = array_values((array) data_get($config, 'room_prices', []));

        return $config;
    }

    //* Extractors
    /** Is the room using default season? */
    private function isRoomUsingDefaultSeason(array $room_configuration): bool
    {
        $first = $room_configuration['room_prices'][0] ?? null;
        return (int) data_get($first, 'season_id', 1) === 1;
    }

    /** Extract Season Express Packages */
    private function extractPackageDurations(array $seasons_configuration): array
    {
        return collect($seasons_configuration)
            ->pluck('season_express_pricing')->flatten(1) // Extract Express Pricing from season
            ->pluck('package_duration')->filter()->unique()->values() // Only extract packages durations
            ->whenEmpty(fn () => collect([1])) // If packages are not recorded, set 1 as default
            ->toArray();
    }

    //* Initializers
    /** Define default PAX pricing */
    private function setDefaultPaxPricing(int $room_limit): array
    {
        return collect()->times($room_limit, fn($i) => [
            'px_quantity' => $i,
            'px_price' => 0.00,
        ])->toArray();
    }

    /** Define default Express pricing */
    private function setDefaultExpressPricing(array $durations, int $roomLimit): array
    {
        $paxPricing = [];
        for ($i = 1; $i <= $roomLimit; $i++)
            $paxPricing[] = ['px_quantity' => $i, 'px_price' => 0.0];

        return collect($durations)
            ->map(fn ($duration) => [
                'package_duration' => $duration,
                'package_price' => 0.0,
                'express_pax_pricing' => $paxPricing
            ])->values()->toArray();
    }

}
