<?php

namespace GriffinForms\Includes\Payments;

use GriffinForms\Traits\Singleton;
use GriffinForms\Includes\Helpers\Currency;
use GriffinForms\Settings;

/**
 * Normalizes cart/tax totals from sanitized submission payloads.
 */
class CartBuilder extends \GriffinForms\Format
{
    use Singleton;

    /**
     * @var \GriffinForms\Frontend\Sql\Format
     */
    protected $form_sql;

    /**
     * @var Currency
     */
    protected $currency_helper;

    /**
     * Cache of loaded field objects.
     *
     * @var array<int,\GriffinForms\Items\Field>
     */
    protected $field_cache = [];

    protected function setFormsql(): void
    {
        $this->form_sql = \GriffinForms\Frontend\Sql\Format::getInstance();
    }

    protected function setCurrencyhelper(): void
    {
        $this->currency_helper = Currency::getInstance();
    }

    /**
     * Build a normalized cart from submission payload.
     *
     * @param int   $form_id
     * @param array $submission_data
     * @return array{
     *   items: array,
     *   subtotal: float,
     *   tax_total: float,
     *   tax_lines: array,
     *   total: float,
     *   currency: string,
     *   field_values: array,
     *   field_types: array
     * }
     */
    public function build(int $form_id, array $submission_data): array
    {
        $items       = [];
        $tax_lines   = [];
        $subtotal    = 0.0;
        $tax_total   = 0.0;
        $currency    = $this->currency_helper->sanitizeCurrency($this->settings->getOption('payment_currency', 'USD'));
        $field_types = [];
        $field_values = [];

        if (empty($submission_data)) {
            return [
                'items'        => [],
                'subtotal'     => 0,
                'tax_total'    => 0,
                'tax_lines'    => [],
                'total'        => 0,
                'currency'     => $currency,
                'field_values' => [],
                'field_types'  => [],
            ];
        }

        foreach ($submission_data as $page_fields) {
            if (is_string($page_fields)) {
                $maybe_json = json_decode(wp_unslash($page_fields), true);
                if (json_last_error() === JSON_ERROR_NONE && is_array($maybe_json)) {
                    $page_fields = $maybe_json;
                } else {
                    $maybe_ser = maybe_unserialize($page_fields);
                    if (is_array($maybe_ser)) {
                        $page_fields = $maybe_ser;
                    }
                }
            }
            if (!is_array($page_fields)) {
                continue;
            }
            foreach ($page_fields as $field_id => $value) {
                $field_id = absint($field_id);
                if ($field_id <= 0) {
                    continue;
                }

                $value = $this->normalizePaymentValue($value);

                $field_values[$field_id] = $value;

                if (!isset($field_types[$field_id])) {
                    $field_types[$field_id] = strtolower((string) $this->form_sql->getFieldType($field_id));
                }

                if ($field_types[$field_id] !== 'payment' || !is_array($value) || empty($value['items'])) {
                    continue;
                }

                $field_model    = $this->loadField($field_id);
                $field_settings = $field_model ? ($field_model->getProp('field_settings') ?? []) : [];
                $products_map   = $field_model ? $this->resolveProductsMap($field_model) : [];
                $field_subtotal = 0.0;

                foreach ((array) $value['items'] as $entry) {
                    if (!is_array($entry)) {
                        continue;
                    }

                    $product_id   = sanitize_text_field($entry['product_id'] ?? '');
                    if ($product_id === '') {
                        continue;
                    }

                    $product_def = isset($products_map[$product_id]) && is_array($products_map[$product_id])
                        ? $products_map[$product_id]
                        : [];
                    $product_name = sanitize_text_field($product_def['name'] ?? ($entry['product_name'] ?? $product_id));
                    $unit_price   = isset($product_def['price'])
                        ? (float) $product_def['price']
                        : (isset($entry['unit_price']) ? (float) $entry['unit_price'] : 0.0);

                    $quantity = absint($entry['quantity'] ?? 0);
                    if ($quantity <= 0) {
                        $quantity = isset($product_def['default_qty']) ? absint($product_def['default_qty']) : 1;
                    }
                    if ($quantity <= 0) {
                        $quantity = 1;
                    }

                    if (!empty($product_def)) {
                        $allow_qty = !empty($product_def['allow_quantity']) && !empty($field_settings['allow_quantity_controls']);
                        $min_qty   = isset($product_def['min_qty']) ? absint($product_def['min_qty']) : 1;
                        $max_qty   = isset($product_def['max_qty']) ? absint($product_def['max_qty']) : 0;

                        if (!$allow_qty) {
                            $quantity = isset($product_def['default_qty']) ? absint($product_def['default_qty']) : 1;
                            if ($quantity <= 0) {
                                $quantity = 1;
                            }
                        } else {
                            if ($min_qty > 0 && $quantity < $min_qty) {
                                $quantity = $min_qty;
                            }
                            if ($max_qty > 0 && $quantity > $max_qty) {
                                $quantity = $max_qty;
                            }
                        }
                    }

                    $line_total = isset($entry['total'])
                        ? (float) $entry['total']
                        : ($unit_price * $quantity);
                    if ($line_total <= 0 && $unit_price > 0) {
                        $line_total = $unit_price * $quantity;
                    }

                    $items[] = [
                        'field_id'    => $field_id,
                        'product_id'  => $product_id,
                        'label'       => $product_name,
                        'quantity'    => $quantity,
                        'unit_price'  => $unit_price,
                        'subtotal'    => $line_total,
                        'currency'    => $this->currency_helper->sanitizeCurrency($entry['currency'] ?? $currency),
                        'description' => sanitize_text_field($product_def['description'] ?? ($entry['description'] ?? ($entry['product_description'] ?? ''))),
                        'image'       => esc_url_raw($product_def['image'] ?? ($entry['image'] ?? ($entry['product_image'] ?? ''))),
                    ];

                    $field_subtotal += $line_total;
                }

                if ($field_subtotal <= 0) {
                    continue;
                }

                $subtotal += $field_subtotal;
                $tax_settings = $this->resolveTaxSettings($field_settings);

                if ($tax_settings['enabled']) {
                    $tax_amount = $this->calculateTaxAmount($tax_settings, $field_subtotal);
                    if ($tax_amount > 0) {
                        $tax_total += $tax_amount;
                        $tax_lines[] = array(
                            'field_id' => $field_id,
                            'label'    => $tax_settings['label'],
                            'amount'   => $tax_amount,
                            'type'     => $tax_settings['type'],
                            'value'    => $tax_settings['value'],
                            'registration_label' => $tax_settings['registration_label'],
                            'registration_number' => $tax_settings['registration_number'],
                        );
                    }
                }
            }
        }

        return [
            'items'        => $items,
            'subtotal'     => $subtotal,
            'tax_total'    => $tax_total,
            'tax_lines'    => $tax_lines,
            'total'        => $subtotal + $tax_total,
            'currency'     => $currency,
            'field_values' => $field_values,
            'field_types'  => $field_types,
        ];
    }

