<?php

namespace BitApps\PiPro\src\Integrations\GoogleCalendar;

use BitApps\Pi\Deps\BitApps\WPKit\Helpers\DateTimeHelper;
use BitApps\Pi\Deps\BitApps\WPKit\Helpers\JSON;
use BitApps\Pi\Deps\BitApps\WPKit\Http\Client\HttpClient;
use DateTime;

if (!\defined('ABSPATH')) {
    exit;
}

class GoogleCalendarService
{
    private HttpClient $http;

    private string $baseUrl;

    /**
     * GoogleCalendarService constructor.
     *
     * @param HttpClient $httpClient
     */
    public function __construct($httpClient)
    {
        $this->http = $httpClient;
        $this->baseUrl = 'https://www.googleapis.com/calendar/v3';
    }

    /**
     * Create a new calendar.
     *
     * @param array $fieldMapData
     *
     * @return array
     */
    public function createCalendar($fieldMapData)
    {
        $endPoint = $this->baseUrl . '/calendars';
        $createCalendarData = JSON::encode($fieldMapData);
        $response = $this->http->request($endPoint, 'POST', $createCalendarData);

        return [
            'response'    => $response,
            'payload'     => $createCalendarData,
            'status_code' => $this->http->getResponseCode()
        ];
    }

    /**
     * Create a new event in the specified calendar.
     *
     * @param array $fieldMapData
     * @param string $calendarId
     * @param mixed $attendeeEmails
     *
     * @return array
     */
    public function createEvent($fieldMapData, $calendarId, $attendeeEmails)
    {
        $endPoint = $this->baseUrl . '/calendars/' . urlencode($calendarId) . '/events';

        if (!empty($attendeeEmails)) {
            $result = array_map(
                function ($item) {
                    return $item['attendee-email'];
                },
                $attendeeEmails
            );
            $attendeeData['attendees'] = $result;
            $fieldMapData = array_merge($fieldMapData, $attendeeData);
        }

        $wpTimezone = DateTimeHelper::wp_timezone();
        $startDateTime = new DateTime($fieldMapData['start'], $wpTimezone);
        $endDateTime = new DateTime($fieldMapData['end'], $wpTimezone);

        $fieldMapData = array_merge(
            $fieldMapData,
            [
                'start' => [
                    'dateTime' => $startDateTime->format('c'),
                    'timeZone' => DateTimeHelper::wp_timezone_string(),
                ],
                'end' => [
                    'dateTime' => $endDateTime->format('c'),
                    'timeZone' => DateTimeHelper::wp_timezone_string(),
                ],
            ]
        );

        $eventDataJson = JSON::encode($fieldMapData);
        $response = $this->http->request($endPoint, 'POST', $eventDataJson);

        return [
            'response'    => $response,
            'payload'     => $eventDataJson,
            'status_code' => $this->http->getResponseCode()
        ];
    }

    /**
     * Delete an event from the calendar.
     *
     * @param string $calendarId
     * @param string $eventId
     *
     * @return array
     */
    public function deleteEvent($calendarId, $eventId)
    {
        $endPoint = $this->baseUrl . '/calendars/' . urlencode($calendarId) . '/events/' . urlencode($eventId);
        $response = $this->http->request($endPoint, 'DELETE', null);

        return [
            'response'    => $response ?: ['success' => true],
            'payload'     => ['calendarId' => $calendarId, 'eventId' => $eventId],
            'status_code' => $this->http->getResponseCode()
        ];
    }

    /**
     * Get a single event from the calendar.
     *
     * @param string $calendarId
     * @param array $fieldMapData
     * @param null|mixed $eventFieldMapSwitch
     *
     * @return array
     */
    public function getEvent($calendarId, $fieldMapData, $eventFieldMapSwitch = null)
    {
        if ($eventFieldMapSwitch === true) {
            $eventId = $fieldMapData['eventSelectorId'] ?? null;
        } else {
            $eventId = $fieldMapData['eventMixInputId'] ?? null;
        }

        $endPoint = $this->baseUrl . '/calendars/' . urlencode($calendarId) . '/events/' . urlencode($eventId);
        $response = $this->http->request($endPoint, 'GET', null);

        return [
            'response'    => $response,
            'payload'     => ['calendarId' => $calendarId, 'eventId' => $eventId],
            'status_code' => $this->http->getResponseCode()
        ];
    }

