<?php

namespace classes;

use economic_invoice_draft_mo;
use Exception;
use objects\collected_order_invoices_o;
use objects\customer_fixed_pricing_o;
use objects\departments_o;
use objects\economic_module_orders;
use objects\logs_o;
use objects\orders_o;
use objects\users_o;

/**
 * Executes e-conomic transfer flows that are now run by queue workers.
 */
class economic_transfer_executor
{
    /**
     * Export order draft invoice to e-conomic.
     * @throws Exception
     */
    public function exportOrderDraftInvoice(int $order_id, int $user_id = 0): array
    {
        $economic = new economic();
        $order = (new orders_o())->getOrderById($order_id);
        if (!$order->exists()) {
            throw new Exception('Order not found');
        }

        $economic_module_orders = (new economic_module_orders())->getByOrderId($order_id);
        $invoice_id = (int)$economic_module_orders->economic_invoice_id->value();
        if ($invoice_id > 0) {
            throw new Exception('An invoice has already been created, invoice ID: ' . $invoice_id);
        }

        $order_items = (new orders_o())->getOrderItems($order_id);
        $order_items = (new orders_o())->applyDepartmentPrices($order_items, $order->department_id->value());
        if (count($order_items) === 0) {
            throw new Exception('No order items found');
        }

        $customer = (new orders_o())->getCustomerByOrderId($order_id);
        if (!$customer->exists()) {
            throw new Exception('Customer not found');
        }
        $economic->assertCustomerNumberIsNotDraft((int)$customer->customer_number->value());

        $customer_economic = $customer->getCustomerEcocomicData()->economic_customer;
        $economic_invoice_draft = new economic_invoice_draft_mo();
        $economic_invoice_draft->setCustomerNumber((int)$customer_economic->customer_number);
        $economic_invoice_draft->setRecipient(
            $customer_economic->name ?? 'Ukendt',
            $customer_economic->address ?? 'Ukendt',
            $customer_economic->zip ?? 'Ukendt',
            $customer_economic->city ?? 'Ukendt'
        );

        $department = (new departments_o())->getDepartmentById($order->department_id->value());
        $this->addTheDepartmentDateReference($economic_invoice_draft, $department['name'], $order);

        $billable_order_items = 0;
        foreach ($order_items as $order_item) {
            $added = $this->addOrderItemToInvoice(
                $customer,
                $order,
                $order_item,
                $economic_invoice_draft,
                (int)($order_item['quantity'] ?? 1)
            );
            if ($added) {
                $billable_order_items++;
            }
        }

        if ($billable_order_items < 1) {
            throw new Exception('No billable order items found');
        }

        $result = null;
        if ($customer->hasOpenInvoiceDraft() && !$customer->invoicePerOrder()) {
            $open_invoice_draft = (int)$customer->getOpenInvoiceDraft();
            $result = $this->addOrderToInvoiceDraft($open_invoice_draft, $order, $customer, $order_items);
        }
        if ($result === null) {
            $result = $economic_invoice_draft->createInvoiceDraftExample();
        }

        if (!isset($result->draftInvoiceNumber) && !isset($result->lines[0])) {
            (new logs_o())->add(
                'economic_invoice_draft',
                'global',
                3,
                $user_id,
                'ECONOMIC_INVOICE_DRAFT_EXPORT',
                'Failed to create economic invoice draft'
            );
            $message = $result->message ?? 'Failed to create economic invoice draft';
            throw new Exception((string)$message);
        }

        if ($economic_module_orders->economic_invoice_draft_id->value() > 0) {
            $economic_invoice_draft->deleteInvoiceDraft($economic_module_orders->economic_invoice_draft_id->value());
        }

        $new_draft_id = (int)($result->draftInvoiceNumber ?? $customer->getOpenInvoiceDraft());
        $economic_module_orders->economic_invoice_draft_id->set($new_draft_id);
        if (!$customer->invoicePerOrder()) {
            $customer->setOpenInvoiceDraft($new_draft_id);
        } else {
            $customer->unsetOpenInvoiceDraft();
        }

        (new logs_o())->add(
            'economic_invoice_draft',
            'global',
            1,
            $user_id,
            'ECONOMIC_INVOICE_DRAFT_EXPORT',
            'Successfully exported an economic invoice draft'
        );

        return $economic_module_orders->getArray();
    }

