<?php

namespace endpoints\orders;

use Exception;
use traits\economic_endpoint_t;

class economic_invoices_booked_endpoint
{
    use economic_endpoint_t;

    private const Q2C_API_URL = 'https://apis.e-conomic.com/q2capi/v5.1.0';
    private const BULK_INVOICE_LINE_BATCH_SIZE = 200;
    private const INVOICE_LINE_REQUEST_CONCURRENCY = 50;

    /**
     * List sent invoices
     * @param string $filter Set the filter using the self::filter method
     * @return object {collection: [order], pagination: {maxPageSize: number, skipPages: number, results: number}}
     */
    public function get(array $filters = [], array $pagination = []): object
    {
        $response = $this->send_request(
            '/invoices/booked/?filter=' . self::filters($filters) . self::pagination($pagination),
            'GET'
        );
        // Return the response as an object
        return json_decode($response);
    }

    /**
     * Get invoice lines
     * @param array $invoice_ids List of invoice ids to get lines for (e.g. [1, 2, 3])
     * @param array $filters Set the filter using the self::filter method
     * @return array {invoice_id: {collection: [invoice_line], pagination: {maxPageSize: number, skipPages: number, results: number}}}
     */
    public function get_invoice_lines(array $invoice_ids, array $filters = []): array
    {
        $invoice_ids = array_values(array_unique(array_filter(array_map('intval', $invoice_ids), static fn(int $invoice_id): bool => $invoice_id > 0)));
        if (empty($invoice_ids)) {
            return [];
        }

        if (!empty($filters)) {
            return $this->get_invoice_lines_via_detail_requests($invoice_ids, $filters);
        }

        return $this->get_invoice_lines_via_bulk_api($invoice_ids);
    }

    private function get_invoice_lines_via_bulk_api(array $invoice_ids): array
    {
        $invoice_lines = array_fill_keys($invoice_ids, []);

        foreach (array_chunk($invoice_ids, self::BULK_INVOICE_LINE_BATCH_SIZE) as $batch) {
            $cursor = null;

            do {
                $url = '/invoices/booked/lines?filter=documentId$in:[' . implode(',', $batch) . ']';
                if ($cursor !== null) {
                    $url .= '&cursor=' . $cursor;
                }

                $handle = $this->create_curl_handle_for_base_url(self::Q2C_API_URL, $url, 'GET');
                $response = curl_exec($handle);
                if (curl_errno($handle)) {
                    $error = curl_error($handle);
                    curl_close($handle);
                    throw new \RuntimeException('Curl error while fetching bulk booked invoice lines: ' . $error);
                }
                curl_close($handle);
                $decoded = json_decode($response);

                if ($decoded === null && json_last_error() !== JSON_ERROR_NONE) {
                    throw new \RuntimeException('Invalid bulk booked invoice lines JSON: ' . json_last_error_msg());
                }

                if (isset($decoded->errorCode) || isset($decoded->message)) {
                    throw new \RuntimeException((string)($decoded->message ?? 'Failed to fetch bulk booked invoice lines.'));
                }

                foreach ((array)($decoded->items ?? []) as $line) {
                    $invoice_id = (int)($line->documentId ?? 0);
                    if (!isset($invoice_lines[$invoice_id])) {
                        continue;
                    }

                    $invoice_lines[$invoice_id][] = $line;
                }

                $cursor = isset($decoded->cursor) && $decoded->cursor !== '' ? (string)$decoded->cursor : null;
            } while ($cursor !== null);
        }

        ksort($invoice_lines);
        return $invoice_lines;
    }

