<?php

namespace FluentBooking\App\Services;

use FluentBooking\App\Models\Calendar;
use FluentBooking\App\Models\CalendarSlot;
use FluentBooking\App\Models\Availability;
use FluentBooking\App\Services\Helper;
use FluentBooking\Framework\Support\Arr;

class CalendarService
{
    public static function createCalendar($data, $useCurrentUser = false, $isFileInput = false)
    {
        $calendarData = self::prepareCalendarData($data, $useCurrentUser, $isFileInput);

        if (is_wp_error($calendarData)) {
            return new \WP_Error($calendarData->get_error_code(), $calendarData->get_error_message());
        }

        $calendarData['slug'] = sanitize_title($calendarData['title']);

        if (!Helper::isCalendarSlugAvailable($calendarData['slug'], true)) {
            $calendarData['slug'] .= '-' . time();
        }

        $calendar = Calendar::create($calendarData);

        $calendarMetas = self::prepareCalendarMetas(Arr::get($data, 'metas', []));

        $calendar->metas()->createMany($calendarMetas);

        $availabilitiesData = Arr::get($data, 'availabilities', []);

        $createdAvailabilities = self::createAvailabilities($calendar, $availabilitiesData);

        $eventsData = Arr::get($data, 'events', []);

        self::createCalendarEvents($calendar, $eventsData, $createdAvailabilities);

        do_action('fluent_booking/after_create_calendar', $calendar);

        return [
            'calendar' => $calendar
        ];
    }

    public static function createAvailabilities($calendar, $availabilitiesData)
    {
        $createdAvailabilities = [];

        foreach ($availabilitiesData as $existingId => $availabilityData) {
            $availability = Arr::only($availabilityData, ['key', 'value']);
            $availability['value']['timezone'] = $calendar->author_timezone;
            $availability['object_id'] = $calendar->user_id;
            $availabilityModel = Availability::create($availability);
            $createdAvailabilities[$existingId] = $availabilityModel->id;
        }

        return $createdAvailabilities;
    }

    public static function createCalendarEvents($calendar, $eventsData, $availabilities = [])
    {
        $defaultEventData = (new CalendarSlot())->getEventDefaultData($calendar);

        if (empty($eventsData)) {
            $eventsData = [$defaultEventData];
        }

        $createEventsData = [];

        $createEventMetasData = [];

        foreach ($eventsData as $eventData) {
            $eventMetas = Arr::get($eventData, 'event_metas', []);

            $eventData = self::prepareEventData($eventData, $calendar, $availabilities);

            $createEventData = wp_parse_args($eventData, $defaultEventData);

            $createEventData['slug'] = Helper::generateSlotSlug((int)$createEventData['duration'] . 'min', $calendar);

            $createEventData['settings'] = wp_parse_args($createEventData['settings'], $defaultEventData['settings']);

            $createEventsData[] = Arr::only($createEventData, (new CalendarSlot())->getFillable());

            $createEventMetasData[] = $eventMetas;
        }

        $createdEvents = $calendar->events()->createMany($createEventsData);

        foreach ($createdEvents as $index => $event) {
            $eventMetasData = Arr::get($createEventMetasData, $index, []);

            $eventMetasData = self::prepareEventMetas($eventMetasData);

            $event->event_metas()->createMany($eventMetasData);
        }

        return $createdEvents;
    }

