<?php

namespace helpers;

use classes\economic;
use Exception;
use objects\currency_conversion_rates_o;
use objects\departments_o;
use objects\orders_o;

class economic_invoice_draft
{
    /**
     * The Economic draftInvoiceNumber
     * @var int $draft_invoice_number
     */
    protected int $draft_invoice_number;

    /**
     * The draft lines
     * @var array $draft_lines
     */
    protected array $draft_lines = [];

    /**
     * The raw draft invoice data
     * @var object $draft_invoice_data
     */
    protected object $draft_invoice_data;
    /**
     * The currency for the draft invoice
     * @var string $currency
     * @note The currency is set to DKK by default
     */
    protected string $currency = 'DKK';
    /**
     * The currency conversion rate
     * @var float $conversion_rate
     * @note The conversion rate is set to 1.0 by default
     */
    protected float $conversion_rate = 1.0;


    /**
     * Construct a new Economic draft invoice object
     * @throws Exception if the invoice data is invalid or empty
     */
    public function __construct(int $draft_invoice_number, string $currency = 'DKK', bool $skip_fetch = false)
    {
        $this->setDraftInvoiceNumber($draft_invoice_number);
        $this->setCurrency(strtoupper($currency));
        // If the skip fetch is set to false, fetch the draft invoice data
        if (!$skip_fetch) {
            $this->fetchDraftInvoiceData();
        } else {
            $this->draft_invoice_data = new \stdClass();
            $this->draft_invoice_data->draftInvoiceNumber = $this->draft_invoice_number;
        }
        $this->requireSelected();
    }

    /**
     * Set the draft invoice number
     * @param int $draft_invoice_number
     * @return void
     */
    private function setDraftInvoiceNumber(int $draft_invoice_number): void
    {
        $this->draft_invoice_number = $draft_invoice_number;
    }

    /**
     * Set the currency for the draft invoice
     * @param string $currency The currency to set
     * @return void
     * @throws Exception If the currency is not valid
     * @throws Exception If the conversion rate is not valid
     */
    private function setCurrency(string $currency): void
    {
        // Set the currency for the draft invoice
        $this->currency = $currency;
        // Get the conversion rate for the currency
        $this->conversion_rate = (float)(new currency_conversion_rates_o())->convertTo($this->currency, 1);
    }

    /**
     * Fetch the draft invoice data from the Economic system
     * @throws Exception If the request fails
     * @throws Exception If the invoice is not found
     */
    private function fetchDraftInvoiceData(): void
    {
        $economic = new economic();
        $this->draft_invoice_data = $economic->invoices->draft->get($this->draft_invoice_number);
        if (!isset($this->draft_invoice_data->draftInvoiceNumber)) {
            throw new Exception('Draft invoice not found');
        }
    }

    /**
     * Require that the draft invoice data is selected
     * @throws Exception if the draft invoice data is not set
     */
    protected function requireSelected(): void
    {
        if (!isset($this->draft_invoice_data->draftInvoiceNumber)) {
            throw new Exception('The draft invoice data is not set');
        }
    }

    /**
     * Add the lines to the draft invoice
     * @return void
     */
    public function addLines(): void
    {
        $economic = new economic();
        $economic->invoices->draft->add_lines($this->draft_invoice_number, $this->draft_lines);
    }

    /**
     * Get the draft invoice number
     * @return int The draft invoice number
     */
    public function getDraftInvoiceNumber(): int
    {
        return $this->draft_invoice_number;
    }

    /**
     * Add a new transaction header to the draft invoice
     * @note The lines won't be saved until the addLines() method is called.
     * @note This is the "[28/02/2025 18:29 Roskilde #685]" line in the invoice
     * @param orders_o $order The order to add
     * @return void
     */
    public function addNewTransactionHeader(orders_o $order): void
    {
        // Get the department name
        $department_name = (new departments_o())->getDepartmentName((int)$order->department_id->value());
        // Parse the date of the transaction.
        $parsed_date = date('d/m/Y H:i', strtotime($order->created_at->value()));
        // Add the text line to the draft invoice
        self::addTextLine("[ " . $parsed_date . ' ' . $department_name . ' #' . $order->id . " ]");
        // If there's a PO number, add it to the invoice
        if ($order->po->value() !== '') {
            self::addTextLine('PO: ' . $order->po->value());
        }
        // If there's a reference, add it to the invoice
        if ($order->reference->value() !== '') {
            self::addTextLine('Reference:');
            // Add "# " in the beginning of each line (If there's more than one line, otherwise we just add the prefix once)
            if (str_contains($order->reference->value(), "\n")) {
                foreach ( explode("\n", $order->reference->value()) as $line ) {
                    self::addTextLine('# ' . $line);
                }
            } else {
                self::addTextLine('# ' . $order->reference->value());
            }
        }
        // Add the registration numbers (if any)
        $line_reg = '';
        if ($order->reg_1->value() !== '')
            $line_reg .= 'Reg 1: ' . strtoupper($order->reg_1->value());
        if ($order->reg_2->value() !== '')
            $line_reg .= ', Reg 2: ' . strtoupper($order->reg_2->value());
        if ($order->reg_3->value() !== '')
            $line_reg .= ', Reg 3: ' . strtoupper($order->reg_3->value());
        // Add the line to the invoice (If there's any registration numbers)
        if ($line_reg !== '')
            self::addTextLine($line_reg);
        // If there's a note, add it to the invoice
        if ($order->notes->value() !== '') {
            self::addTextLine('Notat:');
            // Add "# " in the beginning of each line (If there's more than one line, otherwise we just add the prefix once)
            if (str_contains($order->notes->value(), "\n")) {
                foreach ( explode("\n", $order->notes->value()) as $line ) {
                    self::addTextLine('# ' . $line);
                }
            } else {
                self::addTextLine('# ' . $order->notes->value());
            }
        }
    }

