<?php defined('BASEPATH') or exit('No direct script access allowed');

/* ----------------------------------------------------------------------------
 * Easy!Appointments - Open Source Web Scheduler
 *
 * @package     EasyAppointments
 * @author      A.Tselegidis <alextselegidis@gmail.com>
 * @copyright   Copyright (c) 2013 - 2020, Alex Tselegidis
 * @license     https://opensource.org/licenses/GPL-3.0 - GPLv3
 * @link        https://easyappointments.org
 * @since       v1.5.0
 * ---------------------------------------------------------------------------- */

/**
 * Calendar controller.
 *
 * Handles calendar related operations.
 *
 * @package Controllers
 */
class Calendar extends EA_Controller {
    /**
     * @var array
     */
    protected $permissions;

    /**
     * Calendar constructor.
     */
    public function __construct()
    {
        parent::__construct();

        $this->load->model('appointments_model');
        $this->load->model('customers_model');
        $this->load->model('services_model');
        $this->load->model('providers_model');
        $this->load->model('roles_model');

        $this->load->library('accounts');
        $this->load->library('google_sync');
        $this->load->library('notifications');
        $this->load->library('synchronization');
        $this->load->library('timezones');

        $role_slug = session('role_slug');

        if ($role_slug)
        {
            $this->permissions = $this->roles_model->get_permissions_by_slug($role_slug);
        }
    }

    /**
     * Display the main backend page.
     *
     * This method displays the main backend page. All login permission can view this page which displays a
     * calendar with the events of the selected provider or service. If a user has more privileges he will see more
     * menus at the top of the page.
     *
     * @param string $appointment_hash Appointment edit dialog will appear when the page loads (default '').
     */
    public function index(string $appointment_hash = '')
    {
        session(['dest_url' => site_url('backend/index' . (! empty($appointment_hash) ? '/' . $appointment_hash : ''))]);

        if (cannot('view', PRIV_APPOINTMENTS))
        {
            return;
        }

        $user_id = session('user_id');

        $role_slug = session('role_slug');

        $user = $this->users_model->find($user_id);

        $secretary_providers = [];

        if ($role_slug === DB_SLUG_SECRETARY)
        {
            $secretary = $this->secretaries_model->find(session('user_id'));

            $secretary_providers = $secretary['providers'];
        }

        $occurrences = $this->appointments_model->get(['hash' => $appointment_hash]);

        $edit_appointment = NULL;

        if ($appointment_hash !== '' && ! empty($occurrences))
        {
            $edit_appointment = $occurrences[0];
            
            $this->appointments_model->load($edit_appointment, ['customer']);
        }

        $this->load->view('pages/calendar', [
            'page_title' => lang('calendar'),
            'active_menu' => PRIV_APPOINTMENTS,
            'user_display_name' => $this->accounts->get_user_display_name($user_id),
            'timezones' => $this->timezones->to_array(),
            'privileges' => $this->roles_model->get_permissions_by_slug($role_slug),
            'calendar_view' => request('view', $user['settings']['calendar_view']),
            'available_providers' => $this->providers_model->get_available_providers(),
            'available_services' => $this->services_model->get_available_services(),
            'secretary_providers' => $secretary_providers,
            'edit_appointment' => $edit_appointment,
        ]);
    }

    /**
     * Render the calendar page and display the selected appointment.
     * 
     * This method will call the "index" callback to handle the page rendering.
     *
     * @param string $appointment_hash Appointment hash.
     */
    public function reschedule(string $appointment_hash)
    {
        $this->index($appointment_hash);
    }

