<?php

namespace objects;

use attachments\helpers\attachment_content;
use classes\db;
use classes\email;
use classes\gatewayapi;
use classes\object_property;
use classes\pdf_generator;
use classes\slack;
use Exception;
use traits\db_object_t;

class order_bookings_o extends db
{
    use db_object_t;

    public object_property $customer_number;
    public object_property $department;
    public object_property $reg_1;
    public object_property $reg_2;
    public object_property $reg_3;
    public object_property $datetime;
    public object_property $note;
    public object_property $reference;
    public object_property $po;
    public object_property $pickup;
    public object_property $items;
    public object_property $order_id;
    public object_property $created_at;
    public object_property $updated_at;
    public object_property $deleted_at;

    /**
     * @throws Exception
     */
    public function sendWashCertificateToCustomer(): void
    {
        self::requireSelected();
        // Get the customer from the booking
        $customer = (new users_o())->getUserByCustomerNumber((int)$this->customer_number->value());
        if (!$customer->exists()) {
            return;
        }
        // Check if the customer wants email notifications
        if (!$customer->wantsEmailNotifications()) {
            return;
        }
        // Get the email
        $customer_email = !empty($customer->wash_certificate_email->value())
            ? $customer->wash_certificate_email->value()
            : $customer->email->value();
        if (empty($customer_email)) {
            return;
        }
        // Send wash certificate email
        $email = new email();
        $email->sendWashCertificateEmailToCustomer($this);
    }


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

    /**
     * Add an object
     * @param array $data The additional data of the object (e.g. ["key" => "value"])
     * @return void
     * @throws Exception If the object was not created successfully
     */
    public function add(
        array $data,
    ): void
    {
        global /** @var db $db */
        $db;
        // Add the object to the database
        $new_id = self::add_object($data);
        self::select($new_id);
        $this->onAfterNewBooking();
    }

    /**
     * @throws Exception
     */
    public function onAfterNewBooking(): void
    {
        self::requireSelected();
        $this->notifyNewBooking(); // Notify the department about the new booking
    }