    /**
     * Add a text line to the draft invoice
     * @note The lines won't be saved until the addLines() method is called.
     * @param string $text The text to add
     * @return void
     */
    public function addTextLine(string $text): void
    {
        $this->draft_lines[] = [
            'description' => $text
        ];
    }

    /**
     * Add an order to the draft invoice
     * @note The lines won't be saved until the addLines() method is called.
     * @param orders_o $order The order to add
     * @return void
     * @throws Exception if the order is not found
     * @throws Exception if the order is not valid
     */
    public function addOrderItemLines(orders_o $order): void
    {
        // Get the order items
        $order_items = $order->getOrderItems($order->id);
        // Apply the department pricing
        $order_items = $order->applyDepartmentPrices($order_items, $order->department_id->value());
        // Get the department
        $department = $order->getDepartmentByOrderId($order->id);
        // If the department id is 10, check if the user has a default department set
        if ((int)$department['id'] === 10) {
            // If the default department is set, use it
            $department = (new departments_o())->getDepartmentById((int)$department['id'], true);
        }
        // Remove all the order items that are not included in the invoice
        $order_items = array_filter($order_items, function ($order_item) {
            return (bool)$order_item['include_in_invoice'];
        });
        // Define the total discount applied to the order
        $total_discount = 0;
        // Loop through the order items
        foreach ( $order_items as $order_item ) {
            // Add the order item to the draft invoice
            self::addOrderItemLine($order_item, $department);
            // Add the line discount to the total discount
            $total_discount += ($order_item['product']['price'] - $order_item['price']) * $order_item['quantity'];
        }
        // If the total discount is greater than 0, add it to the invoice
        if ($total_discount > 0) {
            // Add the discount to the invoice
            self::addProductDiscountLine($total_discount, $department['economic_department_id'] ?? 0, $department['dimension'] ?? 0);
        }
    }

    /**
     * Add an order item line to the draft invoice
     * @note The lines won't be saved until the addLines() method is called.
     * @param array $order_item The order item to add
     * @param array $department The department to add
     * @param boolean $show_discount Whether to show the discount or not
     * @return void
     * @throws Exception if the order item is not found
     * @throws Exception if the order item is not valid
     */
    public function addOrderItemLine(array $order_item, array $department, bool $show_discount = false): void
    {
        // Check if the order item is valid
        if (!isset($order_item['id'])) {
            throw new Exception('The order item is not valid');
        }
        // Get the department id
        $economic_department_id = $department['economic_department_id'] ?? 0;
        // Get the dimension id
        $economic_dimension_id = $department['economic_dimension_id'] ?? 0;
        // Add the order item to the draft invoice
        self::addProductLine(
            (string)$order_item['product']['economic_product_id'],
            (string)$order_item['product']['name'],
            (int)$order_item['quantity'] ?? 1,
            (int)($order_item['product']['price'] == 0 ? $order_item['price'] : $order_item['product']['price']),
            (int)$economic_department_id,
            $economic_dimension_id
        );

        // Calculate the discount percentage (If the final price is 0, set the discount percentage to 100)
        if ($order_item['product']['price'] == 0) {
            $show_discount = false;
            $discountPercentage = 0;
        } else {
            // Calculate the discount percentage
            $discountPercentage = round((($order_item['product']['price'] - $order_item['price']) / $order_item['product']['price']) * 100, 0);
        }

        // If the price is different from the product price, add it to the line
        if ($order_item['price'] !== $order_item['product']['price'] && $show_discount)
            self::addTextLine('Rabat: ' . ($order_item['price'] - $order_item['product']['price']) . ' ' . $this->currency . ' (' . $discountPercentage . '%)');

        // If there's a reference, add it to the line
        if ($order_item['reference'] !== '') {
            self::addTextLine('Reference:');
            // Add "# " in the beginning of each line (If there's more than one line, otherwise we just add the prefix once)
            if (str_contains($order_item['reference'], "\n")) {
                foreach ( explode("\n", $order_item['reference']) as $line ) {
                    self::addTextLine('# ' . $line);
                }
            } else {
                self::addTextLine('# ' . $order_item['reference']);
            }
        }

        // If there's a note, add it to the line
        if (!empty($order_item['notes'])) {
            self::addTextLine('Notat:');
            // Add "# " in the beginning of each line (If there's more than one line, otherwise we just add the prefix once)
            if (str_contains($order_item['notes'], "\n")) {
                foreach ( explode("\n", $order_item['notes']) as $line ) {
                    self::addTextLine('# ' . $line);
                }
            } else {
                self::addTextLine('# ' . $order_item['notes']);
            }
        }

    }