    /**
     * Save appointment changes that are made from the backend calendar page.
     */
    public function ajax_save_appointment()
    {
        try
        {
            // Save customer changes to the database.
            $customer_data = request('customer_data');

            if ($customer_data)
            {
                $customer = json_decode($customer_data, TRUE);

                $required_permissions = ( ! isset($customer['id']))
                    ? $this->permissions[PRIV_CUSTOMERS]['add']
                    : $this->permissions[PRIV_CUSTOMERS]['edit'];

                if ($required_permissions == FALSE)
                {
                    throw new Exception('You do not have the required permissions for this task.');
                }

                $customer['id'] = $this->customers_model->save($customer);
            }

            // Save appointment changes to the database.
            $appointment_data = request('appointment_data');

            $manage_mode = ! empty($appointment_data['id']);

            if ($appointment_data)
            {
                $appointment = json_decode($appointment_data, TRUE);

                $required_permissions = ( ! isset($appointment['id']))
                    ? $this->permissions[PRIV_APPOINTMENTS]['add']
                    : $this->permissions[PRIV_APPOINTMENTS]['edit'];

                if ($required_permissions == FALSE)
                {
                    throw new Exception('You do not have the required permissions for this task.');
                }

                // If the appointment does not contain the customer record id, then it means that is going to be
                // inserted. 
                if ( ! isset($appointment['id_users_customer']))
                {
                    $appointment['id_users_customer'] = $customer['id'] ?? $customer_data['id'];
                }

                $appointment['id'] = $this->appointments_model->save($appointment);
            }

            if (empty($appointment['id']))
            {
                throw new RuntimeException('The appointment ID is not available.');
            }

            $appointment = $this->appointments_model->find($appointment['id']);
            $provider = $this->providers_model->find($appointment['id_users_provider']);
            $customer = $this->customers_model->find($appointment['id_users_customer']);
            $service = $this->services_model->find($appointment['id_services']);

            $settings = [
                'company_name' => setting('company_name'),
                'company_link' => setting('company_link'),
                'company_email' => setting('company_email'),
                'date_format' => setting('date_format'),
                'time_format' => setting('time_format')
            ];

            $this->synchronization->sync_appointment_saved($appointment, $service, $provider, $customer, $settings, $manage_mode);

            $this->notifications->notify_appointment_saved($appointment, $service, $provider, $customer, $settings, $manage_mode);

            json_response([
                'success' => TRUE,
            ]);
        }
        catch (Throwable $e)
        {
            json_exception($e);
        }
    }

    /**
     * Delete appointment from the database.
     *
     * This method deletes an existing appointment from the database. Once this action is finished it cannot be undone.
     * Notification emails are send to both provider and customer and the delete action is executed to the Google
     * Calendar account of the provider, if the "google_sync" setting is enabled.
     */
    public function ajax_delete_appointment()
    {
        try
        {
            if (cannot('delete', 'appointments'))
            {
                throw new Exception('You do not have the required permissions for this task.');
            }

            $appointment_id = request('appointment_id');

            if (empty($appointment_id))
            {
                throw new Exception('No appointment id provided.');
            }

            // Store appointment data for later use in this method.
            $appointment = $this->appointments_model->find($appointment_id);
            $provider = $this->providers_model->find($appointment['id_users_provider']);
            $customer = $this->customers_model->find($appointment['id_users_customer']);
            $service = $this->services_model->find($appointment['id_services']);

            $settings = [
                'company_name' => setting('company_name'),
                'company_email' => setting('company_email'),
                'company_link' => setting('company_link'),
                'date_format' => setting('date_format'),
                'time_format' => setting('time_format')
            ];

            // Delete appointment record from the database.
            $this->appointments_model->delete($appointment_id);

            $this->notifications->notify_appointment_deleted($appointment, $service, $provider, $customer, $settings);

            $this->synchronization->sync_appointment_deleted($appointment, $provider);

            json_response([
                'success' => TRUE,
            ]);
        }
        catch (Throwable $e)
        {
            json_exception($e);
        }
    }

    /**
     * Insert of update unavailable time period to database.
     */
    public function ajax_save_unavailable()
    {
        try
        {
            // Check privileges
            $unavailable = json_decode(request('unavailable'), TRUE);

            $required_permissions = ( ! isset($unavailable['id']))
                ? $this->permissions[PRIV_APPOINTMENTS]['add']
                : $this->permissions[PRIV_APPOINTMENTS]['edit'];

            if ( ! $required_permissions)
            {
                throw new Exception('You do not have the required permissions for this task.');
            }

            $provider = $this->providers_model->find($unavailable['id_users_provider']);

            // Add appointment
            $unavailable['id'] = $this->unavailabilities_model->save($unavailable);

            $unavailable = $this->unavailabilities_model->find($unavailable['id']); // fetch all inserted data

            // Google Sync
            try
            {
                $google_sync = $this->providers_model->get_setting($unavailable['id_users_provider'], 'google_sync');

                if ($google_sync)
                {
                    $google_token = json_decode($this->providers_model->get_setting($unavailable['id_users_provider'], 'google_token'));

                    $this->google_sync->refresh_token($google_token->refresh_token);

                    if ($unavailable['id_google_calendar'] == NULL)
                    {
                        $google_event = $this->google_sync->add_unavailable($provider, $unavailable);
                        $unavailable['id_google_calendar'] = $google_event->id;
                        $this->unavailabilities_model->save($unavailable);
                    }
                    else
                    {
                        $this->google_sync->update_unavailable($provider, $unavailable);
                    }
                }
            }
            catch (Throwable $e)
            {
                $warnings[] = $e;
            }

            json_response([
                'success' => TRUE,
                'warnings' => $warnings ?? []
            ]);
        }
        catch (Throwable $e)
        {
            json_exception($e);
        }
    }

