<?php
declare(strict_types=1);

/**
 * Debug helper for probing Apaczka order_send payload variants for a single order.
 *
 * Usage examples:
 *   php tools/apaczka_probe_order.php --order-id=21 --use-remote
 *   php tools/apaczka_probe_order.php --order-id=21 --service-id=41 --use-remote
 */

const DEFAULT_ORDER_ID = 0;
const DEFAULT_TIMEOUT_SECONDS = 30;

$options = parseOptions($argv);
if (($options['order_id'] ?? 0) <= 0) {
    fwrite(STDERR, "Missing --order-id=<int>\n");
    exit(1);
}

$basePath = dirname(__DIR__);
registerAutoloader($basePath);
$env = parse_ini_file($basePath . '/.env');
if (!is_array($env)) {
    fwrite(STDERR, "Cannot read .env\n");
    exit(1);
}

[$pdo, $hostUsed] = openDatabase($env, !empty($options['use_remote']));
$order = fetchOrder($pdo, (int) $options['order_id']);
if ($order === null) {
    fwrite(STDERR, "Order not found: " . (int) $options['order_id'] . "\n");
    exit(1);
}

$addresses = fetchAddresses($pdo, (int) $order['id']);
$sender = fetchSenderAddress($pdo);
$mapping = fetchDeliveryMapping($pdo, $order);

[$appId, $appSecret] = fetchApaczkaCredentials($pdo, $env);
$client = new App\Modules\Settings\ApaczkaApiClient();
$services = $client->getServiceStructure($appId, $appSecret);
$serviceIndex = buildServiceIndex($services);

$basePayload = buildBasePayload($order, $addresses, $sender, $mapping);
$candidates = buildCandidates($basePayload, $mapping, $serviceIndex, $options);

echo "DB host: {$hostUsed}\n";
echo "Order: #" . (int) $order['id'] . " source=" . (string) ($order['source'] ?? '') . " source_order_id=" . (string) ($order['source_order_id'] ?? '') . "\n";
echo "Mapped service: " . (string) ($mapping['provider_service_id'] ?? '(none)') . " (" . (string) ($mapping['provider_service_name'] ?? '') . ")\n";
echo "Receiver point: " . (string) ($basePayload['receiver_point_id'] ?? '(none)') . "\n";
echo "Candidates: " . count($candidates) . "\n\n";

$attempt = 0;
$success = false;
foreach ($candidates as $candidate) {
    $attempt++;
    $label = buildCandidateLabel($candidate, $serviceIndex);
    echo "[{$attempt}] {$label}\n";

    $apiPayload = buildApiPayloadFromCandidate($basePayload, $candidate);
    try {
        $response = $client->sendOrder($appId, $appSecret, $apiPayload);
        $orderResponse = is_array($response['response']['order'] ?? null) ? $response['response']['order'] : [];
        $apaczkaOrderId = (string) ($orderResponse['id'] ?? '');
        $waybill = (string) ($orderResponse['waybill_number'] ?? '');
        echo "  OK: order_id=" . ($apaczkaOrderId !== '' ? $apaczkaOrderId : '-') . " waybill=" . ($waybill !== '' ? $waybill : '-') . "\n";
        $success = true;

        if (empty($options['continue_on_success'])) {
            break;
        }
    } catch (Throwable $exception) {
        echo "  ERR: " . trim($exception->getMessage()) . "\n";
    }
}

echo "\nResult: " . ($success ? "SUCCESS" : "NO SUCCESS") . "\n";
exit($success ? 0 : 2);

/**
 * @return array<string, mixed>
 */
function parseOptions(array $argv): array
{
    $options = [
        'order_id' => DEFAULT_ORDER_ID,
        'service_id' => '',
        'use_remote' => false,
        'continue_on_success' => false,
    ];

    foreach (array_slice($argv, 1) as $arg) {
        if (preg_match('/^--order-id=(\d+)$/', (string) $arg, $m) === 1) {
            $options['order_id'] = (int) $m[1];
            continue;
        }
        if (preg_match('/^--service-id=([A-Za-z0-9_-]+)$/', (string) $arg, $m) === 1) {
            $options['service_id'] = (string) $m[1];
            continue;
        }
        if ((string) $arg === '--use-remote') {
            $options['use_remote'] = true;
            continue;
        }
        if ((string) $arg === '--continue-on-success') {
            $options['continue_on_success'] = true;
            continue;
        }
    }

    return $options;
}