    /**
     * Build an associative map of products keyed by SKU/ID.
     *
     * @param mixed $field_options
     * @return array<string,array>
     */
    protected function buildProductsMap($field_options): array
    {
        if (!is_array($field_options)) {
            return [];
        }

        $map = [];
        foreach ($field_options as $product) {
            if (!is_array($product)) {
                continue;
            }
            $product_id = isset($product['id']) ? sanitize_text_field($product['id']) : '';
            if ($product_id === '') {
                continue;
            }
            $map[$product_id] = $product;
        }

        return $map;
    }

    protected function resolveProductsMap(\GriffinForms\Items\Field $field_model): array
    {
        $products_map = $this->buildProductsMap($field_model->getProp('field_options'));
        if (!empty($products_map)) {
            return $products_map;
        }

        $field_settings = $field_model->getProp('field_settings');
        if (is_array($field_settings) && !empty($field_settings['products']) && is_array($field_settings['products'])) {
            return $this->buildProductsMap($field_settings['products']);
        }

        return [];
    }

    protected function normalizePaymentValue($value)
    {
        if (is_string($value)) {
            $decoded = json_decode(wp_unslash($value), true);
            if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
                return $this->normalizePaymentValue($decoded);
            }

            $maybe = maybe_unserialize(wp_unslash($value));
            if (is_array($maybe)) {
                return $this->normalizePaymentValue($maybe);
            }

            return $value;
        }

