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

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

use Jsvrcek\ICS\CalendarExport;
use Jsvrcek\ICS\CalendarStream;
use Jsvrcek\ICS\Exception\CalendarEventException;
use Jsvrcek\ICS\Model\Calendar;
use Jsvrcek\ICS\Model\CalendarAlarm;
use Jsvrcek\ICS\Model\CalendarEvent;
use Jsvrcek\ICS\Model\Description\Location;
use Jsvrcek\ICS\Model\Relationship\Attendee;
use Jsvrcek\ICS\Model\Relationship\Organizer;
use Jsvrcek\ICS\Utility\Formatter;

/**
 * Ics file library.
 *
 * Handle ICS related functionality.
 *
 * An ICS file is a calendar file saved in a universal calendar format used by many email and calendar programs,
 * including Microsoft Outlook, Google Calendar, and Apple Calendar.
 *
 * @package Libraries
 */
class Ics_file
{
    /**
     * @var EA_Controller|CI_Controller
     */
    protected EA_Controller|CI_Controller $CI;

    /**
     * Availability constructor.
     */
    public function __construct()
    {
        $this->CI = &get_instance();
    }

    /**
     * Get the ICS file contents for the provided arguments.
     *
     * @param array $appointment Appointment data.
     * @param array $service Service data.
     * @param array $provider Provider data.
     * @param array $customer Customer data.
     *
     * @return string Returns the contents of the ICS file.
     *
     * @throws CalendarEventException
     * @throws Exception
     */
    public function get_stream(array $appointment, array $service, array $provider, array $customer): string
    {
        $appointment_timezone = new DateTimeZone($provider['timezone']);

        $appointment_start = new DateTime($appointment['start_datetime'], $appointment_timezone);

        $appointment_end = new DateTime($appointment['end_datetime'], $appointment_timezone);

        // Set up the event.
        $event = new CalendarEvent();

        $event
            ->setStart($appointment_start)
            ->setEnd($appointment_end)
            ->setStatus('CONFIRMED')
            ->setSummary($service['name'])
            ->setUid($appointment['id_caldav_calendar'] ?: $this->generate_uid($appointment['id']))
            ->setSequence($this->generate_sequence($appointment['update_datetime'] ?? null));

        if (!empty($service['location'])) {
            $location = new Location();
            $location->setName((string) $service['location']);
            $event->addLocation($location);
        }

        $meeting_link_content = [];

        if (!empty($appointment['meeting_link'])) {
            $meeting_link_content[] = '';
            $meeting_link_content[] = lang('meeting_link') . ': ' . $appointment['meeting_link'];
            $meeting_link_content[] = '';
        }

        $description = [
            ...$meeting_link_content,
            '',
            lang('provider'),
            '',
            lang('name') . ': ' . $provider['first_name'] . ' ' . $provider['last_name'],
            lang('email') . ': ' . $provider['email'],
            lang('phone_number') . ': ' . $provider['phone_number'],
            lang('address') . ': ' . $provider['address'],
            lang('city') . ': ' . $provider['city'],
            lang('zip_code') . ': ' . $provider['zip_code'],
            '',
            lang('customer'),
            '',
            lang('name') . ': ' . $customer['first_name'] . ' ' . $customer['last_name'],
            lang('email') . ': ' . $customer['email'],
            lang('phone_number') . ': ' . ($customer['phone_number'] ?? '-'),
            lang('address') . ': ' . $customer['address'],
            lang('city') . ': ' . $customer['city'],
            lang('zip_code') . ': ' . $customer['zip_code'],
            '',
            lang('notes'),
            '',
            $appointment['notes'],
        ];

        $event->setDescription(implode("\\n", $description));

        $attendee = new Attendee(new Formatter());

        if (isset($customer['email']) && !empty($customer['email'])) {
            $attendee->setValue($customer['email']);
        }

        // Add the event attendees.
        $attendee->setName($customer['first_name'] . ' ' . $customer['last_name']);
        $attendee
            ->setCalendarUserType('INDIVIDUAL')
            ->setRole('REQ-PARTICIPANT')
            ->setParticipationStatus('NEEDS-ACTION')
            ->setRsvp('TRUE');
        $event->addAttendee($attendee);

        $alarm = new CalendarAlarm();
        $alarm_datetime = clone $appointment_start;
        $alarm->setTrigger($alarm_datetime->modify('-15 minutes'));
        $alarm->setSummary('Alarm notification');
        $alarm->setDescription('This is an event reminder');
        $alarm->setAction('EMAIL');
        $alarm->addAttendee($attendee);
        $event->addAlarm($alarm);

        $alarm = new CalendarAlarm();
        $alarm_datetime = clone $appointment_start;
        $alarm->setTrigger($alarm_datetime->modify('-60 minutes'));
        $alarm->setSummary('Alarm notification');
        $alarm->setDescription('This is an event reminder');
        $alarm->setAction('EMAIL');
        $alarm->addAttendee($attendee);
        $event->addAlarm($alarm);

        $attendee = new Attendee(new Formatter());

        if (isset($provider['email']) && !empty($provider['email'])) {
            $attendee->setValue($provider['email']);
        }

        $attendee->setName($provider['first_name'] . ' ' . $provider['last_name']);
        $attendee
            ->setCalendarUserType('INDIVIDUAL')
            ->setRole('REQ-PARTICIPANT')
            ->setParticipationStatus('ACCEPTED')
            ->setRsvp('FALSE');
        $event->addAttendee($attendee);

        // Set the organizer.
        $organizer = new Organizer(new Formatter());

        $organizer->setValue($provider['email'])->setName($provider['first_name'] . ' ' . $provider['last_name']);

        $event->setOrganizer($organizer);

        // Setup calendar.
        $calendar = new Calendar();

        $calendar
            ->setProdId('-//EasyAppointments//Open Source Web Scheduler//EN')
            ->setMethod('REQUEST')
            ->setTimezone(new DateTimeZone($provider['timezone']))
            ->addEvent($event);

        // Setup exporter.
        $calendarExport = new CalendarExport(new CalendarStream(), new Formatter());
        $calendarExport->setDateTimeFormat('utc');
        $calendarExport->addCalendar($calendar);

        return $calendarExport->getStream();
    }