    /**
     * Add a product line to the draft invoice
     * @note The lines won't be saved until the addLines() method is called.
     */
    public function addProductLine(string $productNumber, string $description, float $quantity, float $unitNetPrice, int $economic_department_id, int $dimension): void
    {
        // We then add a 0 in front of the product number to make sure it's the right one, made by FlexPOS.
        // Add a line to the invoice
        $line = [
            'product' => [
                'productNumber' => $productNumber,
            ],
            'quantity' => $quantity,
            'unitNetPrice' => self::convertCurrency($unitNetPrice),
            'discountPercentage' => 0,
            'description' => $description,
        ];
        // If the department is set, add it to the line
        if ($economic_department_id && $economic_department_id > 0) {
            $line['departmentalDistribution'] = [
                'departmentalDistributionNumber' => $economic_department_id,
                'DistributionType' => 'Department',
                'dimension' => $dimension
            ];
        }
        $this->draft_lines[] = $line;
    }

    private function convertCurrency(float $amount): float
    {
        // Convert the amount to the correct currency
        $converted_value = $amount * self::getConversionRate();
        // Round the value to 2 decimal places
        return round($converted_value, 2);
    }

    private function getConversionRate(): float
    {
        // Get the conversion rate for the currency
        return $this->conversion_rate;
    }

    /**
     * Add a discount line to the draft invoice
     * @note The lines won't be saved until the addLines() method is called.
     * @param float $discount The discount to add
     * @param int $economic_department_id The department id to add
     * @param int $economic_dimension_id The dimension id to add
     * @return void
     */
    public function addProductDiscountLine(float $discount, int $economic_department_id, int $economic_dimension_id): void
    {
        // Add a line to the invoice
        $line = [
            'product' => [
                'productNumber' => 'TotDiscount',
            ],
            'quantity' => 1,
            'unitNetPrice' => self::convertCurrency(-$discount),
            'discountPercentage' => 0,
            'description' => 'Rabat',
        ];
        // If the department is set, add it to the line
        if ($economic_department_id && $economic_department_id > 0) {
            $line['departmentalDistribution'] = [
                'departmentalDistributionNumber' => $economic_department_id,
                'DistributionType' => 'Department',
                'dimension' => $economic_dimension_id
            ];
        }
        $this->draft_lines[] = $line;
    }

    /**
     * Check if a draft invoice exists in the Economic system
     * @throws Exception if the draft invoice does not exist
     */
    protected function requireExists(): void
    {
        if (!self::exists()) {
            throw new Exception('The draft invoice does not exist');
        }
    }

    /**
     * Check if a customer exists in the Economic system
     * @return bool True if the customer exists, false if not
     * @throws Exception
     */
    public function exists(): bool
    {
        // Make sure the draft invoice number is set before calling
        self::requireDraftInvoiceNumber();
        // Check if the draft invoice exists
        $economic = new economic();
        return $economic->invoices->drafts->exists($this->draft_invoice_number);
    }

    /**
     * Require that the draft invoice number is set
     * @throws Exception if the draft invoice number is not set
     */
    protected function requireDraftInvoiceNumber(): void
    {
        if (!isset($this->draft_invoice_number)) {
            throw new Exception('The draft invoice number is not set');
        }
    }

    /**
     * Require that the draft invoice data is set
     * @throws Exception if the draft invoice data is not set
     */
    protected function requireDraftInvoiceData(): void
    {
        if (!isset($this->draft_invoice_data)) {
            throw new Exception('The draft invoice data is not set');
        }
    }
}