<?php

namespace objects;

use classes\db;
use classes\gatewayapi;
use classes\object_property;
use Exception;
use traits\db_object_t;

class department_time_bookings_entries_o extends db
{
    use db_object_t;

    public object_property $department; // The department id
    public object_property $type; // The booking type id
    public object_property $start; // The start timestamp of the start time
    public object_property $end; // The end timestamp of the end time
    public object_property $note; // The note for the booking
    public object_property $reg; // The registration number plate (optional)
    public object_property $phone; // The phone number (optional))
    public object_property $phone_country_code; // The phone country code (optional)
    public object_property $deleted_at; // The deleted at timestamp


    public function structure(): void
    {
        $this->setTable('department_time_bookings_entries');
    }

    /**
     * Convert the object to an array
     * @throws Exception If the object is not selected
     * @throws Exception If the object is not found
     */
    public function asArray(): array
    {
        self::requireSelected();
        return [
            'id' => (int)$this->id,
            'department' => (int)$this->department->value(),
            'type' => (int)$this->type->value(),
            'start' => (string)$this->start->value(),
            'end' => (string)$this->end->value(),
            'note' => $this->note->value(),
            'reg' => $this->reg->value(),
            'phone' => (int)$this->phone->value(),
            'phone_country_code' => (int)$this->phone_country_code->value(),
        ];
    }

    /**
     * Add a new object to the database
     * @param int $department_id The department id
     * @param int $type_id The booking type id
     * @param int $start The start timestamp of the start time
     * @param int $end The end timestamp of the end time
     * @param int $phone_country_code The phone country code
     * @param int $phone The phone number
     * @param null|string $note The note for the booking
     * @param null|string $reg The registration number plate (optional)
     * @return void
     * @throws Exception If the object was not created successfully
     */
    public function add(
        int     $department_id,
        int     $type_id,
        string  $start,
        string  $end,
        int     $phone_country_code,
        int     $phone,
        ?string $note = null,
        ?string $reg = null,
        ?array  $addons = null,
    ): void
    {
        self::preChecks(...func_get_args());
        $tmp_id = self::add_object([
            'department' => (int)$department_id,
            'type' => (int)$type_id,
            'start' => $start,
            'end' => $end,
            'note' => $note,
            'reg' => $reg,
            'phone' => $phone,
            'phone_country_code' => $phone_country_code,
            'addons' => $addons,
        ]);
        $this->id = $tmp_id;
        self::getObjectProperties();
        self::objectChanged();
        // Notify the parties involved
        self::notifyBooking(true);
    }

    /**
     * Pre-checks for the add method
     * @param int $department_id The department id
     * @param int $type_id The booking type id
     * @param string $start The start timestamp of the start time
     * @param string $end The end timestamp of the end time
     * @param int $phone_country_code The phone country code
     * @param int $phone The phone number
     * @param null|string $note The note for the booking
     * @param null|string $reg The registration number plate (optional)
     * @return void
     * @throws Exception
     */
    public static function preChecks(
        int     $department_id,
        int     $type_id,
        string  $start,
        string  $end,
        int     $phone_country_code,
        int     $phone,
        ?string $note = null,
        ?string $reg = null,
        ?array  $addons = null,
    ): void
    {
        // Check if the type_id exists
        $type = new department_time_bookings_types_o();
        $type->select($type_id);
        $type->requireSelected();
        // Check if the type_id belongs to the department
        if ((int)$type->department->value() !== (int)$department_id) {
            throw new Exception('Type ID does not belong to the department');
        }
        // Check if the time slot is during the opening hours
        $opening_hours = new department_time_bookings_opening_hours_o();
        $opening_hours->selectByDepartment((int)$department_id);
        if (!$opening_hours->isOpen($start, $end)) {
            throw new Exception('The time slot is not during the opening hours.');
        }
        // Make sure the start time is before the end time
        if ($start >= $end) {
            throw new Exception('The start time must be before the end time.');
        }
        // Check if the time slot is already booked
        $bookings = new department_time_bookings_entries_o();
        $bookings_overlapping = $bookings->getBookingsByDepartmentAndTime((int)$department_id, $start, $end);
        if (count($bookings_overlapping) > 0) {
            throw new Exception('The time slot is already booked');
        }
        // Validate phone number
        if (!is_numeric($phone) || $phone <= 0) {
            throw new Exception('Invalid phone number');
        }
        // Validate phone country code
        if (!is_numeric($phone_country_code) || $phone_country_code <= 0) {
            throw new Exception('Invalid phone country code');
        }
    }

    private function getBookingsByDepartmentAndTime(int $department_id, string $start, string $end): array
    {
        global $db;
        $query = "SELECT * FROM {$this->table} WHERE department = ? AND start < ? AND end > ?";
        $stmt = $db->prepare($query);
        $stmt->bind_param('iss', $department_id, $end, $start);
        $stmt->execute();
        $result = $stmt->get_result();
        $bookings = [];
        while ($row = $result->fetch_assoc()) {
            $bookings[] = $row;
        }
        $stmt->close();
        return $bookings;
    }