    /**
     * Export booked invoice from existing draft.
     * @throws Exception
     */
    public function exportOrderInvoice(int $order_id, int $user_id = 0): array
    {
        $economic = new economic();
        $order = (new orders_o())->getOrderById($order_id);
        if (!$order->exists()) {
            throw new Exception('Order not found');
        }

        $customer = (new orders_o())->getCustomerByOrderId($order_id);
        if (!$customer->exists()) {
            throw new Exception('Customer not found');
        }
        $economic->assertCustomerNumberIsNotDraft((int)$customer->customer_number->value());

        $economic_module_orders = (new economic_module_orders())->getByOrderId($order_id);
        if ($economic_module_orders->economic_invoice_draft_id->value() === 0) {
            throw new Exception('No economic invoice draft found');
        }

        $invoice_id = (int)$economic_module_orders->economic_invoice_id->value();
        if ($invoice_id > 0) {
            throw new Exception('An invoice has already been created, invoice ID: ' . $invoice_id);
        }

        $invoice_draft_id = (int)$economic_module_orders->economic_invoice_draft_id->value();
        $economic_invoice_draft = new economic_invoice_draft_mo();
        $result = $economic_invoice_draft->publishInvoiceDraft($invoice_draft_id);

        if (!isset($result->bookedInvoiceNumber)) {
            (new logs_o())->add(
                'economic_invoice',
                'global',
                3,
                $user_id,
                'ECONOMIC_INVOICE_EXPORT',
                'Failed to create economic invoice from draft: ' . $invoice_draft_id
            );
            $message = $result->message ?? 'Failed to create economic invoice';
            throw new Exception((string)$message);
        }

        $economic_module_orders = (new economic_module_orders())->getByOrderId($order_id);
        $economic_module_orders->economic_invoice_id->set((int)$result->bookedInvoiceNumber);
        $customer->unsetOpenInvoiceDraft();

        (new logs_o())->add(
            'economic_invoice',
            'global',
            1,
            $user_id,
            'ECONOMIC_INVOICE_EXPORT',
            'Successfully exported an economic invoice'
        );

        return $economic_module_orders->getArray();
    }

