<?php

namespace Formatrica\Submission\Integrations\Dispatchers;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

use Formatrica\Submission\Integrations\Integration_Dispatcher_Interface;
use WP_Error;
use WC_Order_Item_Product;

/**
 * Dispatch WooCommerce order creation from form submissions.
 *
 * This dispatcher requires WooCommerce to be active.
 */
final class WooCommerce_Dispatcher implements Integration_Dispatcher_Interface
{
    /**
     * @param array<string,mixed> $settings
     * @param array<string,mixed> $form
     * @param array<string,mixed> $payload
     * @param array<string,mixed> $context
     * @param array<int, array<string,mixed>> $field_meta
     */
    public function dispatch(array $settings, array $form, array $payload, array $context, array $field_meta): void
    {
        if (empty($settings['enabled'])) {
            return;
        }

        if (!class_exists('\WooCommerce') || !function_exists('wc_create_order')) {
            return;
        }

        $line_items = $this->build_line_items($settings, $payload);

        if (empty($line_items)) {
            return;
        }

        $order = wc_create_order();

        if ($order instanceof WP_Error || !$order) {
            return;
        }

        foreach ($line_items as $item) {
            $product = wc_get_product($item['product_id']);

            if (!$product) {
                continue;
            }

            $args = [];

            if (!empty($item['variation_id'])) {
                $variation = wc_get_product($item['variation_id']);
                if ($variation) {
                    $args['variation_id'] = $item['variation_id'];
                    $args['variation'] = $variation->get_attributes();
                }
            }

            if ($item['price'] !== null) {
                $args['totals'] = [
                    'subtotal' => $item['price'] * $item['quantity'],
                    'total' => $item['price'] * $item['quantity'],
                ];
            }

            /** @var WC_Order_Item_Product $order_item */
            $order_item = $order->add_product($product, $item['quantity'], $args);

            if (!empty($item['meta']) && $order_item instanceof WC_Order_Item_Product) {
                foreach ($item['meta'] as $meta_key => $meta_value) {
                    $order_item->add_meta_data($meta_key, $meta_value, true);
                }
            }
        }

        $customer = $this->prepare_customer_payload($settings, $payload);
        $customer_id = $this->resolve_customer($customer, !empty($settings['create_customer']));

        if ($customer_id > 0) {
            $order->set_customer_id($customer_id);
        }

        if (!empty($customer['billing'])) {
            $order->set_address($customer['billing'], 'billing');
        }

        if (!empty($customer['shipping'])) {
            $order->set_address($customer['shipping'], 'shipping');
        }

        $metadata = isset($settings['metadata']) && is_array($settings['metadata'])
            ? $settings['metadata']
            : [];

        foreach ($metadata as $row) {
            if (!is_array($row)) {
                continue;
            }
            $key = isset($row['key']) ? sanitize_text_field((string) $row['key']) : '';
            $field = isset($row['field']) ? sanitize_text_field((string) $row['field']) : '';
            if ($key === '' || $field === '' || !isset($payload[$field])) {
                continue;
            }
            $order->update_meta_data($key, $this->normalise_value($payload[$field]));
        }

        $order_status = isset($settings['order_status']) ? sanitize_text_field((string) $settings['order_status']) : 'pending';

        if (!function_exists('wc_get_order_statuses') || !array_key_exists($order_status, wc_get_order_statuses())) {
            $order_status = 'wc-pending';
        }

        $normalized_status = str_starts_with($order_status, "wc-") ? substr($order_status, 3) : $order_status;
        $order->set_status($normalized_status);

        $order->add_order_note(sprintf(
            /* translators: %s: form title */
            __('Created from Formatrica submission (%s).', 'formatrica'),
            isset($form['title']) ? sanitize_text_field((string) $form['title']) : __('Untitled form', 'formatrica')
        ));

        $order->calculate_totals();
        $order->save();

        /**
         * Fires after a WooCommerce order has been created via form submission.
         *
         * @param int $order_id
         * @param array<string,mixed> $form
         * @param array<string,mixed> $payload
         * @param array<string,mixed> $settings
         * @param array<string,mixed> $context
         */
        do_action(
            'formatrica_woocommerce_order_created',
            $order->get_id(),
            $form,
            $payload,
            $settings,
            $context
        );
    }