    protected static function prepareCalendarData($calendarData, $useCurrentUser = false, $isFileInput = false)
    {
        if (!$calendarData) {
            return new \WP_Error('invalid_data', esc_html__('Invalid JSON Data', 'fluent-booking'));
        }

        $preparedData = [
            'title'           => sanitize_text_field(Arr::get($calendarData, 'title')),
            'description'     => sanitize_textarea_field(Arr::get($calendarData, 'description')),
            'user_id'         => intval(Arr::get($calendarData, 'user_id')),
            'status'          => sanitize_text_field(Arr::get($calendarData, 'status', 'active')),
            'type'            => sanitize_text_field(Arr::get($calendarData, 'type', 'simple')),
            'event_type'      => sanitize_text_field(Arr::get($calendarData, 'event_type')),
            'account_type'    => sanitize_text_field(Arr::get($calendarData, 'account_type')),
            'visibility'      => sanitize_text_field(Arr::get($calendarData, 'visibility')),
            'author_timezone' => sanitize_text_field(Arr::get($calendarData, 'author_timezone')),
        ];

        if ($useCurrentUser || !Arr::get($preparedData, 'user_id')) {
            $preparedData['user_id'] = get_current_user_id();
        }

        if (!Arr::get($preparedData, 'author_timezone')) {
            $preparedData['author_timezone'] = wp_timezone_string();
        }

        if (!in_array(Arr::get($preparedData, 'type'), ['simple', 'team', 'event'])) {
            $preparedData['type'] = 'simple';
        }

        $user = get_user_by('ID', $preparedData['user_id']);

        if (!$user) {
            return new \WP_Error('invalid_user', esc_html__('Invalid User ID', 'fluent-booking'));
        }

        $isHostCalendar = $preparedData['type'] == 'simple' ? true : false;

        if ($isHostCalendar || !$preparedData['title']) {
            $preparedData['title'] = is_email($user->user_login) ? explode('@', $user->user_login)[0] : $user->user_login;
        }

        $firstCalendar = Calendar::where('user_id', $preparedData['user_id'])->where('type', 'simple')->first();

        if ($isHostCalendar && $firstCalendar) {
            if ($isFileInput) {
                return new \WP_Error('calendar_exists', esc_html__('The user already have a calendar. Please delete it first to create a new one', 'fluent-booking'));
            }
            return $firstCalendar;
        }

        return $preparedData;
    }

    protected static function prepareCalendarMetas($calendarMetas)
    {
        $preparedCalendarMetas = [];

        foreach ($calendarMetas as $calendarMeta) {
            if (empty($calendarMeta['key']) || empty($calendarMeta['value'])) {
                continue;
            }

            $value = $calendarMeta['value'];

            $preparedCalendarMetas[] = [
                'key'         => sanitize_text_field($calendarMeta['key']),
                'value'       => is_array($value) ? self::sanitize_mapped_data($value) : sanitize_text_field($value),
                'object_type' => sanitize_text_field($calendarMeta['object_type'])
            ];
        }

        return $preparedCalendarMetas;
    }