    /**
     * Delete an unavailable time period from database.
     */
    public function ajax_delete_unavailable()
    {
        try
        {
            if ($this->permissions[PRIV_APPOINTMENTS]['delete'] == FALSE)
            {
                throw new Exception('You do not have the required permissions for this task.');
            }

            $unavailable_id = request('unavailable_id');

            $unavailable = $this->appointments_model->find($unavailable_id);

            $provider = $this->providers_model->find($unavailable['id_users_provider']);

            $this->appointments_model->delete($unavailable['id']);

            // Google Sync
            try
            {
                $google_sync = $this->providers_model->get_setting($provider['id'], 'google_sync');

                if ($google_sync == TRUE)
                {
                    $google_token = json_decode($this->providers_model->get_setting($provider['id'], 'google_token'));

                    $this->google_sync->refresh_token($google_token->refresh_token);

                    $this->google_sync->delete_unavailable($provider, $unavailable['id_google_calendar']);
                }
            }
            catch (Throwable $e)
            {
                $warnings[] = $e;
            }

            json_response([
                'success' => TRUE,
                'warnings' => $warnings ?? []
            ]);
        }
        catch (Throwable $e)
        {
            json_exception($e);
        }
    }

    /**
     * Insert of update working plan exceptions to database.
     */
    public function ajax_save_working_plan_exception()
    {
        try
        {
            $required_permissions = $this->permissions[PRIV_USERS]['edit'];

            if ( ! $required_permissions)
            {
                throw new Exception('You do not have the required permissions for this task.');
            }

            $date = request('date');

            $working_plan_exception = request('working_plan_exception');

            $provider_id = request('provider_id');

            $this->providers_model->save_working_plan_exception($provider_id, $date, $working_plan_exception);

            json_response([
                'success' => TRUE,
            ]);
        }
        catch (Throwable $e)
        {
            json_exception($e);
        }
    }

    /**
     * Delete a working plan exceptions time period to database.
     */
    public function ajax_delete_working_plan_exception()
    {
        try
        {
            $required_permissions = $this->permissions[PRIV_USERS]['edit'];

            if ( ! $required_permissions)
            {
                throw new Exception('You do not have the required permissions for this task.');
            }

            $date = request('date');

            $provider_id = request('provider_id');

            $this->providers_model->delete_working_plan_exception($provider_id, $date);

            json_response([
                'success' => TRUE
            ]);
        }
        catch (Throwable $e)
        {
            json_exception($e);
        }
    }

