<?php

namespace App\Modules\Ews;

// Map to global classes.
use Illuminate\Support\Facades\Config;
use App\Facades\DBconfig;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\DB;

use jamesiarmes\PhpEws\Client;

// Request types
use jamesiarmes\PhpEws\Request\FindItemType;
use jamesiarmes\PhpEws\Request\CreateItemType;
use jamesiarmes\PhpEws\Request\GetServerTimeZonesType;
use jamesiarmes\PhpEws\Request\GetItemType;
use jamesiarmes\PhpEws\Request\UpdateItemType;

// Types
use jamesiarmes\PhpEws\Type\CalendarViewType;
use jamesiarmes\PhpEws\Type\CalendarItemType;
use jamesiarmes\PhpEws\Type\TimeZoneDefinitionType;
use jamesiarmes\PhpEws\Type\BodyType;
use jamesiarmes\PhpEws\Type\EmailAddressType;
use jamesiarmes\PhpEws\Type\SingleRecipientType;
use jamesiarmes\PhpEws\Type\ItemIdType;
use jamesiarmes\PhpEws\Type\PathToUnindexedFieldType;
use jamesiarmes\PhpEws\Type\ItemChangeType;
use jamesiarmes\PhpEws\Type\ItemResponseShapeType;
use jamesiarmes\PhpEws\Type\DistinguishedFolderIdType;
use jamesiarmes\PhpEws\Type\PeriodsType;
use jamesiarmes\PhpEws\Type\TransitionType;

// Array types
use jamesiarmes\PhpEws\ArrayType\NonEmptyArrayOfBaseFolderIdsType;
use jamesiarmes\PhpEws\ArrayType\NonEmptyArrayOfAllItemsType;
use jamesiarmes\PhpEws\ArrayType\NonEmptyArrayOfPeriodsType;
use jamesiarmes\PhpEws\ArrayType\ArrayOfTransitionsGroupsType;
use jamesiarmes\PhpEws\ArrayType\ArrayOfTransitionsType;
use jamesiarmes\PhpEws\ArrayType\NonEmptyArrayOfTimeZoneIdType;
use jamesiarmes\PhpEws\ArrayType\NonEmptyArrayOfBaseItemIdsType;
use jamesiarmes\PhpEws\ArrayType\ArrayOfStringsType;

// Enumeration classes
use jamesiarmes\PhpEws\Enumeration\DefaultShapeNamesType;
use jamesiarmes\PhpEws\Enumeration\DistinguishedFolderIdNameType;
use jamesiarmes\PhpEws\Enumeration\ItemQueryTraversalType;
use jamesiarmes\PhpEws\Enumeration\BodyTypeResponseType;
use jamesiarmes\PhpEws\Enumeration\BodyTypeType;
use jamesiarmes\PhpEws\Enumeration\ItemClassType;
use jamesiarmes\PhpEws\Enumeration\SensitivityChoicesType;
use jamesiarmes\PhpEws\Enumeration\ImportanceChoicesType;
use jamesiarmes\PhpEws\Enumeration\CalendarItemCreateOrDeleteOperationType;
use jamesiarmes\PhpEws\Enumeration\KindType as KindTypeEnum;

class EwsUnavailableException extends \Exception {}

class EwsAuthenticationException extends \Exception {}

class Ews
{
    protected $instance;

    public function __construct()
    {
        $server = DBconfig::get('ews::config.server');
        $username = DBconfig::get('ews::config.username');
        $password = DBconfig::get('ews::config.password');
        $version = DBconfig::get('ews::config.version');
        
        if (empty($server) || empty($username) || empty($password)) {
            Log::error('EWS credentials missing: Please set ews::config.server, username, and password in your configuration.');
            throw new EwsUnavailableException('EWS credentials missing: Please set ews::config.server, username, and password in your configuration.');
        }
        
        try {
            $this->instance = new Client($server, $username, $password, $version);
        } catch (\Exception $e) {
            Log::warning('EWS Client initialization failed', [
                'error' => $e->getMessage(),
                'server' => $server,
                'username' => $username,
                'version' => $version
            ]);
            throw new EwsUnavailableException('EWS Client initialization failed: ' . $e->getMessage());
        }
    }