    protected static function prepareEventData($eventData, $calendar, $availabilities = [])
    {
        $preparedEventData = [
            'title'             => sanitize_text_field(Arr::get($eventData, 'title')),
            'duration'          => (int)Arr::get($eventData, 'duration', 30),
            'description'       => wp_kses_post(Arr::get($eventData, 'description')),
            'type'              => sanitize_text_field(Arr::get($eventData, 'type')),
            'status'            => sanitize_text_field(Arr::get($eventData, 'status', 'active')),
            'color_schema'      => sanitize_text_field(Arr::get($eventData, 'color_schema', '#0099ff')),
            'event_type'        => sanitize_text_field(Arr::get($eventData, 'event_type')),
            'availability_id'   => (int)self::prepareAvailabilityId(Arr::get($eventData, 'availability_id', 0), $availabilities),
            'availability_type' => sanitize_text_field(Arr::get($eventData, 'availability_type')),
            'location_type'     => sanitize_text_field(Arr::get($eventData, 'location_type')),
            'location_settings' => SanitizeService::locationSettings(Arr::get($eventData, 'location_settings', [])),
            'max_book_per_slot' => (int)Arr::get($eventData, 'max_book_per_slot', 1),
            'is_display_spots'  => (bool)Arr::get($eventData, 'is_display_spots', false),
        ];

        if (!empty($eventData['hash'])) {
            $hash = sanitize_text_field($eventData['hash']);
            if (!CalendarSlot::where('hash', $hash)->exists()) {
                $preparedEventData['hash'] = $hash;
            }
        }

        $eventSettings = Arr::get($eventData, 'settings', []);

        if (!$eventSettings) {
            return $preparedEventData;
        }

        $preparedEventData['settings'] = [
            'schedule_type'       => sanitize_text_field(Arr::get($eventSettings, 'schedule_type')),
            'weekly_schedules'    => SanitizeService::weeklySchedules(Arr::get($eventSettings, 'weekly_schedules', []), $calendar->author_timezone, 'UTC', true),
            'date_overrides'      => SanitizeService::slotDateOverrides(Arr::get($eventSettings, 'date_overrides', []), $calendar->author_timezone, 'UTC', null, true),
            'range_type'          => sanitize_text_field(Arr::get($eventSettings, 'range_type')),
            'range_days'          => (int)(Arr::get($eventSettings, 'range_days', 60)) ?: 60,
            'range_date_between'  => SanitizeService::rangeDateBetween(Arr::get($eventSettings, 'range_date_between', ['', ''])),
            'schedule_conditions' => SanitizeService::scheduleConditions(Arr::get($eventSettings, 'schedule_conditions', [])),
            'common_schedule'     => Arr::isTrue($eventSettings, 'common_schedule', false),
            'buffer_time_before'  => sanitize_text_field(Arr::get($eventSettings, 'buffer_time_before', '0')),
            'buffer_time_after'   => sanitize_text_field(Arr::get($eventSettings, 'buffer_time_after', '0')),
            'slot_interval'       => sanitize_text_field(Arr::get($eventSettings, 'slot_interval', '')),
            'team_members'        => array_map('intval', Arr::get($eventSettings, 'team_members', [])),
            'multi_duration'      => [
                'enabled'             => Arr::isTrue($eventSettings, 'multi_duration.enabled'),
                'default_duration'    => Arr::get($eventSettings, 'multi_duration.default_duration', ''),
                'available_durations' => array_map('sanitize_text_field', Arr::get($eventSettings, 'multi_duration.available_durations', []))
            ],
            'booking_frequency'   => [
                'enabled' => Arr::isTrue($eventSettings, 'booking_frequency.enabled'),
                'limits'  => self::sanitize_mapped_data(Arr::get($eventSettings, 'booking_frequency.limits', []))
            ],
            'booking_duration'    => [
                'enabled' => Arr::isTrue($eventSettings, 'booking_duration.enabled'),
                'limits'  => self::sanitize_mapped_data(Arr::get($eventSettings, 'booking_duration.limits', []))
            ],
            'lock_timezone'       => [
                'enabled'  => Arr::isTrue($eventSettings, 'lock_timezone.enabled'),
                'timezone' => sanitize_text_field(Arr::get($eventSettings, 'lock_timezone.timezone'))
            ],
            'booking_title'         => sanitize_text_field(Arr::get($eventSettings, 'booking_title')),
            'submit_button_text'    => sanitize_text_field(Arr::get($eventSettings, 'submit_button_text')),
            'custom_redirect'       => [
                'enabled'         => Arr::isTrue($eventSettings, 'custom_redirect.enabled'),
                'redirect_url'    => sanitize_url(Arr::get($eventSettings, 'custom_redirect.redirect_url')),
                'is_query_string' => Arr::get($eventSettings, 'custom_redirect.is_query_string') == 'yes' ? 'yes' : 'no',
                'query_string'    => sanitize_text_field(Arr::get($eventSettings, 'custom_redirect.query_string')),
            ],
            'requires_confirmation' => [
                'enabled'   => Arr::isTrue($eventSettings, 'requires_confirmation.enabled'),
                'type'      => sanitize_text_field(Arr::get($eventSettings, 'requires_confirmation.type')),
                'condition' => [
                    'unit'  => sanitize_text_field(Arr::get($eventSettings, 'requires_confirmation.condition.unit')),
                    'value' => intval(Arr::get($eventSettings, 'requires_confirmation.condition.value'))
                ]
            ],
            'multiple_booking'    => [
                'enabled'   => Arr::isTrue($eventSettings, 'multiple_booking.enabled'),
                'limit'     => intval(Arr::get($eventSettings, 'multiple_booking.limit'))
            ],
            'can_not_cancel'       => [
                'enabled'   => Arr::isTrue($eventSettings, 'can_not_cancel.enabled'),
                'type'      => sanitize_text_field(Arr::get($eventSettings, 'can_not_cancel.type')),
                'message'   => sanitize_text_field(Arr::get($eventSettings, 'can_not_cancel.message')),
                'condition' => [
                    'unit'  => sanitize_text_field(Arr::get($eventSettings, 'can_not_cancel.condition.unit')),
                    'value' => intval(Arr::get($eventSettings, 'can_not_cancel.condition.value'))
                ]
            ],
            'can_not_reschedule'   => [
                'enabled'   => Arr::isTrue($eventSettings, 'can_not_reschedule.enabled'),
                'type'      => sanitize_text_field(Arr::get($eventSettings, 'can_not_reschedule.type')),
                'message'   => sanitize_text_field(Arr::get($eventSettings, 'can_not_reschedule.message')),
                'condition' => [
                    'unit'  => sanitize_text_field(Arr::get($eventSettings, 'can_not_reschedule.condition.unit')),
                    'value' => intval(Arr::get($eventSettings, 'can_not_reschedule.condition.value'))
                ]
            ]
        ];

        return $preparedEventData;
    }