    /**
     * Duplicate an existing event with optional modifications.
     *
     * @param string $calendarId
     * @param array $fieldMapData
     *
     * @return array
     */
    public function duplicateEvent($calendarId, $fieldMapData)
    {
        $originalEventResult = $this->getEvent($calendarId, $fieldMapData);

        if ($originalEventResult['status_code'] !== 200) {
            return $originalEventResult;
        }

        $eventData = $originalEventResult['response'];

        if (\is_object($eventData)) {
            $eventData = JSON::decode(JSON::encode($eventData), true);
        }

        unset($eventData['id'], $eventData['iCalUID'], $eventData['htmlLink'], $eventData['created'], $eventData['updated'], $eventData['etag']);
        $eventData['summary'] = $fieldMapData['newTitle'];
        $wpTimezone = DateTimeHelper::wp_timezone();

        if (!empty($fieldMapData['start'])) {
            $startDateTime = new DateTime($fieldMapData['start'], $wpTimezone);
            $eventData['start'] = [
                'dateTime' => $startDateTime->format('c'),
                'timeZone' => DateTimeHelper::wp_timezone_string(),
            ];
        }

        if (!empty($fieldMapData['end'])) {
            $endDateTime = new DateTime($fieldMapData['end'], $wpTimezone);
            $eventData['end'] = [
                'dateTime' => $endDateTime->format('c'),
                'timeZone' => DateTimeHelper::wp_timezone_string(),
            ];
        }

        $createEventEndpoint = $this->baseUrl . '/calendars/' . urlencode($calendarId) . '/events';
        $eventDataJson = JSON::encode($eventData);
        $response = $this->http->request($createEventEndpoint, 'POST', $eventDataJson);

        return [
            'response'    => $response,
            'payload'     => $eventDataJson,
            'status_code' => $this->http->getResponseCode()
        ];
    }

    /**
     * Get multiple events from the calendar.
     *
     * @param string $calendarId
     * @param array $fieldMapData
     *
     * @return array
     */
    public function getEvents($calendarId, $fieldMapData)
    {
        $endPoint = $this->baseUrl . '/calendars/' . urlencode($calendarId) . '/events';

        $queryParams = [
            'timeMin'      => $fieldMapData['timeMin'] ?? null,
            'timeMax'      => $fieldMapData['timeMax'] ?? null,
            'maxResults'   => $fieldMapData['maxResults'] ?? null,
            'singleEvents' => $fieldMapData['singleEvents'] ? 'true' : 'false',
        ];

        $queryParams = array_filter($queryParams);
        $endPoint = add_query_arg($queryParams, $this->baseUrl . '/calendars/' . urlencode($calendarId) . '/events');
        $response = $this->http->request($endPoint, 'GET', null);

        return [
            'response'    => $response,
            'payload'     => ['calendarId' => $calendarId],
            'status_code' => $this->http->getResponseCode()
        ];
    }

    /**
     * Update an existing event.
     *
     * @param array $fieldMapData
     * @param string $calendarId
     * @param string $eventId
     *
     * @return array
     */
    public function updateEvent($fieldMapData, $calendarId, $eventId)
    {
        $endPoint = $this->baseUrl . '/calendars/' . urlencode($calendarId) . '/events/' . urlencode($eventId);

        $wpTimezone = DateTimeHelper::wp_timezone();
        $startDateTime = new DateTime($fieldMapData['start'], $wpTimezone);
        $endDateTime = new DateTime($fieldMapData['end'], $wpTimezone);

        $fieldMapData = array_merge(
            $fieldMapData,
            [
                'start' => [
                    'dateTime' => $startDateTime->format('c'),
                    'timeZone' => DateTimeHelper::wp_timezone_string(),
                ],
                'end' => [
                    'dateTime' => $endDateTime->format('c'),
                    'timeZone' => DateTimeHelper::wp_timezone_string(),
                ],
            ]
        );

        $eventDataJson = JSON::encode($fieldMapData);
        $response = $this->http->request($endPoint, 'PATCH', $eventDataJson);

        return [
            'response'    => $response,
            'payload'     => $eventDataJson,
            'status_code' => $this->http->getResponseCode()
        ];
    }

    /**
     * Find availability for a calendar within a time range.
     *
     * @param string $calendarId
     * @param array $fieldMapData
     *
     * @return array
     */
    public function findAvailability($calendarId, $fieldMapData)
    {
        $endPoint = $this->baseUrl . '/freeBusy';

        $wpTimezone = DateTimeHelper::wp_timezone();

        $availabilityData = [
            'items' => [
                [
                    'id' => $calendarId,
                ],
            ],
        ];

        if (!empty($fieldMapData['timeMin'])) {
            $timeMin = new DateTime($fieldMapData['timeMin'], $wpTimezone);
            $availabilityData['timeMin'] = $timeMin->format('c');
        }

        if (!empty($fieldMapData['timeMax'])) {
            $timeMax = new DateTime($fieldMapData['timeMax'], $wpTimezone);
            $availabilityData['timeMax'] = $timeMax->format('c');
        }

        $availabilityData['timeZone'] = DateTimeHelper::wp_timezone_string();

        $availabilityDataJson = JSON::encode($availabilityData);
        $response = $this->http->request($endPoint, 'POST', $availabilityDataJson);

        return [
            'response'    => $response,
            'payload'     => $availabilityDataJson,
            'status_code' => $this->http->getResponseCode()
        ];
    }