    public function getCalendarEntries($start_date = '2013-07-31 00:00:00', $end_date = '2013-07-31 23:00:00')
    {
        try {
            $ews = $this->instance;

            $start_date = Carbon::parse($start_date);
            $end_date = Carbon::parse($end_date);

            // Set init class
            $request = new FindItemType();
            $request->Traversal = ItemQueryTraversalType::SHALLOW;
            $request->ItemShape = new ItemResponseShapeType();
            $request->ItemShape->BaseShape = DefaultShapeNamesType::DEFAULT_PROPERTIES;

            // Define the timeframe to load calendar items
            $request->CalendarView = new CalendarViewType();
            $request->CalendarView->StartDate = $start_date->toRfc3339String();
            $request->CalendarView->EndDate = $end_date->toRfc3339String();

            // Only look in the "calendars folder"
            $request->ParentFolderIds = new NonEmptyArrayOfBaseFolderIdsType();
            $request->ParentFolderIds->DistinguishedFolderId = new DistinguishedFolderIdType();
            $request->ParentFolderIds->DistinguishedFolderId->Id = DistinguishedFolderIdNameType::CALENDAR;

            // Send request with proper error handling
            $response = $ews->FindItem($request);

            $results = [];

            // Loop through each item if event(s) were found in the timeframe specified
            if ($response->ResponseMessages->FindItemResponseMessage->RootFolder->TotalItemsInView > 0) {
                $events = $response->ResponseMessages->FindItemResponseMessage->RootFolder->Items->CalendarItem;
                $results = $events;
            }

            return $results;
            
        } catch (\Exception $e) {
            // Handle specific authentication errors
            if (strpos($e->getMessage(), '401') !== false || strpos($e->getMessage(), 'Unauthorized') !== false) {
                Log::error('EWS Authentication Failed - Office 365 may have disabled basic authentication', [
                    'error' => $e->getMessage(),
                    'suggestion' => 'Consider migrating to OAuth2/Modern Authentication for Office 365',
                    'username' => DBconfig::get('ews::config.username'),
                    'server' => DBconfig::get('ews::config.server')
                ]);
                
                throw new EwsAuthenticationException(
                    'EWS Authentication Failed: Office 365 has likely disabled basic authentication. ' .
                    'Please contact system administrator to configure OAuth2/Modern Authentication.',
                    401
                );
            }
            
            // Handle other EWS errors
            Log::error('EWS Calendar Request Failed', [
                'error' => $e->getMessage(),
                'start_date' => $start_date,
                'end_date' => $end_date
            ]);
            
            throw new EwsUnavailableException('EWS service temporarily unavailable: ' . $e->getMessage());
        }
    }

    public function getTimeZones()
    {
        try {
            $ews = $this->instance;

            $request = new GetServerTimeZonesType();
            $request->Ids = new NonEmptyArrayOfTimeZoneIdType();
            $request->Ids->Id[] = 'GMT Standard Time';

            $response = $ews->GetServerTimeZones($request);
            return $response->ResponseMessages->GetServerTimeZonesResponseMessage->TimeZoneDefinitions->TimeZoneDefinition;
        } catch (\Exception $e) {
            // Log the error and return a default timezone
            Log::error('EWS GetTimeZones failed: ' . $e->getMessage());
            
            // Return a basic timezone object to prevent further errors
            $defaultTimezone = new \stdClass();
            $defaultTimezone->Id = 'GMT Standard Time';
            $defaultTimezone->Periods = new \stdClass();
            $defaultTimezone->Periods->Period = [];
            $defaultTimezone->TransitionsGroups = new \stdClass();
            $defaultTimezone->TransitionsGroups->TransitionsGroup = [];
            $defaultTimezone->Transitions = new \stdClass();
            $defaultTimezone->Transitions->Transition = new \stdClass();
            $defaultTimezone->Transitions->Transition->To = new \stdClass();
            $defaultTimezone->Transitions->Transition->To->_ = 'GMT';
            $defaultTimezone->Transitions->Transition->To->Kind = 'Period';
            
            return $defaultTimezone;
        }
    }