    /**
     * Export collected invoice to e-conomic.
     * @throws Exception
     */
    public function exportCollectedInvoice(int $collected_invoice_id, bool $send_as_is = false, int $user_id = 0): array
    {
        $collected_order_invoices = (new collected_order_invoices_o())->select($collected_invoice_id);
        $collected_order_invoices->requireSelected();
        (new economic())->assertCustomerNumberIsNotDraft((int)$collected_order_invoices->customer_number->value());

        if ($collected_order_invoices->external_id->value() === null) {
            if (!$send_as_is) {
                $customer_fixed_pricing_o = new customer_fixed_pricing_o();
                if ($customer_fixed_pricing_o->doesUserHaveFixedPricing((int)$collected_order_invoices->customer_number->value())) {
                    $customer_fixed_pricing_price = $customer_fixed_pricing_o->selectByCustomerNumber((int)$collected_order_invoices->customer_number->value());
                    $customer_fixed_pricing_price = (int)$customer_fixed_pricing_price->price->value();
                    $collected_order_invoices->overridePricesFixed($customer_fixed_pricing_price);
                } else {
                    $collected_order_invoices->addVehicleSubscriptionsTransaction();
                }
            } else {
                $collected_order_invoices->removeSpecialArrangements();
                $collected_order_invoices->setAllItemsToBeIncludedInInvoice();
            }

            $collected_order_invoices->addToEconomic();
        } else {
            if ($collected_order_invoices->booked_invoice_id->value() !== null) {
                throw new Exception('Invoice has already been booked');
            }
            if ($collected_order_invoices->isDraftExisting()) {
                throw new Exception('Invoice draft already exists in E-Conomic');
            }

            $customer_fixed_pricing_o = new customer_fixed_pricing_o();
            if ($customer_fixed_pricing_o->doesUserHaveFixedPricing((int)$collected_order_invoices->customer_number->value())) {
                $customer_fixed_pricing_price = $customer_fixed_pricing_o->selectByCustomerNumber((int)$collected_order_invoices->customer_number->value());
                $customer_fixed_pricing_price = (int)$customer_fixed_pricing_price->price->value();
                $collected_order_invoices->overridePricesFixed($customer_fixed_pricing_price);
            } else {
                $collected_order_invoices->addVehicleSubscriptionsTransaction();
            }

            $collected_order_invoices->addToEconomic(true);
        }

        (new logs_o())->add(
            'orderInvoices',
            'global',
            1,
            $user_id,
            'ADD_COLLECTED_INVOICE_ECONOMIC',
            'Queued transfer processed successfully for collected invoice #' . $collected_invoice_id
        );

        $result = $collected_order_invoices->asArray();
        $transfer_metrics = $collected_order_invoices->getLastEconomicTransferMetrics();
        if ($transfer_metrics !== null) {
            $result['economic_transfer_metrics'] = $transfer_metrics;
        }

        return $result;
    }