    /**
     * @param array<string,mixed> $settings
     * @param array<string,mixed> $payload
     * @return array<int, array<string,mixed>>
     */
    private function build_line_items(array $settings, array $payload): array
    {
        $items = [];

        $product_mode = isset($settings['product_mode']) && $settings['product_mode'] === 'field'
            ? 'field'
            : 'static';

        if ($product_mode === 'static') {
            $static_products = isset($settings['static_products']) && is_array($settings['static_products'])
                ? $settings['static_products']
                : [];

            foreach ($static_products as $row) {
                if (!is_array($row)) {
                    continue;
                }
                $product_id = isset($row['product_id']) ? absint((int) $row['product_id']) : 0;
                $variation_id = isset($row['variation_id']) ? absint((int) $row['variation_id']) : 0;
                $quantity = isset($row['quantity']) ? max(1, (int) $row['quantity']) : 1;

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

                $items[] = [
                    'product_id' => $product_id,
                    'variation_id' => $variation_id,
                    'quantity' => $quantity,
                    'price' => null,
                    'meta' => [],
                ];
            }
        } else {
            $product_field = isset($settings['product_field']) ? (string) $settings['product_field'] : '';
            if ($product_field === '' || !isset($payload[$product_field])) {
                return [];
            }

            $raw = $payload[$product_field];
            $raw_ids = is_array($raw) ? $raw : explode(',', (string) $raw);
            $ids = array_values(array_filter(array_map('absint', array_map('intval', $raw_ids)), static fn($id) => $id > 0));

            if (empty($ids)) {
                return [];
            }

            $quantity = 1;
            $quantity_field = isset($settings['quantity_field']) ? (string) $settings['quantity_field'] : '';
            if ($quantity_field !== '' && isset($payload[$quantity_field])) {
                $quantity = max(1, (int) $payload[$quantity_field]);
            }

            $price = null;
            $price_field = isset($settings['price_field']) ? (string) $settings['price_field'] : '';
            if ($price_field !== '' && isset($payload[$price_field])) {
                $price = $this->sanitize_decimal($payload[$price_field]);
            }

            $variation_id = 0;
            $variation_field = isset($settings['variation_field']) ? (string) $settings['variation_field'] : '';
            if ($variation_field !== '' && isset($payload[$variation_field])) {
                $variation_raw = $payload[$variation_field];
                if (is_array($variation_raw)) {
                    $variation_ids = array_values(array_filter(array_map('absint', array_map('intval', $variation_raw)), static fn($id) => $id > 0));
                    if (!empty($variation_ids)) {
                        $variation_id = $variation_ids[0];
                    }
                } else {
                    $variation_id = absint((int) $variation_raw);
                }
            }

            foreach ($ids as $product_id) {
                $items[] = [
                    'product_id' => $product_id,
                    'variation_id' => $variation_id,
                    'quantity' => $quantity,
                    'price' => $price,
                    'meta' => [],
                ];
            }
        }

        return $items;
    }