    public function createEvent($title, $start_date, $end_date, $description, $category, $attendee_email)
    {
        try {
            $ews = $this->instance;

            $timezone_definition = $this->getTimeZones();

            // Start building the request.
            $request = new CreateItemType();
            $request->Items = new NonEmptyArrayOfAllItemsType();
            $request->Items->CalendarItem = new CalendarItemType();

            // Set the subject.
            $request->Items->CalendarItem->Subject = $title;

            // Set the start and end times. For Exchange 2007, you need to include the timezone offset.
            // For Exchange 2010, you should set the StartTimeZone and EndTimeZone properties. See below for
            // an example.
            $request->Items->CalendarItem->Start = Carbon::parse($start_date)->toRfc3339String();
            $request->Items->CalendarItem->End = Carbon::parse($end_date)->toRfc3339String(); // format('Y-m-d\TH:i:s')

            // Build the timezone definition and set it as the StartTimeZone.
            $request->Items->CalendarItem->StartTimeZone = new TimeZoneDefinitionType();
            $request->Items->CalendarItem->StartTimeZone->Id = $timezone_definition->Id;
            $request->Items->CalendarItem->StartTimeZone->Periods = new NonEmptyArrayOfPeriodsType();

            if (isset($timezone_definition->Periods->Period) && is_array($timezone_definition->Periods->Period)) {
                foreach ($timezone_definition->Periods->Period as $v) {
                    $period = new PeriodType();
                    $period->Bias = $v->Bias ?? 0;
                    $period->Name = $v->Name ?? 'Standard';
                    $period->Id = $v->Id ?? 'Standard';
                    $request->Items->CalendarItem->StartTimeZone->Periods->Period[] = $period;
                }
            }

            $request->Items->CalendarItem->StartTimeZone->TransitionsGroups = new ArrayOfTransitionsGroupsType();
            $request->Items->CalendarItem->StartTimeZone->TransitionsGroups->TransitionsGroup = [];

            // Only process transitions if they exist
            if (isset($timezone_definition->TransitionsGroups->TransitionsGroup)) {
                if (is_array($timezone_definition->TransitionsGroups->TransitionsGroup)) {
                    foreach ($timezone_definition->TransitionsGroups->TransitionsGroup as $v) {
                        $value = $timezone_definition->TransitionsGroups->TransitionsGroup;
                        $group = new ArrayOfTransitionsGroupsType();
                        $group->Id = $value->Id ?? 'Standard';

                        if (isset($value->RecurringDayTransition)) {
                            foreach ($value->RecurringDayTransition as $v) {
                                $transition = new RecurringDayTransitionType();
                                $transition->To = new TransitionTargetType();
                                $transition->To->_ = $v->To->_ ?? 'Standard';
                                $transition->To->Kind = new KindType();
                                $transition->To->Kind->_ = KindTypeEnum::PERIOD;
                                $transition->TimeOffset = $v->TimeOffset ?? '00:00:00';
                                $transition->Month = $v->Month ?? 1;
                                $transition->Occurrence = new OccurrenceType();
                                $transition->Occurrence->_ = $v->Occurrence ?? 1;
                                $transition->DayOfWeek = new DayOfWeekType();
                                $transition->DayOfWeek->_ = $v->DayOfWeek ?? 'Sunday';
                                $group->RecurringDayTransition[] = $transition;
                            }
                        }

                        $request->Items->CalendarItem->StartTimeZone->TransitionsGroups->TransitionsGroup[] = $group;
                    }
                } else {
                    $value = $timezone_definition->TransitionsGroups->TransitionsGroup;
                    $group = new ArrayOfTransitionsGroupsType();
                    $group->Id = $value->Id ?? 'Standard';

                    if (isset($value->RecurringDayTransition)) {
                        foreach ($value->RecurringDayTransition as $v) {
                            $transition = new RecurringDayTransitionType();
                            $transition->To = new TransitionTargetType();
                            $transition->To->_ = $v->To->_ ?? 'Standard';
                            $transition->To->Kind = new KindType();
                            $transition->To->Kind->_ = KindTypeEnum::PERIOD;
                            $transition->TimeOffset = $v->TimeOffset ?? '00:00:00';
                            $transition->Month = $v->Month ?? 1;
                            $transition->Occurrence = new OccurrenceType();
                            $transition->Occurrence->_ = $v->Occurrence ?? 1;
                            $transition->DayOfWeek = new DayOfWeekType();
                            $transition->DayOfWeek->_ = $v->DayOfWeek ?? 'Sunday';
                            $group->RecurringDayTransition[] = $transition;
                        }
                    }

                    $request->Items->CalendarItem->StartTimeZone->TransitionsGroups->TransitionsGroup[] = $group;
                }
            }

            $request->Items->CalendarItem->StartTimeZone->Transitions = new ArrayOfTransitionsType();
            $request->Items->CalendarItem->StartTimeZone->Transitions->Transition = new TransitionType();
            $request->Items->CalendarItem->StartTimeZone->Transitions->Transition->To = new TransitionTargetType();
            $request->Items->CalendarItem->StartTimeZone->Transitions->Transition->To->_ = $timezone_definition->Transitions->Transition->To->_ ?? 'Standard';
            $request->Items->CalendarItem->StartTimeZone->Transitions->Transition->To->Kind = new KindType();
            $request->Items->CalendarItem->StartTimeZone->Transitions->Transition->To->Kind = $timezone_definition->Transitions->Transition->To->Kind ?? 'Period';

            // The EndTimeZone is the same so just clone the StartTimeZone.
            $request->Items->CalendarItem->EndTimeZone = clone $request->Items->CalendarItem->StartTimeZone;

            // Set no reminders
            $request->Items->CalendarItem->ReminderIsSet = false;

            // Build the body.
            $request->Items->CalendarItem->Body = new BodyType();
            $request->Items->CalendarItem->Body->BodyType = BodyTypeType::HTML;
            $request->Items->CalendarItem->Body->_ = $description;

            // Set the item class type (not required).
            $request->Items->CalendarItem->ItemClass = new ItemClassType();
            $request->Items->CalendarItem->ItemClass->_ = ItemClassType::APPOINTMENT;

            // Set the sensativity of the event (defaults to normal).
            $request->Items->CalendarItem->Sensitivity = new SensitivityChoicesType();
            $request->Items->CalendarItem->Sensitivity->_ = SensitivityChoicesType::NORMAL;

            // Add some categories to the event.
            $request->Items->CalendarItem->Categories = new ArrayOfStringsType();
            $request->Items->CalendarItem->Categories->String = [$category, 'ArchDB', 'Onsite'];

            // Set the importance of the event.
            $request->Items->CalendarItem->Importance = new ImportanceChoicesType();
            $request->Items->CalendarItem->Importance->_ = ImportanceChoicesType::NORMAL;

            // Send invitation to the user
            if ($attendee_email) {
                //$request->SendMeetingInvitations = CalendarItemCreateOrDeleteOperationType::SEND_TO_NONE;
                $request->SendMeetingInvitations = CalendarItemCreateOrDeleteOperationType::SEND_ONLY_TO_ALL;
                $request->Items->CalendarItem->RequiredAttendees = new NonEmptyArrayOfAllItemsType();
                $request->Items->CalendarItem->RequiredAttendees->Attendee = new SingleRecipientType();
                $request->Items->CalendarItem->RequiredAttendees->Attendee->Mailbox = new EmailAddressType();
                $request->Items->CalendarItem->RequiredAttendees->Attendee->Mailbox->EmailAddress = $attendee_email;
            }

            $response = $ews->CreateItem($request);

            return $response;
        } catch (\Exception $e) {
            Log::error('EWS createEvent failed: ' . $e->getMessage());
            
            // Return a mock response to prevent application crashes
            $mockResponse = new \stdClass();
            $mockResponse->ResponseMessages = new \stdClass();
            $mockResponse->ResponseMessages->CreateItemResponseMessage = new \stdClass();
            $mockResponse->ResponseMessages->CreateItemResponseMessage->ResponseClass = 'Error';
            $mockResponse->ResponseMessages->CreateItemResponseMessage->MessageText = 'EWS service unavailable: ' . $e->getMessage();
            
            return $mockResponse;
        }
    }