    /**
     * List all calendars for the authenticated user.
     *
     * @param array $fieldMapData
     *
     * @return array
     */
    public function listCalendars($fieldMapData)
    {
        $endPoint = $this->baseUrl . '/users/me/calendarList';

        $queryParams = [];

        if (!empty($fieldMapData['maxResults'])) {
            $queryParams['maxResults'] = (int) $fieldMapData['maxResults'];
        }

        $endPoint = add_query_arg($queryParams, $this->baseUrl . '/users/me/calendarList');
        $response = $this->http->request($endPoint, 'GET', null);

        return [
            'response'    => $response,
            'payload'     => $fieldMapData,
            'status_code' => $this->http->getResponseCode()
        ];
    }

    /**
     * Delete a calendar.
     *
     * @param array $fieldMapData
     * @param null|mixed $calendarFieldMapSwitch
     *
     * @return array
     */
    public function deleteCalendar($fieldMapData, $calendarFieldMapSwitch = null)
    {
        if ($calendarFieldMapSwitch === true) {
            $calendarId = $fieldMapData['calendarSelectorId'] ?? null;
        } else {
            $calendarId = $fieldMapData['calendarMixInputId'] ?? null;
        }

        $endPoint = $this->baseUrl . '/calendars/' . urlencode($calendarId);
        $response = $this->http->request($endPoint, 'DELETE', null);

        return [
            'response'    => $response ?: ['success' => true],
            'payload'     => $fieldMapData,
            'status_code' => $this->http->getResponseCode()
        ];
    }

    /**
     * Clear all events from a calendar.
     *
     * @param array $fieldMapData
     * @param null|mixed $calendarFieldMapSwitch
     *
     * @return array
     */
    public function clearCalendar($fieldMapData, $calendarFieldMapSwitch = null)
    {
        if ($calendarFieldMapSwitch === true) {
            $calendarId = $fieldMapData['calendarSelectorId'] ?? null;
        } else {
            $calendarId = $fieldMapData['calendarMixInputId'] ?? null;
        }

        $endPoint = $this->baseUrl . '/calendars/' . urlencode($calendarId) . '/clear';
        $response = $this->http->request($endPoint, 'POST', null);

        return [
            'response'    => $response ?: ['success' => true],
            'payload'     => $fieldMapData,
            'status_code' => $this->http->getResponseCode()
        ];
    }

    /**
     * Update calendar properties.
     *
     * @param array $fieldMapData
     * @param null|mixed $calendarFieldMapSwitch
     *
     * @return array
     */
    public function updateCalendar($fieldMapData, $calendarFieldMapSwitch = null)
    {
        if ($calendarFieldMapSwitch === true) {
            $calendarId = $fieldMapData['calendarSelectorId'] ?? null;
        } else {
            $calendarId = $fieldMapData['calendarMixInputId'] ?? null;
        }

        $endPoint = $this->baseUrl . '/calendars/' . urlencode($calendarId);
        $updateDataJson = JSON::encode($fieldMapData);
        $response = $this->http->request($endPoint, 'PATCH', $updateDataJson);

        return [
            'response'    => $response,
            'payload'     => $updateDataJson,
            'status_code' => $this->http->getResponseCode()
        ];
    }

    /**
     * Create an access control rule for calendar sharing.
     *
     * @param string $calendarId
     * @param array $fieldMapData
     *
     * @return array
     */
    public function createAccessControlRule($calendarId, $fieldMapData)
    {
        $endPoint = $this->baseUrl . '/calendars/' . urlencode($calendarId) . '/acl';
        $aclData = JSON::encode($fieldMapData);
        $response = $this->http->request($endPoint, 'POST', $aclData);

        return [
            'response'    => $response,
            'payload'     => $aclData,
            'status_code' => $this->http->getResponseCode()
        ];
    }

    /**
     * List all access control rules for a calendar.
     *
     * @param string $calendarId
     *
     * @return array
     */
    public function listAccessControlRules($calendarId)
    {
        $endPoint = $this->baseUrl . '/calendars/' . urlencode($calendarId) . '/acl';
        $response = $this->http->request($endPoint, 'GET', null);

        return [
            'response'    => $response,
            'payload'     => ['calendarId' => $calendarId],
            'status_code' => $this->http->getResponseCode()
        ];
    }