    public function getObjectProperties(): void
    {
        $this->department = new object_property($this->table, $this->id, 'department', 'int', true);
        $this->type = new object_property($this->table, $this->id, 'type', 'int', true);
        $this->start = new object_property($this->table, $this->id, 'start', 'int', true);
        $this->end = new object_property($this->table, $this->id, 'end', 'int', true);
        $this->note = new object_property($this->table, $this->id, 'note', 'string', true);
        $this->reg = new object_property($this->table, $this->id, 'reg', 'string', true);
        $this->phone = new object_property($this->table, $this->id, 'phone', 'int', true);
        $this->phone_country_code = new object_property($this->table, $this->id, 'phone_country_code', 'int', true);
        $this->deleted_at = new object_property($this->table, $this->id, 'deleted_at', 'int', true);
    }

    public function objectChanged(): void
    {
        //TODO: Add cache invalidation
    }

    /**
     * Notify the parties involved
     * @param bool $is_new_booking True if the booking is new, false if it is an update
     * @return void
     * @throws Exception If the object is not selected
     * @throws Exception If the department is not found
     */
    public function notifyBooking(bool $is_new_booking): void
    {
        self::requireSelected();
        $department = new departments_o();
        $department->select((int)$this->department->value());
        $gateway = new gatewayapi();
        if ($gateway->isEnabled()) {
            // Send the message to the department
            $gateway->send(
                [...$department->notificationSmsPhoneNumbers()],
                ($is_new_booking
                    ? self::generateNewBookingMessage(true)
                    : self::generateUpdateBookingMessage(true)),
                true,
            );
            //print_r($department->notificationSmsPhoneNumbers());
            // Get the phone numbers of the customer
            $customer_phone_numbers = [];
            $customer_phone_numbers[] = '' . $this->phone_country_code->value() . $this->phone->value();
            //print_r($customer_phone_numbers);
            // Send the message to the customer
            $gateway->send(
                [...$customer_phone_numbers],
                ($is_new_booking
                    ? $this->generateNewBookingMessage(false)
                    : $this->generateUpdateBookingMessage(false)),
                true,
            );
        }
    }

    /**
     * Generate the message for a new booking
     * @param bool $is_to_department True if the message is to the department, false if it is to the customer
     * @return string The message
     * @throws Exception If the object is not selected
     * @throws Exception If the department is not found
     */
    public function generateNewBookingMessage(bool $is_to_department = false): string
    {
        self::requireSelected();
        $department = new departments_o();
        $department->select((int)$this->department->value());
        $type = new department_time_bookings_types_o();
        $type->select((int)$this->type->value());

        $message = '';
        if ($is_to_department) {
            $message .= self::addNewline('Din afdeling har modtaget en ny booking');
        } else {
            $message .= self::addNewline('Bookingbekræftelse');
        }
        return $this->generateBookingMessageContent($department, $message, $type, $is_to_department);
    }

    private function addNewline(string $message): string
    {
        return $message . "\n";
    }

    /**
     * @param departments_o $department
     * @param string $message
     * @param department_time_bookings_types_o $type
     * @param bool $is_to_department
     * @return string
     * @throws Exception
     */
    public function generateBookingMessageContent(departments_o $department, string $message, department_time_bookings_types_o $type, bool $is_to_department): string
    {
        self::requireSelected();
        $message .= self::addNewline('Afdeling: ' . $department->name->value());
        $message .= self::addNewline('Tidspunkt: ' . (string)$this->start->value());
        $message .= self::addNewline('Type: ' . (string)$type->getName());
        $registration_number = $this->reg->value();
        if (!empty($registration_number)) {
            $message .= self::addNewline('Registreringsnummer: ' . $registration_number);
        }
        $message .= self::addNewline('Telefonnummer: +' . $this->phone_country_code->value() . $this->phone->value());
        if (!empty($this->note->value())) {
            $message .= self::addNewline('Note: ' . $this->note->value());
        }
        if (!$is_to_department) {
            $message .= self::addNewline('For mere information, kontakt venligst afdelingen.');
        }
        $message .= 'Denne besked er genereret automatisk og kan ikke besvares.';
        return $message;
    }

    /**
     * Generate the message for an updated booking
     * @param bool $is_to_department True if the message is to the department, false if it is to the customer
     * @return string The message
     * @throws Exception If the object is not selected
     * @throws Exception If the department is not found
     */
    public function generateUpdateBookingMessage(bool $is_to_department = false): string
    {
        self::requireSelected();
        $department = new departments_o();
        $department->select((int)$this->department->value());
        $type = new department_time_bookings_types_o();
        $type->select((int)$this->type->value());
        $message = $is_to_department
            ? self::addNewline('Din afdeling har modtaget en opdatering på en booking')
            : self::addNewline('Du har modtaget en opdatering på en booking');
        return $this->generateBookingMessageContent($department, $message, $type, $is_to_department);
    }
}