    /**
     * Notify the department about a new booking
     * @throws Exception If the object is not selected
     * @throws ClientExceptionInterface If the request to the API fails
     */
    public function notifyNewBooking(): void
    {
        self::requireSelected();
        $department_notification = true;
        $customer_notification = true;
        /**
         * Get the department, branding and customer
         */
        // Get the department from the booking
        $department = (new departments_o())->select((int)$this->department->value());
        if (!$department->exists()) {
            throw new Exception('Department not found');
        }
        // Get the branding from the department
        $branding = (new branding_o())->select((int)$department->branding->value());
        if (!$branding->exists()) {
            throw new Exception('Branding not found');
        }
        // Get the customer from the booking
        $customer = (new users_o())->getUserByCustomerNumber((int)$this->customer_number->value());
        $department_array = $department->asArray();
        $customer_array = $customer->asArray();
        $message = "";
        $message .= "Ny booking fra {$customer->getCustomerName($customer_array['customer_number'])} (Kundenr: {$customer_array['customer_number']}, Bookingnr: {$this->id})\n";
        $message .= "Dato: " . date('d-m-Y H:i', strtotime($this->datetime->value())) . "\n";
        $message .= "Køretøj: " . $this->reg_1->value() . (!empty($this->reg_2->value()) ? ", " . $this->reg_2->value() : "") . (
            !empty($this->reg_3->value()) ? ", " . $this->reg_3->value() : ""
        ) ."\n";
        // If the booking has a note, add it to the message
        if (!empty($this->note->value())) $message .= "Note: " . $this->note->value() . "\n";
        // If the booking has items, add them to the message
        if (!empty($this->items->value())) {
            $items = array_map(function ($item) {
                // Get the product name
                $product = (new products_o())->select((int)$item['id']);
                if (!$product->exists()) {
                    return null;
                }
                $item['name'] = $product->name->value();
                if (!isset($item['quantity'])) {
                    $item['quantity'] = 1;
                }
                return "- " . $item['quantity'] . " x " . $item['name'];
            }, $this->items->value());
            $message .= "Ydelser:\n" . implode("\n", array_filter($items)) . "\n";
        }
        // If the booking has a reference, add it to the message
        if (!empty($this->reference->value())) $message .= "Reference: " . $this->reference->value() . "\n";
        // If the booking has a PO, add it to the message
        if (!empty($this->po->value())) $message .= "PO: " . $this->po->value() . "\n";
        if ($this->pickup->value()) $message .= "Afhentning: Ja\n"; else $message .= "Afhentning: Nej\n";
        // If the booking has a note, add it to the message
        if (!empty($this->note->value())) $message .= "Note: " . $this->note->value() . "\n";
        // Add the branding name
        $message = "*" . $branding->name->value() . "*\n" . $message;
        /**
         * Department booking notification
         */
        if ($department_notification) {
            // Define delivery methods
            $deliverSlack = (boolean)!empty($department->slack_webhook->value());
            $deliverSMS = true;
            // Check if the department has a slack webhook
            if ($deliverSlack) {
                // Construct email
                $message = "*" . $branding->name->value() . "*\n";
                $message .= "Ny booking fra *" . $customer->getCustomerName($customer_array['customer_number']) . "* (Kundenr: " . $customer_array['customer_number'] . ", Bookingnr: " . $this->id . ")\n";
                $message .= "Dato: " . date('d-m-Y H:i', strtotime($this->datetime->value())) . "\n";
                $message .= "Køretøj: " . $this->reg_1->value() . (!empty($this->reg_2->value()) ? ", " . $this->reg_2->value() : "") . (
                    !empty($this->reg_3->value()) ? ", " . $this->reg_3->value() : ""
                ) ."\n";
                // If the booking has a note, add it to the message
                if (!empty($this->note->value())) {
                    $message .= "Note: " . $this->note->value() . "\n";
                }
                // If the booking has items, add them to the message
                $items = array_map(function ($item) {
                    // Get the product name
                    $product = (new products_o())->select((int)$item['id']);
                    if (!$product->exists()) {
                        return null;
                    }
                    $item['name'] = $product->name->value();
                    if (!isset($item['quantity'])) {
                        $item['quantity'] = 1;
                    }
                    return "- " . $item['quantity'] . " x " . $item['name'];
                }, $this->items->value());
                if (!empty($items)) {
                    $message .= "Ydelser:\n" . implode("\n", array_filter($items)) . "\n";
                }
                // Send a notification to the department
                $slack = new slack();
                $slack->send_department_booking_notification($department->id, $message);
            }

            if ($deliverSMS) {
                // Send an SMS notification to the department
                $gatewayapi = new gatewayapi();
                try {
                    $gatewayapi->send(
                        [
                            // Add all the phone numbers from the department
                            ...$department->notificationSmsPhoneNumbers()
                        ],
                        $message,
                        true
                    );
                } catch (Exception $e) {
                    // Log the error
                    $logs = new logs_o();
                    $logs->add('gatewayapi', 0, 3, 0, 'SEND_DEPARTMENT_BOOKING_NOTIFICATION', $e->getMessage());
                }
            }
        }
        /**
         * Customer booking confirmation
         */
        if ($customer_notification) {
            // Check if the customer has a phone number
            $country_code = $customer->phone_country_code->value();
            $customer_phone = $customer->phone->value();
            $customer_email = $customer->getWashCertificateEmail();
            if (!$customer_email) {
                $customer_email = $customer->email->value();
            }
            if (empty($customer_phone) && empty($customer_email)) {
                return;
            }
            if (!empty($customer_phone) && $customer->wantsSmsNotifications()) {
                // Send an SMS confirmation to the customer
                $gatewayapi = new gatewayapi();
                $message = "Tak for din booking hos " . $branding->name->value() . ". Dit bookingnummer er " . $this->id . ". "
                    . "\nDato: " . date('d-m-Y', strtotime($this->datetime->value())) . "."
                    . "\nKøretøj: " . $this->reg_1->value() . (!empty($this->reg_2->value()) ? ", " . $this->reg_2->value() : "") . (
                        !empty($this->reg_3->value()) ? ", " . $this->reg_3->value() : ""
                    ) .". "
                    . "\nAdresse: " . $branding->address->value() . ". "
                    . "\nVi glæder os til at se dig!";
                try {
                    $gatewayapi->send(
                        [
                            (string)$country_code . (string)$customer_phone,
                        ],
                        $message,
                        true
                    );
                } catch (Exception $e) {
                    // Log the error
                    $logs = new logs_o();
                    $logs->add('gatewayapi', 0, 3, 0, 'SEND_CUSTOMER_BOOKING_CONFIRMATION', $e->getMessage());
                }
            }
            // Check if the customer has an email
            if (!empty($customer_email) && $customer->wantsEmailNotifications()) {
                // Send an email confirmation to the customer
                $email = new email();
                $email->sendOrderBookingConfirmationEmail($this);
            }
            // TODO: Send booking confirmation to customer
        }
    }