    /**
     * Get a specific access control rule.
     *
     * @param string $calendarId
     * @param string $ruleId
     *
     * @return array
     */
    public function getAccessControlRule($calendarId, $ruleId)
    {
        $endPoint = $this->baseUrl . '/calendars/' . urlencode($calendarId) . '/acl/' . urlencode($ruleId);
        $response = $this->http->request($endPoint, 'GET', null);

        return [
            'response'    => $response,
            'payload'     => ['calendarId' => $calendarId, 'ruleId' => $ruleId],
            'status_code' => $this->http->getResponseCode()
        ];
    }

    /**
     * Delete an access control rule.
     *
     * @param string $calendarId
     * @param string $ruleId
     *
     * @return array
     */
    public function deleteAccessControlRule($calendarId, $ruleId)
    {
        $endPoint = $this->baseUrl . '/calendars/' . urlencode($calendarId) . '/acl/' . urlencode($ruleId);
        $response = $this->http->request($endPoint, 'DELETE', null);

        return [
            'response'    => $response ?: ['success' => true],
            'payload'     => ['calendarId' => $calendarId, 'ruleId' => $ruleId],
            'status_code' => $this->http->getResponseCode()
        ];
    }

    /**
     * Add attendees to an existing event.
     *
     * @param string $calendarId
     * @param array $fieldMapData
     *
     * @return array
     */
    public function addAttendeesToEvent($calendarId, $fieldMapData)
    {
        $attendeesData = $this->getEventAttendeesData($calendarId, $fieldMapData);

        if ($attendeesData['errorResponse'] !== null) {
            return $attendeesData['errorResponse'];
        }

        $existingEmails = $attendeesData['existingEmails'];
        $newEmails = $attendeesData['inputEmails'];

        $mergedEmails = array_unique(array_merge($existingEmails, $newEmails));
        $mergedAttendees = array_map(fn ($email) => ['email' => $email], $mergedEmails);
        $fieldMapData['attendees'] = $mergedAttendees;
        $updateDataJson = JSON::encode($fieldMapData);
        $endPoint = $this->baseUrl . '/calendars/' . urlencode($calendarId) . '/events/' . urlencode($fieldMapData['eventSelectorId']);
        $response = $this->http->request($endPoint, 'PATCH', $updateDataJson);

        return [
            'response'    => $response,
            'payload'     => $updateDataJson,
            'status_code' => $this->http->getResponseCode()
        ];
    }

    /**
     * Remove attendees from an existing event.
     *
     * @param string $calendarId
     * @param array $fieldMapData
     *
     * @return array
     */
    public function removeAttendeesFromEvent($calendarId, $fieldMapData)
    {
        $attendeesData = $this->getEventAttendeesData($calendarId, $fieldMapData);

        if ($attendeesData['errorResponse'] !== null) {
            return $attendeesData['errorResponse'];
        }

        $existingEmails = $attendeesData['existingEmails'];
        $inputEmails = $attendeesData['inputEmails'];

        $filteredEmails = array_filter(
            $existingEmails,
            function ($email) use ($inputEmails) {
                return !\in_array(strtolower($email), array_map('strtolower', $inputEmails));
            }
        );

        $filteredAttendees = array_map(fn ($email) => ['email' => $email], array_values($filteredEmails));
        $fieldMapData['attendees'] = $filteredAttendees;
        $updateDataJson = JSON::encode($fieldMapData);
        $endPoint = $this->baseUrl . '/calendars/' . urlencode($calendarId) . '/events/' . urlencode($fieldMapData['eventSelectorId']);
        $response = $this->http->request($endPoint, 'PATCH', $updateDataJson);

        return [
            'response'    => $response,
            'payload'     => $updateDataJson,
            'status_code' => $this->http->getResponseCode()
        ];
    }

    /**
     * Helper method to get existing attendees and parse input emails.
     *
     * @param string $calendarId
     * @param array $fieldMapData
     *
     * @return array
     */
    private function getEventAttendeesData($calendarId, $fieldMapData)
    {
        $getEventData = $this->getEvent($calendarId, $fieldMapData, true);

        if ($getEventData['status_code'] !== 200) {
            return ['errorResponse' => $getEventData];
        }

        $existingAttendees = $getEventData['response']->attendees ?? [];
        $existingEmails = array_map(fn ($attendee) => $attendee->email, $existingAttendees);

        $inputEmails = !empty($fieldMapData['attendees'])
            ? array_map('trim', explode(',', $fieldMapData['attendees']))
            : [];

        return [
            'existingEmails' => $existingEmails,
            'inputEmails'    => $inputEmails,
            'errorResponse'  => null
        ];
    }
}