function registerAutoloader(string $basePath): void
{
    spl_autoload_register(static function (string $class) use ($basePath): void {
        $prefix = 'App\\';
        if (!str_starts_with($class, $prefix)) {
            return;
        }
        $relative = substr($class, strlen($prefix));
        $file = $basePath . '/src/' . str_replace('\\', '/', $relative) . '.php';
        if (is_file($file)) {
            require $file;
        }
    });
}

/**
 * @return array{0: PDO, 1: string}
 */
function openDatabase(array $env, bool $useRemote): array
{
    $host = $useRemote
        ? (string) ($env['DB_HOST_REMOTE'] ?? ($env['DB_HOST'] ?? '127.0.0.1'))
        : (string) ($env['DB_HOST'] ?? '127.0.0.1');
    $port = (string) ($env['DB_PORT'] ?? '3306');
    $db = (string) ($env['DB_DATABASE'] ?? '');
    $user = (string) ($env['DB_USERNAME'] ?? '');
    $pass = (string) ($env['DB_PASSWORD'] ?? '');
    $dsn = 'mysql:host=' . $host . ';port=' . $port . ';dbname=' . $db . ';charset=utf8mb4';
    $pdo = new PDO($dsn, $user, $pass, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
    return [$pdo, $host];
}

/**
 * @return array<string, mixed>|null
 */
function fetchOrder(PDO $pdo, int $orderId): ?array
{
    $stmt = $pdo->prepare('SELECT * FROM orders WHERE id = :id LIMIT 1');
    $stmt->execute(['id' => $orderId]);
    $row = $stmt->fetch(PDO::FETCH_ASSOC);
    return is_array($row) ? $row : null;
}

/**
 * @return array<int, array<string, mixed>>
 */
function fetchAddresses(PDO $pdo, int $orderId): array
{
    $stmt = $pdo->prepare('SELECT * FROM order_addresses WHERE order_id = :order_id ORDER BY id ASC');
    $stmt->execute(['order_id' => $orderId]);
    $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
    return is_array($rows) ? $rows : [];
}

/**
 * @return array<string, string>
 */
function fetchSenderAddress(PDO $pdo): array
{
    $stmt = $pdo->prepare('SELECT * FROM company_settings WHERE id = 1 LIMIT 1');
    $stmt->execute();
    $row = $stmt->fetch(PDO::FETCH_ASSOC);
    $data = is_array($row) ? $row : [];

    return [
        'name' => trim((string) (($data['person_name'] ?? '') !== '' ? $data['person_name'] : ($data['company_name'] ?? ''))),
        'line1' => trim((string) ($data['street'] ?? '')),
        'postal_code' => trim((string) ($data['postal_code'] ?? '')),
        'city' => trim((string) ($data['city'] ?? '')),
        'country_code' => strtoupper(trim((string) ($data['country_code'] ?? 'PL'))),
        'phone' => trim((string) ($data['phone'] ?? '')),
        'email' => trim((string) ($data['email'] ?? '')),
    ];
}

/**
 * @param array<string, mixed> $order
 * @return array<string, mixed>|null
 */
function fetchDeliveryMapping(PDO $pdo, array $order): ?array
{
    $source = strtolower(trim((string) ($order['source'] ?? '')));
    $orderMethod = trim((string) ($order['external_carrier_id'] ?? ''));
    if ($source === '' || $orderMethod === '') {
        return null;
    }

    $sourceIntegrationId = $source === 'shoppro' ? max(0, (int) ($order['integration_id'] ?? 0)) : 0;
    $stmt = $pdo->prepare(
        'SELECT *
         FROM carrier_delivery_method_mappings
         WHERE source_system = :source_system
           AND source_integration_id = :source_integration_id
           AND order_delivery_method = :order_delivery_method
         LIMIT 1'
    );
    $stmt->execute([
        'source_system' => $source,
        'source_integration_id' => $sourceIntegrationId,
        'order_delivery_method' => $orderMethod,
    ]);
    $row = $stmt->fetch(PDO::FETCH_ASSOC);
    return is_array($row) ? $row : null;
}

/**
 * @return array{0: string, 1: string}
 */
function fetchApaczkaCredentials(PDO $pdo, array $env): array
{
    $stmt = $pdo->prepare(
        'SELECT a.app_id,
                COALESCE(NULLIF(i.api_key_encrypted, \'\'), a.app_secret_encrypted, a.api_key_encrypted) AS secret_encrypted
         FROM apaczka_integration_settings a
         LEFT JOIN integrations i ON i.id = a.integration_id
         WHERE a.id = 1
         LIMIT 1'
    );
    $stmt->execute();
    $row = $stmt->fetch(PDO::FETCH_ASSOC);
    if (!is_array($row)) {
        throw new RuntimeException('Missing apaczka_integration_settings row.');
    }

    $appId = trim((string) ($row['app_id'] ?? ''));
    $encrypted = trim((string) ($row['secret_encrypted'] ?? ''));
    if ($appId === '' || $encrypted === '') {
        throw new RuntimeException('Missing Apaczka app_id or app_secret.');
    }

    $cipher = new App\Modules\Settings\IntegrationSecretCipher((string) ($env['INTEGRATIONS_SECRET'] ?? ''));
    $appSecret = trim($cipher->decrypt($encrypted));
    if ($appSecret === '') {
        throw new RuntimeException('Cannot decrypt Apaczka app_secret.');
    }

    return [$appId, $appSecret];
}

/**
 * @param array<int, array<string, mixed>> $services
 * @return array<string, array<string, mixed>>
 */
function buildServiceIndex(array $services): array
{
    $result = [];
    foreach ($services as $service) {
        if (!is_array($service)) {
            continue;
        }
        $serviceId = trim((string) ($service['service_id'] ?? $service['id'] ?? ''));
        if ($serviceId === '') {
            continue;
        }
        $result[$serviceId] = $service;
    }

    return $result;
}

/**
 * @param array<string, mixed> $order
 * @param array<int, array<string, mixed>> $addresses
 * @param array<string, string> $sender
 * @param array<string, mixed>|null $mapping
 * @return array<string, mixed>
 */
function buildBasePayload(array $order, array $addresses, array $sender, ?array $mapping): array
{
    $delivery = null;
    $customer = null;
    foreach ($addresses as $address) {
        if (!is_array($address)) {
            continue;
        }
        $type = trim((string) ($address['address_type'] ?? ''));
        if ($type === 'delivery' && $delivery === null) {
            $delivery = $address;
        }
        if ($type === 'customer' && $customer === null) {
            $customer = $address;
        }
    }

    $receiver = is_array($delivery) ? $delivery : (is_array($customer) ? $customer : []);
    $receiverPointId = trim((string) ($receiver['parcel_external_id'] ?? ''));
    $receiverName = trim((string) ($receiver['name'] ?? ''));
    $customerName = trim((string) (($customer['name'] ?? '')));
    if ($receiverPointId !== '' && $customerName !== '') {
        $receiverName = $customerName;
    }
    if ($receiverName === '') {
        $receiverName = 'Klient';
    }

    $totalWithTax = (float) ($order['total_with_tax'] ?? 0);
    $serviceId = trim((string) ($mapping['provider_service_id'] ?? ''));
    if ($serviceId === '') {
        $serviceId = trim((string) ($order['external_carrier_account_id'] ?? ''));
    }

    return [
        'service_id' => $serviceId,
        'source_order_id' => trim((string) ($order['source_order_id'] ?? $order['id'] ?? '')),
        'insurance_cents' => $totalWithTax > 0 ? (int) round($totalWithTax * 100) : 0,
        'receiver_point_id' => $receiverPointId,
        'sender_point_id' => '',
        'receiver' => [
            'name' => $receiverName,
            'line1' => trim((string) ($receiver['street_name'] ?? '')),
            'postal_code' => trim((string) ($receiver['zip_code'] ?? '')),
            'city' => trim((string) ($receiver['city'] ?? '')),
            'country_code' => strtoupper(trim((string) ($receiver['country'] ?? 'PL'))),
            'phone' => trim((string) ($receiver['phone'] ?? '')),
            'email' => trim((string) ($receiver['email'] ?? '')),
        ],
        'sender' => $sender,
        'shipment' => [
            'shipment_type_code' => 'PACZKA',
            'dimension1' => 25,
            'dimension2' => 20,
            'dimension3' => 8,
            'weight' => 1.0,
            'is_nstd' => 0,
        ],
    ];
}

/**
 * @param array<string, mixed> $basePayload
 * @param array<string, mixed>|null $mapping
 * @param array<string, array<string, mixed>> $serviceIndex
 * @param array<string, mixed> $options
 * @return array<int, array<string, string>>
 */
function buildCandidates(array $basePayload, ?array $mapping, array $serviceIndex, array $options): array
{
    $serviceIds = [];
    $forcedServiceId = trim((string) ($options['service_id'] ?? ''));
    if ($forcedServiceId !== '') {
        $serviceIds[] = $forcedServiceId;
    } else {
        $mapped = trim((string) ($basePayload['service_id'] ?? ''));
        if ($mapped !== '') {
            $serviceIds[] = $mapped;
        }
        foreach ($serviceIndex as $serviceId => $service) {
            $supplier = strtoupper(trim((string) ($service['supplier'] ?? '')));
            $doorToPoint = (int) ($service['door_to_point'] ?? 0) === 1;
            $pointToPoint = (int) ($service['point_to_point'] ?? 0) === 1;
            if ($supplier !== 'INPOST') {
                continue;
            }
            if (!$doorToPoint && !$pointToPoint) {
                continue;
            }
            $serviceIds[] = $serviceId;
        }
    }

    $serviceIds = array_values(array_unique(array_filter($serviceIds, static fn(string $v): bool => $v !== '')));
    if ($serviceIds === []) {
        return [];
    }

    $pointModes = ['all_keys', 'point_only', 'foreign_only', 'point_id_only'];
    $result = [];
    foreach ($serviceIds as $serviceId) {
        foreach ($pointModes as $mode) {
            $result[] = [
                'service_id' => $serviceId,
                'point_mode' => $mode,
            ];
        }
    }

    return $result;
}

/**
 * @param array<string, string> $candidate
 * @param array<string, array<string, mixed>> $serviceIndex
 */
function buildCandidateLabel(array $candidate, array $serviceIndex): string
{
    $serviceId = (string) ($candidate['service_id'] ?? '');
    $pointMode = (string) ($candidate['point_mode'] ?? '');
    $service = $serviceIndex[$serviceId] ?? null;
    $serviceName = is_array($service) ? trim((string) ($service['name'] ?? '')) : '';
    return 'service=' . $serviceId
        . ($serviceName !== '' ? ' (' . $serviceName . ')' : '')
        . ' point_mode=' . $pointMode;
}

/**
 * @param array<string, mixed> $basePayload
 * @param array<string, string> $candidate
 * @return array<string, mixed>
 */
function buildApiPayloadFromCandidate(array $basePayload, array $candidate): array
{
    $serviceId = (string) ($candidate['service_id'] ?? '');
    $pointMode = (string) ($candidate['point_mode'] ?? 'all_keys');
    $receiverPointId = trim((string) ($basePayload['receiver_point_id'] ?? ''));
    $senderPointId = trim((string) ($basePayload['sender_point_id'] ?? ''));

    $receiver = (array) ($basePayload['receiver'] ?? []);
    $sender = (array) ($basePayload['sender'] ?? []);
    unset($receiver['point'], $receiver['foreign_address_id'], $receiver['point_id']);
    unset($sender['point'], $sender['foreign_address_id'], $sender['point_id']);

    applyPointMode($receiver, $receiverPointId, $pointMode);
    if ($senderPointId !== '') {
        applyPointMode($sender, $senderPointId, $pointMode);
    }

    $payload = [
        'service_id' => ctype_digit($serviceId) ? (int) $serviceId : $serviceId,
        'address' => [
            'receiver' => $receiver,
            'sender' => $sender,
        ],
        'shipment' => [(array) ($basePayload['shipment'] ?? [])],
        'content' => 'orderPRO ' . (string) ($basePayload['source_order_id'] ?? ''),
        'comment' => 'orderPRO ' . (string) ($basePayload['source_order_id'] ?? ''),
    ];
    $insuranceCents = (int) ($basePayload['insurance_cents'] ?? 0);
    if ($insuranceCents > 0) {
        $payload['shipment_value'] = $insuranceCents;
    }

    return $payload;
}

/**
 * @param array<string, mixed> $node
 */
function applyPointMode(array &$node, string $pointId, string $mode): void
{
    if ($pointId === '') {
        return;
    }

    if ($mode === 'point_only') {
        $node['point'] = $pointId;
        return;
    }
    if ($mode === 'foreign_only') {
        $node['foreign_address_id'] = $pointId;
        return;
    }
    if ($mode === 'point_id_only') {
        $node['point_id'] = $pointId;
        return;
    }

    $node['point'] = $pointId;
    $node['foreign_address_id'] = $pointId;
    $node['point_id'] = $pointId;
}