    public function getObjectProperties(): void
    {
        $this->customer_number = new object_property($this->table, $this->id, 'customer_number', 'int', false);
        $this->department = new object_property($this->table, $this->id, 'department', 'int', false);
        $this->reg_1 = new object_property($this->table, $this->id, 'reg_1', 'string', false);
        $this->reg_2 = new object_property($this->table, $this->id, 'reg_2', 'string', false);
        $this->reg_3 = new object_property($this->table, $this->id, 'reg_3', 'string', false);
        $this->datetime = new object_property($this->table, $this->id, 'datetime', 'timestamp', false);
        $this->note = new object_property($this->table, $this->id, 'note', 'string', false);
        $this->reference = new object_property($this->table, $this->id, 'reference', 'string', false);
        $this->po = new object_property($this->table, $this->id, 'po', 'string', false);
        $this->pickup = new object_property($this->table, $this->id, 'pickup', 'bool', false);
        $this->items = new object_property($this->table, $this->id, 'items', 'json', false);
        $this->order_id = new object_property($this->table, $this->id, 'order_id', 'int', false);
        $this->created_at = new object_property($this->table, $this->id, 'created_at', 'timestamp', false);
        $this->updated_at = new object_property($this->table, $this->id, 'updated_at', 'timestamp', false);
        $this->deleted_at = new object_property($this->table, $this->id, 'deleted_at', 'timestamp', false);
    }

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


    /**
     * @throws Exception
     */
    public function asArray(): array
    {
        $order_id = $this->order_id->value();
        return [
            'id' => (int)$this->id,
            'customer_number' => (int)$this->customer_number->value(),
            'department' => (int)$this->department->value(),
            'reg_1' => $this->reg_1->value(),
            'reg_2' => $this->reg_2->value(),
            'reg_3' => $this->reg_3->value(),
            'datetime' => $this->datetime->value(),
            'note' => $this->note->value(),
            'reference' => $this->reference->value(),
            'po' => $this->po->value(),
            'pickup' => (bool)$this->pickup->value(),
            'items' => $this->parseProductNames($this->items->value()),
            'order_id' => $order_id === null ? null : (int)$order_id,
            'customer_name' => (new users_o())->getUserByCustomerNumber((int)$this->customer_number->value())->getCustomerName((int)$this->customer_number->value()),
            'created_at' => $this->created_at->value(),
            'updated_at' => $this->updated_at->value(),
        ];
    }

    /**
     * @throws Exception
     */
    private function parseProductNames(array $items): array
    {
        $parsedItems = [];
        foreach ($items as $item) {
            if (!isset($item['id']) || !isset($item['quantity'])) {
                continue;
            }
            $product = (new products_o())->select((int)$item['id']);
            if (!$product->exists()) {
                continue;
            }
            $parsedItems[] = [
                ...$item,
                'id' => (int)$product->id,
                'name' => (string)$product->name->value(),
                'quantity' => (int)$item['quantity'],
            ];
        }
        return $parsedItems;
    }

    /**
     * @throws Exception
     */
    public function completeBooking(int $user_id, string $safety_seal = null): void
    {
        self::requireSelected();
        if (!$this->order_id->value()) {
            // Create order, if not already created
            self::createOrderBy($user_id);
            // Add order items, re-calculate the prices to be customer-specific
            self::createOrderItemsBy($user_id);
        }
        // Create a wash certificate (If applicable)
        if (self::containsWashCertificateItem()) self::attachWashCertificate($user_id, $safety_seal);
        // Send wash certificate
        self::sendWashCertificateToCustomer();
    }

    /**
     * @throws Exception
     */
    public function createOrderBy(int $user_id): void
    {
        self::requireSelected();
        // Create order
        $orders = new orders_o();
        $orders->createOrderFromBooking($this->id, $user_id);
        // Set the order id in the booking
        $this->order_id->set((int)$orders->id);
        self::objectChanged();
    }

    /**
     * @throws Exception
     */
    public function createOrderItemsBy(int $user_id): void
    {
        self::requireSelected();
        // Get the order
        $order = self::getOrder();
        $order->requireSelected();
        // Add order items
        foreach ($this->items->value() as $item) {
            if (!isset($item['id']) || !isset($item['quantity'])) {
                continue;
            }
            $orderItems = new order_items_o();
            $orderItems->addItemToOrder(
                (int)$order->id,
                (int)$item['id'],
                (int)$user_id,
                (int)$item['quantity'],
            );
        }

    }

    /**
     * @throws Exception
     */
    public function containsWashCertificateItem(): bool
    {
        self::requireSelected();
        return self::containsProductId(41);
    }