    public function getItem($item_id)
    {
        $ews = $this->instance;

        // Set init class
        $request = new GetItemType();

        $request->ItemShape = new ItemResponseShapeType();
        $request->ItemShape->BaseShape = DefaultShapeNamesType::ALL_PROPERTIES;

        $request->ItemIds = new NonEmptyArrayOfBaseItemIdsType();
        $request->ItemIds->ItemId = new ItemIdType();
        $request->ItemIds->ItemId->Id = $item_id;

        $response = $ews->GetItem($request);

        // Loop through each item if more than one event was found in the timeframe specified
        if ($response->ResponseMessages->GetItemResponseMessage->ResponseClass == 'Success') {
            $event = $response->ResponseMessages->GetItemResponseMessage->Items->CalendarItem;

            // Return the event
            return $event;
        } else {
            // No items returned
            return false;
        }
    }

    public function updateEvent($item_id, $change_key, $start_date, $end_date, $email = null, $title = null)
    {
        $ews = $this->instance;

        $request = new UpdateItemType();
        $request->ConflictResolution = 'AlwaysOverwrite';
        $request->SendMeetingInvitationsOrCancellations = 'SendOnlyToAll'; // SendToNone,SendOnlyToAll, SendOnlyToChanged, SendToAllAndSaveCopy
        $request->ItemChanges = [];

        $change = new ItemChangeType();
        $change->ItemId = new ItemIdType();
        $change->ItemId->Id = $item_id;
        $change->ItemId->ChangeKey = $change_key;

        $start_date = Carbon::parse($start_date);
        $end_date = Carbon::parse($end_date);

        //Update Start Property
        $field = new SetItemFieldType();
        $field->FieldURI = new PathToUnindexedFieldType();
        $field->FieldURI->FieldURI = 'calendar:Start';
        $field->CalendarItem = new CalendarItemType();
        $field->CalendarItem->Start = $start_date->toRfc3339String();
        $change->Updates->SetItemField[] = $field;

        //Update End Property
        $field = new SetItemFieldType();
        $field->FieldURI = new PathToUnindexedFieldType();
        $field->FieldURI->FieldURI = 'calendar:End';
        $field->CalendarItem = new CalendarItemType();
        $field->CalendarItem->End = $end_date->toRfc3339String();
        $change->Updates->SetItemField[] = $field;

        if ($email) {
            //Update End Property
            $field = new SetItemFieldType();
            $field->FieldURI = new PathToUnindexedFieldType();
            $field->FieldURI->FieldURI = 'calendar:RequiredAttendees';
            $field->CalendarItem = new CalendarItemType();
            $field->CalendarItem->RequiredAttendees = new NonEmptyArrayOfAllItemsType();

            $field->CalendarItem->RequiredAttendees->Attendee = new SingleRecipientType();

            $field->CalendarItem->RequiredAttendees->Attendee->Mailbox = new EmailAddressType();
            $field->CalendarItem->RequiredAttendees->Attendee->Mailbox->EmailAddress = $email;

            $field->CalendarItem->RequiredAttendees->Attendee->Mailbox->EmailAddress = $email;
            $field->CalendarItem->RequiredAttendees->Attendee->Mailbox->RoutingType = 'SMTP';

            $change->Updates->SetItemField[] = $field;
        }

        if ($title) {
            //Update End Property
            $field = new SetItemFieldType();
            $field->FieldURI = new PathToUnindexedFieldType();
            $field->FieldURI->FieldURI = 'item:Subject';
            $field->CalendarItem = new CalendarItemType();
            $field->CalendarItem->Subject = $title;
            $change->Updates->SetItemField[] = $field;
        }

        $request->ItemChanges[] = $change;

        $response = $ews->UpdateItem($request);

        return $response;
    }

    public function editTitle($item_id, $change_key, $title)
    {
        $ews = $this->instance;

        $request = new UpdateItemType();
        $request->ConflictResolution = 'AlwaysOverwrite';
        $request->SendMeetingInvitationsOrCancellations = 'SendOnlyToAll'; // SendToNone, SendOnlyToAll, SendOnlyToChanged, SendToAllAndSaveCopy
        $request->ItemChanges = [];

        $change = new ItemChangeType();
        $change->ItemId = new ItemIdType();
        $change->ItemId->Id = $item_id;
        $change->ItemId->ChangeKey = $change_key;

        //Update End Property
        $field = new SetItemFieldType();
        $field->FieldURI = new PathToUnindexedFieldType();
        $field->FieldURI->FieldURI = 'item:Subject';
        $field->CalendarItem = new CalendarItemType();
        $field->CalendarItem->Subject = $title;
        $change->Updates->SetItemField[] = $field;

        $request->ItemChanges[] = $change;

        $response = $ews->UpdateItem($request);

        return $response;
    }
}