    /**
     * Get Calendar Events
     *
     * This method will return all the calendar events within a specified period.
     */
    public function ajax_get_calendar_events()
    {
        try
        {
            $start_date = request('startDate') . ' 00:00:00';

            $end_date = request('endDate') . ' 23:59:59';

            $response = [
                'appointments' => $this->appointments_model->get([
                    'is_unavailable' => FALSE,
                    'start_datetime >=' => $start_date,
                    'end_datetime <=' => $end_date
                ]),
                'unavailability_events' => $this->appointments_model->get([
                    'is_unavailable' => TRUE,
                    'start_datetime >=' => $start_date,
                    'end_datetime <=' => $end_date
                ])
            ];

            foreach ($response['appointments'] as &$appointment)
            {
                $appointment['provider'] = $this->providers_model->find($appointment['id_users_provider']);
                $appointment['service'] = $this->services_model->find($appointment['id_services']);
                $appointment['customer'] = $this->customers_model->find($appointment['id_users_customer']);
            }

            unset($appointment);

            $user_id = session('user_id');

            $role_slug = session('role_slug');

            // If the current user is a provider he must only see his own appointments.
            if ($role_slug === DB_SLUG_PROVIDER)
            {
                foreach ($response['appointments'] as $index => $appointment)
                {
                    if ((int)$appointment['id_users_provider'] !== (int)$user_id)
                    {
                        unset($response['appointments'][$index]);
                    }
                }

                foreach ($response['unavailability_events'] as $index => $unavailability_event)
                {
                    if ((int)$unavailability_event['id_users_provider'] !== (int)$user_id)
                    {
                        unset($response['unavailability_events'][$index]);
                    }
                }
            }

            // If the current user is a secretary he must only see the appointments of his providers.
            if ($role_slug === DB_SLUG_SECRETARY)
            {
                $providers = $this->secretaries_model->find($user_id)['providers'];

                foreach ($response['appointments'] as $index => $appointment)
                {
                    if ( ! in_array((int)$appointment['id_users_provider'], $providers))
                    {
                        unset($response['appointments'][$index]);
                    }
                }

                foreach ($response['unavailability_events'] as $index => $unavailability_event)
                {
                    if ( ! in_array((int)$unavailability_event['id_users_provider'], $providers))
                    {
                        unset($response['unavailability_events'][$index]);
                    }
                }
            }

            json_response($response);
        }
        catch (Throwable $e)
        {
            json_exception($e);
        }
    }

    /**
     * Get the registered appointments for the given date period and record.
     *
     * This method returns the database appointments and unavailable periods for the user selected date period and
     * record type (provider or service).
     */
    public function ajax_get_calendar_appointments()
    {
        try
        {
            if ($this->permissions[PRIV_APPOINTMENTS]['view'] == FALSE)
            {
                throw new Exception('You do not have the required permissions for this task.');
            }

            $filter_type = request('filter_type');

            if ( ! $filter_type)
            {
                json_response([
                    'appointments' => [],
                    'unavailables' => []
                ]);

                return;
            }

            if ($filter_type == FILTER_TYPE_PROVIDER)
            {
                $where_id = 'id_users_provider';
            }
            else
            {
                $where_id = 'id_services';
            }

            // Get appointments
            $record_id = $this->db->escape(request('record_id'));
            $start_date = $this->db->escape(request('start_date'));
            $end_date = $this->db->escape(date('Y-m-d', strtotime(request('end_date') . ' +1 day')));

            $where_clause = $where_id . ' = ' . $record_id . '
                AND ((start_datetime > ' . $start_date . ' AND start_datetime < ' . $end_date . ') 
                or (end_datetime > ' . $start_date . ' AND end_datetime < ' . $end_date . ') 
                or (start_datetime <= ' . $start_date . ' AND end_datetime >= ' . $end_date . ')) 
                AND is_unavailable = 0
            ';

            $response['appointments'] = $this->appointments_model->get($where_clause);

            foreach ($response['appointments'] as &$appointment)
            {
                $appointment['provider'] = $this->providers_model->find($appointment['id_users_provider']);
                $appointment['service'] = $this->services_model->find($appointment['id_services']);
                $appointment['customer'] = $this->customers_model->find($appointment['id_users_customer']);
            }

            // Get unavailable periods (only for provider).
            $response['unavailables'] = [];

            if ($filter_type == FILTER_TYPE_PROVIDER)
            {
                $where_clause = $where_id . ' = ' . $record_id . '
                    AND ((start_datetime > ' . $start_date . ' AND start_datetime < ' . $end_date . ') 
                    or (end_datetime > ' . $start_date . ' AND end_datetime < ' . $end_date . ') 
                    or (start_datetime <= ' . $start_date . ' AND end_datetime >= ' . $end_date . ')) 
                    AND is_unavailable = 1
                ';

                $response['unavailables'] = $this->appointments_model->get($where_clause);
            }

            foreach ($response['unavailables'] as &$unavailable)
            {
                $unavailable['provider'] = $this->providers_model->find($unavailable['id_users_provider']);
            }

            json_response($response);
        }
        catch (Throwable $e)
        {
            json_exception($e);
        }
    }
}