    protected static function prepareEventMetas($eventMetasData)
    {
        $preparedEventMetas = [];

        foreach ($eventMetasData as $eventMeta) {
            if (empty($eventMeta['key']) || empty($eventMeta['value'])) {
                continue;
            }

            $value = $eventMeta['value'];

            if ($eventMeta['key'] == 'email_notification') {
                $value = self::updateNotificationImageUrl($value);
            }

            $preparedEventMetas[] = [
                'key'         => sanitize_text_field($eventMeta['key']),
                'value'       => is_array($value) ? self::sanitize_mapped_data($value) : sanitize_text_field($value),
                'object_type' => sanitize_text_field($eventMeta['object_type'])
            ];
        }

        return $preparedEventMetas;
    }

    protected static function prepareAvailabilityId($availabilityId, $availabilities)
    {
        if ($availabilityId && isset($availabilities[$availabilityId])) {
            return $availabilities[$availabilityId];
        }

        if ($availabilities) {
            $firstKey = array_key_first($availabilities);
            return $availabilities[$firstKey];
        }

        return $availabilityId;
    }

    protected static function updateNotificationImageUrl($notifications)
    {
        $formattedNotifications = [];

        foreach ($notifications as $key => $notification) {
            $emailBody = Arr::get($notification, 'email.body');
            if (!$emailBody) {
                continue;
            }

            $newImageUrl = FLUENT_BOOKING_URL . 'assets/images';
            $pattern = '/(https:\/\/[^"]*?' . preg_quote('assets/images', '/') . ')/';
            $emailBody = preg_replace($pattern, $newImageUrl, $emailBody);
            $notification['email']['body'] = $emailBody;

            $formattedNotifications[$key] = $notification;
        }

        return $formattedNotifications;
    }

    public static function getSlotOptions($calendarId = null, $userId = null)
    {
        $calendarSlots = CalendarSlot::select(['id', 'title'])
            ->when($calendarId, function ($query) use ($calendarId) {
                return $query->where('calendar_id', $calendarId);
            })
            ->when($userId, function ($query) use ($userId) {
                return $query->where('user_id', $userId);
            })
            ->where('status', '!=', 'expired')
            ->latest()
            ->get();

        $options = [];
        foreach ($calendarSlots as $slot) {
            $options[] = [
                'id'    => $slot->id,
                'label' => $slot->title,
            ];
        }
        return apply_filters('fluent_booking/calendar_event_options', $options, $calendarId);
    }

    public static function getCalendarOptionsByHost()
    {
        $calendars = Calendar::select(['id', 'title'])
            ->when(!PermissionManager::hasAllCalendarAccess(true), function ($query) {
                return $query->where('user_id', get_current_user_id());
            })
            ->with(['slots' => function ($query) {
                $query->where('status', '!=', 'expired');
            }])
            ->latest()
            ->get();

        $formattedCalendars = [];
        foreach ($calendars as $index => $calendar) {
            $slots = Arr::get($calendar, 'slots');
            if (!empty($slots)) {
                $options = [];
                foreach ($slots as $slot) {
                    $options[] = [
                        'label' => Arr::get($slot, 'title'),
                        'value' => Arr::get($slot, 'id')
                    ];
                }
                if (!empty($options)) {
                    $formattedCalendars[$index] = [
                        'label'   => Arr::get($calendar, 'title'),
                        'options' => $options
                    ];
                }
            }
        }
        return $formattedCalendars;
    }