    private function get_invoice_lines_via_detail_requests(array $invoice_ids, array $filters = []): array
    {
        $filter_query = self::filters($filters);
        $multi_handle = curl_multi_init();
        $invoice_lines = [];
        $handles = [];
        $invoice_by_handle = [];
        $next_index = 0;

        try {
            if (defined('CURLMOPT_PIPELINING') && defined('CURLPIPE_MULTIPLEX')) {
                curl_multi_setopt($multi_handle, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX);
            }
            if (defined('CURLMOPT_MAX_HOST_CONNECTIONS')) {
                curl_multi_setopt($multi_handle, CURLMOPT_MAX_HOST_CONNECTIONS, self::INVOICE_LINE_REQUEST_CONCURRENCY);
            }
            if (defined('CURLMOPT_MAX_TOTAL_CONNECTIONS')) {
                curl_multi_setopt($multi_handle, CURLMOPT_MAX_TOTAL_CONNECTIONS, self::INVOICE_LINE_REQUEST_CONCURRENCY);
            }

            do {
                while ($next_index < count($invoice_ids) && count($handles) < self::INVOICE_LINE_REQUEST_CONCURRENCY) {
                    $invoice_id = $invoice_ids[$next_index++];
                    $handle = $this->create_curl_handle(
                        '/invoices/booked/' . $invoice_id . '/?filter=' . $filter_query,
                        'GET'
                    );

                    $handle_id = spl_object_id($handle);
                    $handles[$handle_id] = $handle;
                    $invoice_by_handle[$handle_id] = $invoice_id;
                    curl_multi_add_handle($multi_handle, $handle);
                }

                do {
                    $multi_status = curl_multi_exec($multi_handle, $running_handles);
                } while ($multi_status === CURLM_CALL_MULTI_PERFORM);

                if ($multi_status !== CURLM_OK) {
                    throw new \RuntimeException('Curl multi error: ' . curl_multi_strerror($multi_status));
                }

                while ($completed = curl_multi_info_read($multi_handle)) {
                    $handle = $completed['handle'];
                    $handle_id = spl_object_id($handle);
                    $invoice_id = (int)($invoice_by_handle[$handle_id] ?? 0);

                    if ($completed['result'] !== CURLE_OK) {
                        throw new \RuntimeException(
                            'Curl error for booked invoice ' . $invoice_id . ': ' . curl_error($handle)
                        );
                    }

                    $response = curl_multi_getcontent($handle);
                    $decoded = json_decode($response);
                    if ($decoded === null && json_last_error() !== JSON_ERROR_NONE) {
                        throw new \RuntimeException(
                            'Invalid JSON for booked invoice ' . $invoice_id . ': ' . json_last_error_msg()
                        );
                    }

                    $invoice_lines[$invoice_id] = is_array($decoded->lines ?? null) ? $decoded->lines : [];

                    curl_multi_remove_handle($multi_handle, $handle);
                    curl_close($handle);
                    unset($handles[$handle_id], $invoice_by_handle[$handle_id]);
                }

                if (($running_handles > 0 || $next_index < count($invoice_ids)) && curl_multi_select($multi_handle, 1.0) === -1) {
                    usleep(10000);
                }
            } while ($running_handles > 0 || $next_index < count($invoice_ids));
        } finally {
            foreach ($handles as $handle) {
                curl_multi_remove_handle($multi_handle, $handle);
                curl_close($handle);
            }
            curl_multi_close($multi_handle);
        }

        ksort($invoice_lines);
        return $invoice_lines;
    }

    /**
     * Get booked invoice from external id
     * @param string $external_id External id to get
     * @return int Invoice id
     * @throws Exception If the request fails
     * @throws Exception If the invoice is not found
     */
    public function get_from_external_id(string $external_id): int
    {
        $response = $this->send_request(
            '/invoices/booked?filter=references.other$eq:' . $external_id,
            'GET'
        );
        // Return the response as an object
        $response = json_decode($response);
        //var_dump($response);
        if (isset($response->collection[0])) {
            return $response->collection[0]->bookedInvoiceNumber;
        } else {
            throw new Exception('Invoice booked with external id (' . $external_id . ') not found');
        }
    }

    public function getFromId(int $invoice_id): object
    {
        $response = $this->send_request(
            '/invoices/booked/' . $invoice_id,
            'GET'
        );
        // Return the response as an object
        return json_decode($response);
    }

}
