<?php
require_once __DIR__ . '/config.php';

$limit = 10;

$mysqli = db();

define('WORK_START_TIME', '11:31:00');
define('WORK_END_TIME', '20:59:59');
define('MATCH_WINDOW_HOURS', 5);

/**
 * Compare phones with emphasis on the last 8 digits.
 * Lalamove often drops the Malaysia country code 60.
 */
function phone_match_details(string $a, string $b): array
{
    $a = preg_replace('/\D+/', '', trim($a));
    $b = preg_replace('/\D+/', '', trim($b));

    if ($a === null) {
        $a = '';
    }
    if ($b === null) {
        $b = '';
    }

    if ($a === '' || $b === '') {
        return [
            'matched' => false,
            'score'   => 0.00,
            'rule'    => 'missing phone'
        ];
    }

    $last8a = (strlen($a) >= 8) ? substr($a, -8) : $a;
    $last8b = (strlen($b) >= 8) ? substr($b, -8) : $b;

    if ($last8a !== '' && $last8a === $last8b && strlen($last8a) === 8) {
        return [
            'matched' => true,
            'score'   => 100.00,
            'rule'    => 'last 8 digits match'
        ];
    }

    return [
        'matched' => false,
        'score'   => 0.00,
        'rule'    => 'phone mismatch'
    ];
}

if (!function_exists('string_similarity_score')) {
function string_similarity_score(string $a, string $b): float
{
    $a = trim($a);
    $b = trim($b);

    if ($a === '' || $b === '') {
        return 0.00;
    }

    similar_text(mb_strtolower($a, 'UTF-8'), mb_strtolower($b, 'UTF-8'), $score);
    return round((float)$score, 2);
}
}

if (!function_exists('address_similarity_score')) {
function address_similarity_score(string $a, string $b): float
{
    return string_similarity_score($a, $b);
}
}

if (!function_exists('parse_dt')) {
function parse_dt(?string $value): ?DateTime
{
    $value = trim((string)$value);
    if ($value === '') {
        return null;
    }

    try {
        return new DateTime($value);
    } catch (Throwable $e) {
        return null;
    }
}
}

function next_work_start_from(DateTime $dt): DateTime
{
    $date = $dt->format('Y-m-d');
    $time = $dt->format('H:i:s');

    if ($time >= '21:00:00') {
        $next = clone $dt;
        $next->modify('+1 day');
        return new DateTime($next->format('Y-m-d') . ' ' . WORK_START_TIME);
    }

    if ($time <= '11:30:59') {
        return new DateTime($date . ' ' . WORK_START_TIME);
    }

    return clone $dt;
}

function effective_match_window(?string $shopifyOrderCreatedAt): array
{
    $created = parse_dt($shopifyOrderCreatedAt);

    if (!$created) {
        return [
            'start' => null,
            'end' => null,
            'label' => 'missing_shopify_order_created_at'
        ];
    }

    $effectiveStart = next_work_start_from($created);
    $effectiveEnd = clone $effectiveStart;

    $sameDayWorkEnd = new DateTime($effectiveStart->format('Y-m-d') . ' ' . WORK_END_TIME);
    $windowEnd = clone $effectiveStart;
    $windowEnd->modify('+' . MATCH_WINDOW_HOURS . ' hours');

    if ($windowEnd > $sameDayWorkEnd) {
        $windowEnd = $sameDayWorkEnd;
    }

    $label = ($effectiveStart->format('Y-m-d H:i:s') === $created->format('Y-m-d H:i:s'))
        ? 'working_hours_forward_only'
        : 'anchored_to_next_work_start';

    return [
        'start' => $effectiveStart,
        'end' => $windowEnd,
        'label' => $label
    ];
}

function lalamove_is_within_window(?string $lalamoveSeenAt, array $window): bool
{
    if (
        !isset($window['start'], $window['end']) ||
        !($window['start'] instanceof DateTime) ||
        !($window['end'] instanceof DateTime)
    ) {
        return false;
    }

    $seenAt = parse_dt($lalamoveSeenAt);
    if (!$seenAt) {
        return false;
    }

    return ($seenAt >= $window['start'] && $seenAt <= $window['end']);
}

/*
 * Pull unmatched Shopify orders.
 */
$sqlShopify = "
SELECT
    so.id,
    so.shopify_order_id,
    so.shopify_order_number,
    so.shopify_name,
    so.financial_status,
    so.fulfillment_status,
    so.customer_name,
    so.customer_phone_normalized,
    so.shipping_phone_normalized,
    so.address_full_text,
    so.order_created_at
FROM shopify_orders so
LEFT JOIN order_matches om
    ON om.shopify_order_row_id = so.id
WHERE so.match_status = 'unmatched'
  AND om.id IS NULL
  AND (so.financial_status IS NULL OR LOWER(so.financial_status) NOT IN ('voided', 'refunded'))
ORDER BY so.order_created_at DESC, so.id DESC
LIMIT ?
";

$stmtShopify = $mysqli->prepare($sqlShopify);
$stmtShopify->bind_param('i', $limit);
$stmtShopify->execute();
$resShopify = $stmtShopify->get_result();

$shopifyOrders = [];
while ($row = $resShopify->fetch_assoc()) {
    $shopifyOrders[] = $row;
}
$stmtShopify->close();

/*
 * Pull recent unmatched Lalamove rows.
 * Limit to rows seen recently so we ignore stale historical data.
 */
