<?php

namespace routes;

use classes\authentication;
use classes\economic;
use classes\economic_transfer_queue;
use classes\response;
use classes\router;
use economic_invoice_draft_mo;
use objects\departments_o;
use objects\economic_module_orders;
use objects\logs_o;
use objects\orders_o;
use objects\users_o;
use traits\route_t;

class economicInvoiceRoute
{
    use route_t;

    public function run(): void
    {
        global /** @var response $response */
        /** @var router $router */
        $router, $response;

        $this->post('/economic/invoice/draft/export', function () {
            global $response;
            $this->requirePermission('economic_invoice_draft_export');
            $user = (new authentication())->get_user();
            if (!$user) {
                (new logs_o())->add('economic_invoice_draft', 'global', 1, 0, 'ECONOMIC_INVOICE_DRAFT_EXPORT', 'No user found, or invalid session');
                $response->error('Invalid session', 400);
            }

            $order_id = $response->getRequestParameter('order_id');
            if (!isset($order_id)) {
                $response->error('Order ID is required', 400);
            }
            if (!is_numeric($order_id) || (int)$order_id < 1) {
                $response->error('Order ID must be a positive number', 400);
            }

            $order = (new orders_o())->getOrderById((int)$order_id);
            if (!$order->exists()) {
                $response->error('Order not found', 404);
            }
            try {
                $this->assertOrderCanBeExportedToEconomic((int)$order_id);
            } catch (\Throwable $e) {
                $response->error($e->getMessage(), 400);
            }

            if (!$this->isEconomicTransferQueueAvailable()) {
                try {
                    $result = $this->exportOrderDraftSynchronously((int)$order_id, (int)$user->id);
                } catch (\Throwable $e) {
                    $response->error($e->getMessage(), 400);
                }

                (new logs_o())->add(
                    'economic_invoice_draft',
                    'global',
                    1,
                    (int)$user->id,
                    'ECONOMIC_INVOICE_DRAFT_EXPORT_FALLBACK',
                    'Processed economic invoice draft export synchronously because queue dependencies are unavailable'
                );
                $response->success([
                    'message' => 'Economic invoice draft export processed synchronously',
                    'mode' => 'synchronous_fallback',
                    'result' => $result,
                ]);
            }

            $queue = new economic_transfer_queue();
            $job = $queue->enqueue(
                economic_transfer_queue::TYPE_ORDER_DRAFT_EXPORT,
                [
                    'order_id' => (int)$order_id,
                    'requested_by' => (int)$user->id,
                ],
                (int)$user->id
            );
            $job_id = (int)($job['id'] ?? 0);
            if ($job_id < 1) {
                $response->error('Failed to enqueue draft export job: missing queue job id in enqueue response', 500);
            }

            (new logs_o())->add('economic_invoice_draft', 'global', 1, $user->id, 'ECONOMIC_INVOICE_DRAFT_EXPORT_QUEUED', 'Queued economic invoice draft export');
            $response->success([
                'message' => 'Economic invoice draft export queued',
                'job_id' => $job_id,
                'job' => $job,
            ], 202);
        }, [
            'economic_invoice_draft_export' => 'Export an economic invoice draft'
        ]);

        $this->delete('/economic/invoice/draft/delete', function () {
            global /** @var response $response */
            $response;
            $this->requirePermission('economic_invoice_draft_delete');
            $user = (new authentication())->get_user();
            if ($user) {
                $order_id = $response->getRequestParameter('order_id');
                if (!isset($order_id)) {
                    $response->error('Order ID is required', 400);
                }
                $order = (new orders_o())->getOrderById($order_id);
                if (!$order->exists()) {
                    $response->error('Order not found', 404);
                }
                $economic_module_orders = (new economic_module_orders())->getByOrderId($order_id);
                if ($economic_module_orders->economic_invoice_draft_id->value() === 0) {
                    $response->error('No economic invoice draft found', 404);
                }
                $economic_invoice_draft = (new economic_invoice_draft_mo());
                $invoiceDraftId = $economic_module_orders->economic_invoice_draft_id->value();
                $customer = (new orders_o())->getCustomerByOrderId($order_id);
                if (!$customer->invoicePerOrder()) {
                    if ($customer->hasOpenInvoiceDraft()) {
                        if ((int)$customer->getOpenInvoiceDraft() === (int)$invoiceDraftId) {
                            $customer->deleteOpenInvoiceDraft();
                        }
                    }
                }
                $economic_invoice_draft->deleteInvoiceDraft($invoiceDraftId);
                $economic_module_orders->economic_invoice_draft_id->set(null);
                (new logs_o())->add('economic_invoice_draft', 'global', 1, $user->id, 'ECONOMIC_INVOICE_DRAFT_DELETE', 'Successfully deleted an economic invoice draft');
                $response->success($economic_module_orders->getArray());
            } else {
                (new logs_o())->add('economic_invoice_draft', 'global', 1, 0, 'ECONOMIC_INVOICE_DRAFT_DELETE', 'No user found, or invalid session');
                $response->error('Invalid session', 400);
            }
        }, [
            'economic_invoice_draft_delete' => 'Delete an economic invoice draft'
        ]);

        $this->post('/economic/invoice/export', function () {
            global $response;
            $this->requirePermission('economic_invoice_export');
            $user = (new authentication())->get_user();
            if (!$user) {
                (new logs_o())->add('economic_invoice', 'global', 1, 0, 'ECONOMIC_INVOICE_EXPORT', 'No user found, or invalid session');
                $response->error('Invalid session', 400);
            }

            $order_id = $response->getRequestParameter('order_id');
            if (!isset($order_id)) {
                $response->error('Order ID is required', 400);
            }
            if (!is_numeric($order_id) || (int)$order_id < 1) {
                $response->error('Order ID must be a positive number', 400);
            }

            $order = (new orders_o())->getOrderById((int)$order_id);
            if (!$order->exists()) {
                $response->error('Order not found', 404);
            }
            $customer = (new orders_o())->getCustomerByOrderId((int)$order_id);
            if (!$customer->exists()) {
                $response->error('Customer not found', 404);
            }
            try {
                (new economic())->assertCustomerNumberIsNotDraft((int)$customer->customer_number->value());
            } catch (\Throwable $e) {
                $response->error($e->getMessage(), 400);
            }
            $economic_module_orders = (new economic_module_orders())->getByOrderId((int)$order_id);
            if ($economic_module_orders->economic_invoice_draft_id->value() === 0) {
                $response->error('No economic invoice draft found', 404);
            }
            if ((int)$economic_module_orders->economic_invoice_id->value() > 0) {
                $response->error('An invoice has already been created, invoice ID: ' . (int)$economic_module_orders->economic_invoice_id->value(), 400);
            }

            if (!$this->isEconomicTransferQueueAvailable()) {
                try {
                    $result = $this->exportOrderInvoiceSynchronously((int)$order_id, (int)$user->id);
                } catch (\Throwable $e) {
                    $response->error($e->getMessage(), 400);
                }

                (new logs_o())->add(
                    'economic_invoice',
                    'global',
                    1,
                    (int)$user->id,
                    'ECONOMIC_INVOICE_EXPORT_FALLBACK',
                    'Processed economic invoice export synchronously because queue dependencies are unavailable'
                );
                $response->success([
                    'message' => 'Economic invoice export processed synchronously',
                    'mode' => 'synchronous_fallback',
                    'result' => $result,
                ]);
            }

            $queue = new economic_transfer_queue();
            $job = $queue->enqueue(
                economic_transfer_queue::TYPE_ORDER_INVOICE_EXPORT,
                [
                    'order_id' => (int)$order_id,
                    'requested_by' => (int)$user->id,
                ],
                (int)$user->id
            );
            $job_id = (int)($job['id'] ?? 0);
            if ($job_id < 1) {
                $response->error('Failed to enqueue invoice export job: missing queue job id in enqueue response', 500);
            }

            (new logs_o())->add('economic_invoice', 'global', 1, $user->id, 'ECONOMIC_INVOICE_EXPORT_QUEUED', 'Queued economic invoice export');
            $response->success([
                'message' => 'Economic invoice export queued',
                'job_id' => $job_id,
                'job' => $job,
            ], 202);
        }, [
            'economic_invoice_export' => 'Export an economic invoice'
        ]);

        $this->get('/economic/invoice/draft/export/status', function () {
            global $response;
            $this->requirePermission('economic_invoice_draft_export');
            $user = (new authentication())->get_user();
            if (!$user) {
                $response->error('Invalid session', 400);
            }
            $this->ensureEconomicTransferQueueIsAvailable();

            $job_id = $response->getRequestParameter('job_id');
            if (!isset($job_id) || !is_numeric($job_id) || (int)$job_id < 1) {
                $response->error('job_id is required and must be a positive number', 400);
            }

            $queue = new economic_transfer_queue();
            $job = $queue->getJobByIdForUser((int)$job_id, (int)$user->id);
            if ($job === null || ($job['transfer_type'] ?? null) !== economic_transfer_queue::TYPE_ORDER_DRAFT_EXPORT) {
                $response->error('Draft export queue job not found', 404);
            }

            $response->success($job);
        }, [
            'economic_invoice_draft_export' => 'Read queue status for economic draft invoice export'
        ]);

        $this->post('/economic/invoice/draft/export/retry', function () {
            global $response;
            $this->requirePermission('economic_invoice_draft_export');
            $user = (new authentication())->get_user();
            if (!$user) {
                $response->error('Invalid session', 400);
            }
            $this->ensureEconomicTransferQueueIsAvailable();

            $job_id = $response->getRequestParameter('job_id');
            if (!isset($job_id) || !is_numeric($job_id) || (int)$job_id < 1) {
                $response->error('job_id is required and must be a positive number', 400);
            }

            $queue = new economic_transfer_queue();
            $existing_job = $queue->getJobByIdForUser((int)$job_id, (int)$user->id);
            if ($existing_job === null || ($existing_job['transfer_type'] ?? null) !== economic_transfer_queue::TYPE_ORDER_DRAFT_EXPORT) {
                $response->error('Draft export queue job not found', 404);
            }

            try {
                $job = $queue->retryJobForUser((int)$job_id, (int)$user->id);
            } catch (\Throwable $e) {
                $response->error($e->getMessage(), 400);
            }

            $response->success([
                'message' => 'Draft export queue job retried',
                'job' => $job,
            ]);
        }, [
            'economic_invoice_draft_export' => 'Retry failed queue job for economic draft invoice export'
        ]);

        $this->get('/economic/invoice/export/status', function () {
            global $response;
            $this->requirePermission('economic_invoice_export');
            $user = (new authentication())->get_user();
            if (!$user) {
                $response->error('Invalid session', 400);
            }
            $this->ensureEconomicTransferQueueIsAvailable();

            $job_id = $response->getRequestParameter('job_id');
            if (!isset($job_id) || !is_numeric($job_id) || (int)$job_id < 1) {
                $response->error('job_id is required and must be a positive number', 400);
            }

            $queue = new economic_transfer_queue();
            $job = $queue->getJobByIdForUser((int)$job_id, (int)$user->id);
            if ($job === null || ($job['transfer_type'] ?? null) !== economic_transfer_queue::TYPE_ORDER_INVOICE_EXPORT) {
                $response->error('Invoice export queue job not found', 404);
            }

            $response->success($job);
        }, [
            'economic_invoice_export' => 'Read queue status for economic invoice export'
        ]);

        $this->post('/economic/invoice/export/retry', function () {
            global $response;
            $this->requirePermission('economic_invoice_export');
            $user = (new authentication())->get_user();
            if (!$user) {
                $response->error('Invalid session', 400);
            }
            $this->ensureEconomicTransferQueueIsAvailable();

            $job_id = $response->getRequestParameter('job_id');
            if (!isset($job_id) || !is_numeric($job_id) || (int)$job_id < 1) {
                $response->error('job_id is required and must be a positive number', 400);
            }

            $queue = new economic_transfer_queue();
            $existing_job = $queue->getJobByIdForUser((int)$job_id, (int)$user->id);
            if ($existing_job === null || ($existing_job['transfer_type'] ?? null) !== economic_transfer_queue::TYPE_ORDER_INVOICE_EXPORT) {
                $response->error('Invoice export queue job not found', 404);
            }

            try {
                $job = $queue->retryJobForUser((int)$job_id, (int)$user->id);
            } catch (\Throwable $e) {
                $response->error($e->getMessage(), 400);
            }

            $response->success([
                'message' => 'Invoice export queue job retried',
                'job' => $job,
            ]);
        }, [
            'economic_invoice_export' => 'Retry failed queue job for economic invoice export'
        ]);
    }