    public static function getCalendarOptionsByTitle($condition = '')
    {
        $calendarsQuery = Calendar::select(['id', 'title', 'user_id'])
            ->where('status', '!=', 'expired')
            ->with(['slots' => function ($query) {
                $query->where('status', '!=', 'expired');
            }]);

        switch ($condition) {
            case 'only_hosts':
                $calendarsQuery->where('type', 'simple');
                break;
            case 'only_teams':
                $calendarsQuery->where('type', 'team');
                break;
            case 'only_events':
                $calendarsQuery->where('type', 'event');
                break;
        }

        if (!PermissionManager::hasAllCalendarAccess(true)) {
            $attachedCalendarIds = self::getAttachedCalendarIds($calendarsQuery);
            $calendarsQuery->whereIn('id', $attachedCalendarIds);
        }

        $calendars = $calendarsQuery->latest()->get();

        $formattedCalendars = [];
        foreach ($calendars as $index => $calendar) {
            $slots = Arr::get($calendar, 'slots');
            if (!empty($slots)) {
                $options = [];
                foreach ($slots as $slot) {
                    $options[] = [
                        'id'    => Arr::get($slot, 'id'),
                        'title' => Arr::get($slot, 'title')
                    ];
                }
                if (!empty($options)) {
                    $formattedCalendars[$index] = [
                        'id'      => Arr::get($calendar, 'id'),
                        'title'   => Arr::get($calendar, 'title'),
                        'options' => $options
                    ];
                }
            }
        }
        return apply_filters('fluent_booking/calendar_options_by_title', $formattedCalendars);
    }

    public static function getAttachedCalendarIds($calendarsQuery)
    {
        $userId = get_current_user_id();

        $calendars = $calendarsQuery->get();

        $calendarIds = [];
        foreach ($calendars as $calendar) {
            if ($calendar->user_id == $userId) {
                $calendarIds[] = $calendar->id;
                continue;
            }

            $events = Arr::get($calendar, 'slots', []);
            foreach ($events as $event) {
                $teamMembers = Arr::get($event, 'settings.team_members', []);
                if (in_array($userId, $teamMembers)) {
                    $calendarIds[] = $calendar->id;
                }
            }
        }

        return $calendarIds;
    }

    public static function isSharedCalendar($calendar)
    {
        $calendarEvents = $calendar->events;

        $userId = get_current_user_id();

        foreach ($calendarEvents as $event) {
            if ($event->user_id == $userId) {
                return true;
            }
            $teamMembers = Arr::get($event, 'settings.team_members', []);
            if (in_array($userId, $teamMembers)) {
                return true;
            }
        }

        return false;
    }

    public static function updateCalendarEventsSchedule($calendarId, $oldTimezone, $updatedTimezone)
    {
        $calendarEvents = CalendarSlot::query()->where('calendar_id', $calendarId)->get();

        foreach ($calendarEvents as $event) {
            if ($weeklySchedule = Arr::get($event->settings, 'weekly_schedules', [])) {
                $originalSchedule = SanitizeService::weeklySchedules($weeklySchedule, 'UTC', $oldTimezone);
                $weeklySchedule = SanitizeService::weeklySchedules($originalSchedule, $updatedTimezone, 'UTC', true);
            }

            if ($dateOverride = Arr::get($event->settings, 'date_overrides', [])) {
                $originalOverride = SanitizeService::slotDateOverrides($dateOverride, 'UTC', $oldTimezone);
                $dateOverride = SanitizeService::slotDateOverrides($originalOverride, $updatedTimezone, 'UTC', null, true);
            }

            $event->settings = [
                'weekly_schedules' => $weeklySchedule,
                'date_overrides'   => $dateOverride
            ];

            $event->save();
        }
    }

    private static function sanitize_mapped_data($settings)
    {
        $sanitizerMap = [
            'value'                 => 'intval',
            'unit'                  => 'sanitize_text_field',
            'subject'               => 'sanitize_text_field',
            'body'                  => 'fcal_sanitize_html',
            'additional_recipients' => 'sanitize_text_field'
        ];

        return Helper::fcal_backend_sanitizer($settings, $sanitizerMap);
    }
}