    /**
     * @throws DateInvalidTimeZoneException
     * @throws CalendarEventException
     * @throws Exception
     */
    public function get_unavailability_stream(array $unavailability, array $provider): string
    {
        $unavailability_timezone = new DateTimeZone($provider['timezone']);

        $unavailability_start = new DateTime($unavailability['start_datetime'], $unavailability_timezone);

        $unavailability_end = new DateTime($unavailability['end_datetime'], $unavailability_timezone);

        // Set up the event.
        $event = new CalendarEvent();

        $event
            ->setStart($unavailability_start)
            ->setEnd($unavailability_end)
            ->setStatus('CONFIRMED')
            ->setSummary('Unavailability')
            ->setUid($unavailability['id_caldav_calendar'] ?: $this->generate_uid($unavailability['id']))
            ->setSequence($this->generate_sequence($unavailability['update_datetime'] ?? null));

        $event->setDescription(str_replace("\n", "\\n", (string) $unavailability['notes']));

        // Set the organizer.
        $organizer = new Organizer(new Formatter());

        $organizer->setValue($provider['email'])->setName($provider['first_name'] . ' ' . $provider['last_name']);

        $event->setOrganizer($organizer);

        // Setup calendar.
        $calendar = new Calendar();

        $calendar
            ->setProdId('-//EasyAppointments//Open Source Web Scheduler//EN')
            ->setMethod('REQUEST')
            ->setTimezone(new DateTimeZone($provider['timezone']))
            ->addEvent($event);

        // Setup exporter.
        $calendarExport = new CalendarExport(new CalendarStream(), new Formatter());
        $calendarExport->setDateTimeFormat('utc');
        $calendarExport->addCalendar($calendar);

        return $calendarExport->getStream();
    }

    public function generate_uid(int $db_record_id): string
    {
        return 'ea-' . md5($db_record_id);
    }

    /**
     * Generate a SEQUENCE number for an ICS event derived from the last update timestamp.
     *
     * RFC 5545 requires SEQUENCE to be a non-negative integer that increases with each
     * significant revision. Using the Unix timestamp of update_datetime satisfies this:
     * it is always greater after a reschedule, with no additional DB columns needed.
     *
     * @param string|null $update_datetime The appointment's update_datetime value.
     *
     * @return int
     */
    public function generate_sequence(?string $update_datetime): int
    {
        if (empty($update_datetime)) {
            return 0;
        }

        return (int) (new DateTime($update_datetime))->getTimestamp();
    }
}