$sqlLalamove = "
SELECT
    lo.id,
    lo.lalamove_order_id,
    lo.status,
    lo.recipient_name,
    lo.recipient_phone_normalized,
    lo.destination_short,
    lo.destination_full,
    lo.share_link,
    lo.share_link_found,
    lo.last_seen_at
FROM lalamove_orders lo
LEFT JOIN order_matches om
    ON om.lalamove_order_row_id = lo.id
WHERE om.id IS NULL
  AND lo.last_seen_at >= DATE_SUB(NOW(), INTERVAL 3 DAY)
ORDER BY lo.last_seen_at DESC, lo.id DESC
LIMIT 200
";

$resLalamove = $mysqli->query($sqlLalamove);

$lalamoveOrders = [];
if ($resLalamove) {
    while ($row = $resLalamove->fetch_assoc()) {
        $lalamoveOrders[] = $row;
    }
    $resLalamove->free();
}

$matches = [];
$usedLalamoveIds = [];

foreach ($shopifyOrders as $so) {
    $best = null;

    $shopifyPhone = (string)($so['customer_phone_normalized'] ?: $so['shipping_phone_normalized']);
    $shopifyAddress = (string)$so['address_full_text'];
    $window = effective_match_window($so['order_created_at']);

    foreach ($lalamoveOrders as $lo) {
        if (isset($usedLalamoveIds[(int)$lo['id']])) {
            continue;
        }

        if (!lalamove_is_within_window((string)$lo['last_seen_at'], $window)) {
            continue;
        }

        $phone = phone_match_details($shopifyPhone, (string)$lo['recipient_phone_normalized']);
        $addressScore = address_similarity_score($shopifyAddress, (string)$lo['destination_full']);
        $nameScore = 0.00;
        $overall = round(($phone['score'] * 0.50) + ($addressScore * 0.50), 2);

        $decision = 'rejected';
        $reason = 'Phone last 8 digits mismatch or address below 51%';

        if ($phone['matched'] && $addressScore >= 30.00) {
            $decision = 'auto_matched';
            $reason = $phone['rule'] . ' and address 30% or above within allowed time window';
        }

        $candidate = [
            'shopify_row_id' => (int)$so['id'],
            'shopify_order_id' => (string)$so['shopify_order_id'],
            'shopify_name' => (string)$so['shopify_name'],
            'shopify_order_created_at' => (string)$so['order_created_at'],
            'window_start' => ($window['start'] instanceof DateTime) ? $window['start']->format('Y-m-d H:i:s') : null,
            'window_end' => ($window['end'] instanceof DateTime) ? $window['end']->format('Y-m-d H:i:s') : null,
            'window_rule' => (string)$window['label'],
            'lalamove_row_id' => (int)$lo['id'],
            'lalamove_order_id' => (string)$lo['lalamove_order_id'],
            'lalamove_last_seen_at' => (string)$lo['last_seen_at'],
            'phone_match' => (bool)$phone['matched'],
            'phone_score' => (float)$phone['score'],
            'phone_rule' => (string)$phone['rule'],
            'address_score' => (float)$addressScore,
            'name_score' => (float)$nameScore,
            'overall_score' => (float)$overall,
            'decision' => (string)$decision,
            'reason' => (string)$reason,
            'share_link_found' => !empty($lo['share_link_found']),
            'share_link' => (string)$lo['share_link']
        ];

        if ($best === null || $candidate['overall_score'] > $best['overall_score']) {
            $best = $candidate;
        }
    }

    if ($best === null) {
        $matches[] = [
            'shopify_row_id' => (int)$so['id'],
            'shopify_order_id' => (string)$so['shopify_order_id'],
            'shopify_name' => (string)$so['shopify_name'],
            'shopify_order_created_at' => (string)$so['order_created_at'],
            'window_start' => ($window['start'] instanceof DateTime) ? $window['start']->format('Y-m-d H:i:s') : null,
            'window_end' => ($window['end'] instanceof DateTime) ? $window['end']->format('Y-m-d H:i:s') : null,
            'window_rule' => (string)$window['label'],
            'decision' => 'no_candidate',
            'reason' => 'No unmatched Lalamove order found within allowed time window'
        ];
        continue;
    }

    $matches[] = $best;

    if ($best['decision'] === 'rejected') {
        continue;
    }

    $insert = $mysqli->prepare("
        INSERT INTO order_matches
        (shopify_order_row_id, lalamove_order_row_id, phone_match, phone_score, address_score, name_score, overall_score,
         match_decision, match_reason, matched_by, matched_at, share_link, tracking_sent)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'system', NOW(), ?, 0)
    ");

    if (!$insert) {
        continue;
    }

    $phoneMatchInt = $best['phone_match'] ? 1 : 0;

    $insert->bind_param(
        'iiiddddsss',
        $best['shopify_row_id'],
        $best['lalamove_row_id'],
        $phoneMatchInt,
        $best['phone_score'],
        $best['address_score'],
        $best['name_score'],
        $best['overall_score'],
        $best['decision'],
        $best['reason'],
        $best['share_link']
    );
    $insert->execute();
    $insert->close();

    $usedLalamoveIds[$best['lalamove_row_id']] = true;

    if ($best['decision'] === 'auto_matched') {
        $upd = $mysqli->prepare("UPDATE shopify_orders SET match_status = 'matched' WHERE id = ?");
        if ($upd) {
            $upd->bind_param('i', $best['shopify_row_id']);
            $upd->execute();
            $upd->close();
        }
    }
}

json_response([
    'ok' => true,
    'count' => count($matches),
    'matches' => $matches
]);