    /**
     * @throws Exception
     */
    public function addTheDepartmentDateReference(economic_invoice_draft_mo $economic_invoice_draft, mixed $department_name, orders_o $order): void
    {
        $parsed_date = date('d/m/Y H:i', strtotime($order->created_at->value()));
        $economic_invoice_draft->addLineTEXT("[ " . $parsed_date . ' ' . $department_name . ' #' . $order->id . " ]");
        if ($order->reference->value() !== '') {
            $economic_invoice_draft->addLineTEXT('Reference:');
            if (str_contains($order->reference->value(), "\n")) {
                foreach (explode("\n", $order->reference->value()) as $line) {
                    $economic_invoice_draft->addLineTEXT('# ' . $line);
                }
            } else {
                $economic_invoice_draft->addLineTEXT('# ' . $order->reference->value());
            }
        }

        $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());
        }
        $economic_invoice_draft->addLineTEXT($line_reg);

        if ($order->notes->value() !== '') {
            $economic_invoice_draft->addLineTEXT('Notat:');
            if (str_contains($order->notes->value(), "\n")) {
                foreach (explode("\n", $order->notes->value()) as $line) {
                    $economic_invoice_draft->addLineTEXT('# ' . $line);
                }
            } else {
                $economic_invoice_draft->addLineTEXT('# ' . $order->notes->value());
            }
        }
    }

    /**
     * Returns true when a product line was added, false when skipped.
     * @throws Exception
     */
    public function addOrderItemToInvoice(
        users_o $customer,
        orders_o $order,
        mixed $order_item,
        economic_invoice_draft_mo $economic_invoice_draft,
        int $quantity = 1
    ): bool {
        if (self::shouldSkipOrderItemForInvoice($order_item, $quantity)) {
            return false;
        }

        if (!is_array($order_item)) {
            throw new Exception('Order item payload must be an array');
        }

        $product_number = trim((string)($order_item['product']['economic_product_id'] ?? ''));
        if ($product_number === '') {
            throw new Exception('Order item is missing economic product id');
        }

        $product_name = trim((string)($order_item['product']['name'] ?? ''));
        if ($product_name === '') {
            $product_name = 'Ukendt produkt';
        }

        $reference = isset($order_item['reference']) ? (string)$order_item['reference'] : '';
        $notes = isset($order_item['notes']) ? (string)$order_item['notes'] : '';

        $department = $order->getDepartmentByOrderId($order->id);
        $economic_department_id = $department['economic_department_id'];
        $economic_dimension_id = $department['economic_dimension_id'] ?? 0;
        $order_item_price = (float)($order_item['price'] ?? 0);
        $product_price = (float)($order_item['product']['price'] ?? 0);

        $economic_invoice_draft->addLine(
            $product_number,
            $product_name,
            $quantity,
            $order_item_price,
            0,
            (int)$economic_department_id ?? 0,
            (int)$economic_dimension_id ?? 0
        );

        $show_discount = abs($order_item_price - $product_price) > 0.00001;
        if ($show_discount && abs($product_price) > 0.00001) {
            $discount_percentage = round((($product_price - $order_item_price) / $product_price) * 100, 0);
            $economic_invoice_draft->addLineTEXT('Rabat: ' . ($order_item_price - $product_price) . ' DKK (' . $discount_percentage . '%)');
        }

        if ($reference !== '') {
            $economic_invoice_draft->addLineTEXT('Reference:');
            if (str_contains($reference, "\n")) {
                foreach (explode("\n", $reference) as $line) {
                    $economic_invoice_draft->addLineTEXT('# ' . $line);
                }
            } else {
                $economic_invoice_draft->addLineTEXT('# ' . $reference);
            }
        }

        if ($customer->doesUserHaveAttribute('requiresRegistrationNumbersInvoice')) {
            $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());
            }
            $economic_invoice_draft->addLineTEXT($line_reg);
        }

        if ($notes !== '') {
            $economic_invoice_draft->addLineTEXT('Notat:');
            if (str_contains($notes, "\n")) {
                foreach (explode("\n", $notes) as $line) {
                    $economic_invoice_draft->addLineTEXT('# ' . $line);
                }
            } else {
                $economic_invoice_draft->addLineTEXT('# ' . $notes);
            }
        }

        return true;
    }

    /**
     * @throws Exception
     */
    public function addOrderToInvoiceDraft(int $economic_invoice_draft_id, orders_o $order, users_o $customer, array $order_items): object
    {
        $has_billable_items = false;
        foreach ($order_items as $order_item) {
            if (!self::shouldSkipOrderItemForInvoice($order_item, (int)($order_item['quantity'] ?? 1))) {
                $has_billable_items = true;
                break;
            }
        }
        if (!$has_billable_items) {
            throw new Exception('No billable order items found');
        }

        $economic_invoice_draft = new economic_invoice_draft_mo();
        $economic_invoice_draft->addLineTEXT('');
        $economic_invoice_draft->addLineTEXT('');
        $this->addTheDepartmentDateReference($economic_invoice_draft, $order->getDepartmentByOrderId($order->id)['name'], $order);

        foreach ($order_items as $order_item) {
            $this->addOrderItemToInvoice($customer, $order, $order_item, $economic_invoice_draft, (int)($order_item['quantity'] ?? 1));
        }

        return $economic_invoice_draft->addLinesToInvoiceDraft($economic_invoice_draft_id);
    }

    /**
     * Skip item lines that are not billable in e-conomic export (0 quantity or 0 unit cost).
     */
    public static function shouldSkipOrderItemForInvoice(mixed $order_item, int $quantity = 1): bool
    {
        if (!is_array($order_item)) {
            return true;
        }

        $line_quantity = $quantity;
        if (isset($order_item['quantity']) && is_numeric($order_item['quantity'])) {
            $line_quantity = (int)$order_item['quantity'];
        }

        if ($line_quantity <= 0) {
            return true;
        }

        $line_price = null;
        if (isset($order_item['price']) && is_numeric($order_item['price'])) {
            $line_price = (float)$order_item['price'];
        }

        if ($line_price === null) {
            return true;
        }

        if (abs($line_price) < 0.00001) {
            return true;
        }

        return false;
    }
}
