<?php

namespace customers;

use economic_m;
use Exception;
use objects\users_o;

class economicCustomers extends economic_m
{
    public users_o $users_o;

    public function searchCustomers(string|int $search, string $filter, int $limit = 10, int $page = 1): object
    {
        // Make sure the search string is ready for the API
        $search = urlencode($search);
        // Search for customers
        $url = '/customers?filter=' . $filter . '$like:' . $search . '&pagesize=' . $limit . '&skippages=' . ($page - 1);
        $response = $this->send_request($url, 'GET', '');
        return json_decode($response);
    }

    public function getCustomerName(int $customer_number): string|null
    {
        // Check if the customer name is cached
        $tmp_user = self::getUsersObject()->getUserByCustomerNumber($customer_number);
        $cached = $tmp_user->getCached('economic_customer');
        if ($cached) {
            return $cached->name;
        }
        // Get the customer name from the economic system
        $tmp_user->getCustomerEcocomicData($customer_number);
        $cached = $tmp_user->getCached('economic_customer');
        if ($cached) {
            return $cached->name;
        }
        return null;
    }

    public function getUsersObject(): users_o
    {
        if (!isset($this->users_o)) {
            $this->users_o = new users_o();
        }
        return $this->users_o;
    }

    public function getCustomerId(int $customerNumber): object|bool
    {
        if ($customerNumber <= 0) {
            return false;
        }

        // Check if the customer exists
        $url = '/customers/' . $customerNumber;
        $response = $this->send_request($url, 'GET', '');
        $response = json_decode($response);

        return isset($response->customerNumber) ? $response : false;
    }

    public function getCustomerProduct(int $customer_number, int $product_id): object
    {
        // Get the customer product
        $url = '/customers/' . $customer_number . '/templates/invoiceline/' . $product_id;
        $response = $this->send_request($url, 'GET', '');
        return json_decode($response);
    }

    public function getCustomerDiscountPercentage(int $customer_number): int
    {
        // If the customer number is 0, return 0
        if ($customer_number === 0) {
            return 0;
        }
        // The discount is global, but e-conomic resolves it through a product-specific
        // invoice-line template. For foreign-currency customers some templates can fail
        // if that product has no price in the customer currency, so try a few products
        // before falling back to zero.
        $products = $this->getCustomerProducts($customer_number, 10);
        foreach ($this->extractCustomerProductNumbers($products) as $product_number) {
            try {
                $discount = $this->getCustomerProductDiscount($customer_number, $product_number);
                return (int)($discount->discountPercentage ?? 0);
            } catch (\RuntimeException $exception) {
                if (!$this->isMissingCurrencyPriceLookupError($exception)) {
                    throw $exception;
                }
            }
        }

        return 0;
    }

    /**
     * @return int[]
     */
    private function extractCustomerProductNumbers(object $products): array
    {
        if (!isset($products->collection) || !is_array($products->collection)) {
            return [];
        }

        $product_numbers = [];
        foreach ( $products->collection as $product ) {
            $product_number = $product->product->productNumber ?? null;
            if ($product_number === null || $product_number === '') {
                continue;
            }
            $product_numbers[] = (int)$product_number;
        }

        return array_values(array_unique($product_numbers));
    }

    private function isMissingCurrencyPriceLookupError(\RuntimeException $exception): bool
    {
        $message = $exception->getMessage();

        return str_contains($message, 'No price in currency')
            && str_contains($message, 'can be found for the product');
    }

    public function getCustomerProducts(int $customer_number, int $limit = 10, int $page = 1): object
    {
        // Get the customer products
        $url = '/customers/' . $customer_number . '/templates/invoiceline/?pagesize=' . $limit . '&skippages=' . ($page - 1);
        $response = $this->send_request($url, 'GET', '');
        return json_decode($response);
    }

    public function getCustomerProductDiscount(int $customer_number, int $product_id)
    {
        // Get the customer global discount
        $url = '/customers/' . $customer_number . '/templates/invoiceline/' . $product_id;
        $response = $this->send_request($url, 'GET', '');
        $response = json_decode($response);
        return $response;
    }