    /**
     * @throws Exception
     */
    private function containsProductId(int $productId): bool
    {
        self::requireSelected();
        $items = $this->items->value();
        foreach ($items as $item) {
            if (isset($item['id']) && (int)$item['id'] == $productId) {
                return true;
            }
        }
        return false;
    }

    /**
     * @throws Exception
     */
    public function getOrder(): orders_o
    {
        self::requireSelected();
        if (!$this->order_id->value()) {
            throw new \Exception('Order not found for this booking');
        }
        $order = new orders_o();
        $order->select((int)$this->order_id->value());
        if (!$order->exists()) {
            throw new \Exception('Order not found for this booking');
        }
        return $order;
    }

    /**
     * @throws Exception
     */
    private function attachWashCertificate(int $user_id, string $safety_seal = null): void
    {
        self::requireSelected();
        // Check if the order already has a wash certificate attached
        if (self::getOrder()->hasWashCertificateAttached()) {
            return;
        }
        // Get the operator name
        $operator = (new users_o())->select($user_id);
        if (!$operator->exists()) {
            throw new Exception('Operator not found');
        }
        // Generate wash certificate
        self::generateWashCertificate($safety_seal, $operator->display_name->value());
    }

    /**
     * Generate a wash certificate
     * @param int|null $safety_seal The safety seal number
     * @param string|null $operator The operator name
     * @throws Exception If the object is not selected
     * @throws Exception If the booking already has a wash certificate
     */
    public function generateWashCertificate(int|null $safety_seal = null, string|null $operator = null): void
    {
        self::requireSelected();
        // Generate the wash certificate
        // Get the department from the booking
        $department = (new departments_o())->select((int)$this->department->value());
        if (!$department->exists()) {
            throw new Exception('Department not found');
        }
        // Get the branding from the department
        $branding = (new branding_o())->select((int)$department->branding->value());
        if (!$branding->exists()) {
            throw new Exception('Branding not found');
        }
        // Get the customer from the booking
        $customer = (new users_o())->getUserByCustomerNumber((int)$this->customer_number->value());
        $department_array = $department->asArray();
        $customer_array = $customer->asArray();
        $booking_array = self::asArray();
        $department_array['branding'] = $branding->asArray();
        //print_r($department_array);
        //print_r($customer_array);
        //print_r($booking_array);
        $pdf_generator = new pdf_generator();
        $pdf_generator->add_html($pdf_generator->templates->getTemplate('wash_certificate')
            ->setCompany([
                'name' => 'Truck Wash',
                'address' => 'Letland Allé 2',
                'zip' => 2630,
                'city' => 'Taastrup',
                'phone_prefix' => 45,
                'phone' => 43717886,
                'email' => 'cph@truckwash.dk',
                'website' => 'www.truckwash.dk',
                'images' => [
                    'logo' => '/truckwash-banner-png.png',
                    'banner' => '/truckwash-banner-png.png',
                    'signature' => '/truckwash-underskrift.png',
                ],
            ])
            ->addData([
                'booking_number' => $this->id,
                'seal_number' => ($safety_seal ?? null),
                'reg_1' => $booking_array['reg_1'],
                'reg_2' => $booking_array['reg_2'],
                'date' => date('d-m-Y'),
                'time' => date('H:i'),
                'carried_out_by' => ($operator ?? null),
                'department_id' => $department->id,
                'department_name' => $department_array['branding']['name'] ?: $department_array['name'],
                'department_address' => $department_array['branding']['address'] ?: $department_array['description'],
                'customer_name' => $customer->getCustomerName($customer_array['customer_number']),
                'customer_address' => $customer->getCustomerEcocomicData($customer_array['customer_number'])->economic_customer->address ?: '-',
                'wash_type' => 'BOOK_WASH'
            ])
            ->getHtml()
        );
        $pdf_path = $pdf_generator->generate_pdf();
        // Set the PDF in the order
        $this->getOrder()->addAttachment(new attachment_content((object)['document' => $pdf_path, 'other' => 'wash_certificate']));
    }

    /**
     * @throws Exception
     */
    public function hasTransaction(): bool
    {
        self::requireSelected();
        return !empty($this->order_id->value());
    }

    public function getDailyUnfulfilledBookingsCountForDepartment(int $department_id, string $date = null): int
    {
        global /** @var db $db */
        $db;
        if ($date === null) {
            $date = date('Y-m-d');
        }
        // Sanitize date
        $date = date('Y-m-d', strtotime($date));
        $order_ids = self::getFieldsWhere([
            'department' => (int)$department_id,
            'DATE(datetime)' => $date,
            'order_id' => null,
        ], ['id']);
        return count($order_ids);
    }

}