    private function isEconomicTransferQueueAvailable(): bool
    {
        return class_exists('\\classes\\economic_transfer_executor')
            && class_exists('\\classes\\economic_transfer_queue_schema_bootstrap')
            && class_exists(economic_transfer_queue::class);
    }

    /**
     * @throws \Exception
     */
    private function exportOrderDraftSynchronously(int $order_id, int $user_id): array
    {
        $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');
        }
        (new 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);

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

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

    /**
     * @throws \Exception
     */
    private function exportOrderInvoiceSynchronously(int $order_id, int $user_id): array
    {
        $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');
        }
        (new 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();
    }

    private 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());
            }
        }
    }

    /**
     * @throws \Exception
     */
    private function addOrderItemToInvoice(users_o $customer, orders_o $order, mixed $order_item, economic_invoice_draft_mo $economic_invoice_draft, int $quantity = 1): void
    {
        $department = $order->getDepartmentByOrderId($order->id);
        $economic_department_id = $department['economic_department_id'] ?? 0;
        $economic_dimension_id = $department['economic_dimension_id'] ?? 0;

        $economic_invoice_draft->addLine(
            (string)$order_item['product']['economic_product_id'],
            (string)$order_item['product']['name'],
            (int)$quantity,
            (int)$order_item['price'],
            0,
            (int)$economic_department_id,
            (int)$economic_dimension_id
        );

        $discount_percentage = round((($order_item['product']['price'] - $order_item['price']) / $order_item['product']['price']) * 100, 0);
        if ($order_item['price'] !== $order_item['product']['price']) {
            $economic_invoice_draft->addLineTEXT('Rabat: ' . ($order_item['price'] - $order_item['product']['price']) . ' DKK (' . $discount_percentage . '%)');
        }

        if ($order_item['reference'] !== '') {
            $economic_invoice_draft->addLineTEXT('Reference:');
            if (str_contains($order_item['reference'], "\n")) {
                foreach (explode("\n", $order_item['reference']) as $line) {
                    $economic_invoice_draft->addLineTEXT('# ' . $line);
                }
            } else {
                $economic_invoice_draft->addLineTEXT('# ' . $order_item['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 ($order_item['notes'] !== '') {
            $economic_invoice_draft->addLineTEXT('Notat:');
            if (str_contains($order_item['notes'], "\n")) {
                foreach (explode("\n", $order_item['notes']) as $line) {
                    $economic_invoice_draft->addLineTEXT('# ' . $line);
                }
            } else {
                $economic_invoice_draft->addLineTEXT('# ' . $order_item['notes']);
            }
        }
    }

    private function addOrderToInvoiceDraft(int $economic_invoice_draft_id, orders_o $order, users_o $customer, array $order_items): object
    {
        $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);
    }

    private function ensureEconomicTransferQueueIsAvailable(): void
    {
        global $response;

        if (!$this->isEconomicTransferQueueAvailable()) {
            $response->error('Economic transfer queue is unavailable in this deployment', 503);
        }
    }

    /**
     * @throws \Exception
     */
    private function assertOrderCanBeExportedToEconomic(int $order_id): void
    {
        $customer = (new orders_o())->getCustomerByOrderId($order_id);
        if (!$customer->exists()) {
            throw new \Exception('Customer not found');
        }

        (new economic())->assertCustomerNumberIsNotDraft((int)$customer->customer_number->value());
    }
}