    /**
     * Get the list of customers
     * @param int $page
     * @param int $limit
     * @param string|null $search
     * @param mixed $barred_filter Supports true/false values (bool, 1/0, true/false, barred/active)
     * @return object The list of customers
     * @throws Exception
     */
    public function listCustomers(int $page, int $limit, string|null $search = null, mixed $barred_filter = null): object
    {
        // Normalize pagination parameters
        $page = max(1, $page); // Ensure it's at least 1
        $skipPages = $page - 1;

        // Construct the base URL with pagination
        $url = '/customers?pagesize=' . $limit . '&skippages=' . $skipPages;

        // Define filterable property groups
        $likeSupported = [
            'zip', 'customerNumber', 'customerGroup.customerGroupNumber', 'name', 'address',
            'city', 'country', 'email', 'telephoneAndFaxNumber', 'website', 'mobilePhone', 'corporateIdentificationNumber'
        ];

        $filter_parts = [];

        // If a search term is present, build the filter expressions
        if (!empty($search)) {
            // Escape special characters in the search string
            $escapedSearch = str_replace(
                ['$', '(', ')', '*', ',', '[', ']'],
                ['$$', '$(', '$)', '$*', '$,', '$[', '$]'],
                $search
            );

            // Build $like filters
            $filters = [];
            foreach ( $likeSupported as $property ) {
                $filters[] = $property . '$like:' . $escapedSearch;
            }

            // Join the filters with `$or:` and keep grouping explicit for later $and composition
            $filter_parts[] = '(' . implode('$or:', $filters) . ')';
        }

        // Optional barred filter support (all | true/barred | false/active)
        $normalized_barred_filter = $this->normalizeBarredFilter($barred_filter);
        if ($normalized_barred_filter !== null) {
            $filter_parts[] = 'barred$eq:' . ($normalized_barred_filter ? 'true' : 'false');
        }

        if (!empty($filter_parts)) {
            $filter_string = count($filter_parts) === 1
                ? $filter_parts[0]
                : '(' . implode('$and:', $filter_parts) . ')';
            $url .= '&filter=' . urlencode($filter_string);
        }

        // Send the GET request to the API endpoint
        $response = $this->send_request($url, 'GET', '');

        // Validate and decode the response safely
        $responseObject = json_decode($response);
        if ($responseObject === null) {
            throw new Exception('Failed to decode the response from the API');
        }

        if (!is_object($responseObject)) {
            throw new \RuntimeException('Malformed e-conomic customers response: expected JSON object.');
        }

        if (!isset($responseObject->collection) || !is_array($responseObject->collection)) {
            $upstreamMessage = isset($responseObject->message) && is_string($responseObject->message)
                ? trim($responseObject->message)
                : 'Missing collection.';
            throw new \RuntimeException('Malformed e-conomic customers response: ' . $upstreamMessage);
        }

        if (!isset($responseObject->pagination) || !is_object($responseObject->pagination)) {
            throw new \RuntimeException('Malformed e-conomic customers response: missing pagination.');
        }

        if (!isset($responseObject->pagination->results) || !is_numeric($responseObject->pagination->results)) {
            throw new \RuntimeException('Malformed e-conomic customers response: missing pagination results.');
        }

        $responseObject->pagination->results = (int)$responseObject->pagination->results;

        return $responseObject;
    }

    private function normalizeBarredFilter(mixed $value): ?bool
    {
        if ($value === null || $value === '') {
            return null;
        }
        if (is_bool($value)) {
            return $value;
        }
        if (is_int($value)) {
            return $value === 1 ? true : ($value === 0 ? false : null);
        }
        $parsed = strtolower(trim((string)$value));
        return match ($parsed) {
            '1', 'true', 'yes', 'barred', 'only_barred' => true,
            '0', 'false', 'no', 'active', 'not_barred' => false,
            default => null,
        };
    }


}