    /**
     * Resolve customer data from payload.
     *
     * @param array<string,mixed> $settings
     * @param array<string,mixed> $payload
     * @return array<string,mixed>
     */
    private function prepare_customer_payload(array $settings, array $payload): array
    {
        $customer_settings = isset($settings['customer']) && is_array($settings['customer'])
            ? $settings['customer']
            : [];

        $email = $this->extract_field_value($customer_settings['email_field'] ?? '', $payload);
        $first_name = $this->extract_field_value($customer_settings['first_name_field'] ?? '', $payload);
        $last_name = $this->extract_field_value($customer_settings['last_name_field'] ?? '', $payload);
        $phone = $this->extract_field_value($customer_settings['phone_field'] ?? '', $payload);
        $company = $this->extract_field_value($customer_settings['company_field'] ?? '', $payload);

        $billing_settings = isset($customer_settings['billing']) && is_array($customer_settings['billing'])
            ? $customer_settings['billing']
            : [];

        $billing = [
            'email' => $email,
            'first_name' => $first_name,
            'last_name' => $last_name,
            'phone' => $phone,
            'company' => $company,
            'address_1' => $this->extract_field_value($billing_settings['line1_field'] ?? '', $payload),
            'address_2' => $this->extract_field_value($billing_settings['line2_field'] ?? '', $payload),
            'city' => $this->extract_field_value($billing_settings['city_field'] ?? '', $payload),
            'state' => $this->extract_field_value($billing_settings['state_field'] ?? '', $payload),
            'postcode' => $this->extract_field_value($billing_settings['postcode_field'] ?? '', $payload),
            'country' => $this->extract_field_value($billing_settings['country_field'] ?? '', $payload),
        ];

        $shipping_settings = isset($customer_settings['shipping']) && is_array($customer_settings['shipping'])
            ? $customer_settings['shipping']
            : [];

        $shipping = [];
        $use_billing = !empty($shipping_settings['use_billing']);

        if ($use_billing) {
            $shipping = $billing;
        } else {
            $shipping = [
                'first_name' => $first_name,
                'last_name' => $last_name,
                'address_1' => $this->extract_field_value($shipping_settings['line1_field'] ?? '', $payload),
                'address_2' => $this->extract_field_value($shipping_settings['line2_field'] ?? '', $payload),
                'city' => $this->extract_field_value($shipping_settings['city_field'] ?? '', $payload),
                'state' => $this->extract_field_value($shipping_settings['state_field'] ?? '', $payload),
                'postcode' => $this->extract_field_value($shipping_settings['postcode_field'] ?? '', $payload),
                'country' => $this->extract_field_value($shipping_settings['country_field'] ?? '', $payload),
            ];
        }

        return [
            'email' => $email,
            'first_name' => $first_name,
            'last_name' => $last_name,
            'billing' => $this->filter_empty($billing),
            'shipping' => $this->filter_empty($shipping),
        ];
    }

    /**
     * Attempt to resolve or create a WooCommerce customer.
     *
     * @param array<string,mixed> $customer
     * @param bool $allow_create
     * @return int
     */
    private function resolve_customer(array $customer, bool $allow_create): int
    {
        $email = isset($customer['email']) ? sanitize_email((string) $customer['email']) : '';

        if ($email === '' || !is_email($email)) {
            return 0;
        }

        $user = get_user_by('email', $email);

        if ($user) {
            return (int) $user->ID;
        }

        if (!$allow_create) {
            return 0;
        }

        if (!function_exists('wc_create_new_customer')) {
            return 0;
        }

        $password = wp_generate_password();
        $customer_id = wc_create_new_customer(
            $email,
            '',
            $password,
            [
                'first_name' => sanitize_text_field((string) ($customer['first_name'] ?? '')),
                'last_name' => sanitize_text_field((string) ($customer['last_name'] ?? '')),
            ]
        );

        if ($customer_id instanceof WP_Error) {
            return 0;
        }

        return (int) $customer_id;
    }

    /**
     * @param string $field
     * @param array<string,mixed> $payload
     * @return string
     */
    private function extract_field_value(string $field, array $payload): string
    {
        if ($field === '' || !isset($payload[$field])) {
            return '';
        }

        $value = $payload[$field];

        if (is_array($value)) {
            $value = implode(', ', array_map('sanitize_text_field', array_map('strval', $value)));
        }

        return sanitize_text_field((string) $value);
    }

    /**
     * @param mixed $value
     * @return float|null
     */
    private function sanitize_decimal($value): ?float
    {
        if (is_array($value)) {
            $value = reset($value);
        }

        $value = (string) $value;
        $value = preg_replace('/[^0-9\.\-]/', '', $value);

        if (!is_string($value) || $value === '') {
            return null;
        }

        return (float) $value;
    }

    /**
     * @param mixed $value
     * @return mixed
     */
    private function normalise_value($value)
    {
        if (is_array($value)) {
            return array_map([$this, __FUNCTION__], $value);
        }

        return sanitize_text_field((string) $value);
    }

    /**
     * Remove empty values from arrays recursively.
     *
     * @param array<string,mixed> $data
     * @return array<string,mixed>
     */
    private function filter_empty(array $data): array
    {
        return array_filter(
            array_map(static function ($value) {
                if (is_array($value)) {
                    return array_filter($value, static fn($inner) => $inner !== '' && $inner !== null);
                }
                return $value;
            }, $data),
            static fn($value) => $value !== '' && $value !== null && (!is_array($value) || !empty($value))
        );
    }
}