        if (!is_array($value)) {
            return $value;
        }

        if (
            array_keys($value) === range(0, count($value) - 1)
            && count($value) === 1
            && !$this->looksLikePaymentItemsList($value)
        ) {
            return $this->normalizePaymentValue(reset($value));
        }

        foreach ($value as $key => $entry) {
            if (is_string($entry) || is_array($entry)) {
                $value[$key] = $this->normalizePaymentValue($entry);
            }
        }

        return $value;
    }

    protected function looksLikePaymentItemsList(array $value): bool
    {
        if ($value === [] || array_keys($value) !== range(0, count($value) - 1)) {
            return false;
        }

        $sample = current($value);
        if (!is_array($sample)) {
            return false;
        }

        return isset($sample['product_id']) || isset($sample['product_name']) || isset($sample['unit_price']) || isset($sample['total']);
    }

    /**
     * Resolve tax configuration for a payment field.
     */
    protected function resolveTaxSettings(array $field_settings): array
    {
        $mode = isset($field_settings['tax_mode']) ? sanitize_key($field_settings['tax_mode']) : 'inherit';
        if ($mode === 'custom') {
            $type = isset($field_settings['tax_rate_type']) ? sanitize_key($field_settings['tax_rate_type']) : 'percentage';
            $value = isset($field_settings['tax_rate_value']) ? (float) $field_settings['tax_rate_value'] : 0;
            $label = isset($field_settings['tax_display_label']) ? sanitize_text_field($field_settings['tax_display_label']) : '';
            $registration_label = isset($field_settings['tax_registration_label']) ? sanitize_text_field($field_settings['tax_registration_label']) : '';
            $registration_number = isset($field_settings['tax_registration_number']) ? sanitize_text_field($field_settings['tax_registration_number']) : '';

            return [
                'enabled' => $value > 0,
                'type'    => in_array($type, ['percentage', 'fixed'], true) ? $type : 'percentage',
                'value'   => $value,
                'label'   => $label !== '' ? $label : __('Tax', 'griffinforms-form-builder'),
                'registration_label' => $registration_label,
                'registration_number' => $registration_number,
            ];
        }

        $settings = Settings::getInstance();
        $enabled = (bool) $settings->getOption('tax_enabled', 0);
        $type    = sanitize_key($settings->getOption('tax_rate_type', 'percentage'));
        $value   = (float) $settings->getOption('tax_rate_value', 0);
        $label   = sanitize_text_field($settings->getOption('tax_display_label', __('Tax', 'griffinforms-form-builder')));
        $registration_label = sanitize_text_field($settings->getOption('tax_registration_label', ''));
        $registration_number = sanitize_text_field($settings->getOption('tax_registration_number', ''));

        return [
            'enabled' => $enabled && $value > 0,
            'type'    => in_array($type, ['percentage', 'fixed'], true) ? $type : 'percentage',
            'value'   => $value,
            'label'   => $label !== '' ? $label : __('Tax', 'griffinforms-form-builder'),
            'registration_label' => $registration_label,
            'registration_number' => $registration_number,
        ];
    }

    /**
     * Calculate tax amount for a subtotal.
     */
    protected function calculateTaxAmount(array $tax_settings, float $subtotal): float
    {
        if ($tax_settings['type'] === 'percentage') {
            return $subtotal * ((float) $tax_settings['value'] / 100);
        }

        return (float) $tax_settings['value'];
    }

    /**
     * Lazy-load Field objects.
     */
    protected function loadField(int $field_id): ?\GriffinForms\Items\Field
    {
        if (isset($this->field_cache[$field_id])) {
            return $this->field_cache[$field_id];
        }

        $field = new \GriffinForms\Items\Field();
        if (!$field->loadItem($field_id)) {
            $this->field_cache[$field_id] = null;
            return null;
        }

        $this->field_cache[$field_id] = $field;
        return $field;
    }
}
