<?php

declare(strict_types=1);

function cart_reservation_ttl_seconds(): int
{
    $ttl = (int) config('app.cart_reservation_ttl_seconds', 86400);

    return max(300, $ttl);
}

function cart_current_user_id(): int
{
    $user = auth_current_user();
    if (!is_array($user)) {
        return 0;
    }

    $id = (int) ($user['id'] ?? 0);

    return $id > 0 ? $id : 0;
}

function cart_reservations_ensure_table(PDO $pdo): void
{
    if (table_exists($pdo, 'cart_reservations')) {
        return;
    }

    $sql = 'CREATE TABLE IF NOT EXISTS ' . qi('cart_reservations') . ' ('
        . qi('id') . ' BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, '
        . qi('user_id') . ' BIGINT UNSIGNED NOT NULL, '
        . qi('article_id') . ' BIGINT UNSIGNED NOT NULL, '
        . qi('qty') . ' INT UNSIGNED NOT NULL DEFAULT 0, '
        . qi('expires_at') . ' DATETIME NOT NULL, '
        . qi('created_at') . ' DATETIME NOT NULL, '
        . qi('updated_at') . ' DATETIME NOT NULL, '
        . 'PRIMARY KEY (' . qi('id') . '), '
        . 'UNIQUE KEY ' . qi('uniq_user_article') . ' (' . qi('user_id') . ', ' . qi('article_id') . '), '
        . 'KEY ' . qi('idx_article_expires') . ' (' . qi('article_id') . ', ' . qi('expires_at') . '), '
        . 'KEY ' . qi('idx_user_expires') . ' (' . qi('user_id') . ', ' . qi('expires_at') . '), '
        . 'KEY ' . qi('idx_expires') . ' (' . qi('expires_at') . ')'
        . ') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci';

    $pdo->exec($sql);
}

function cart_prune_expired_reservations(): void
{
    $pdo = db();
    if (!table_exists($pdo, 'cart_reservations')) {
        return;
    }

    $pdo->exec(
        'DELETE FROM ' . qi('cart_reservations')
        . ' WHERE ' . qi('expires_at') . ' <= NOW()'
    );
}

function cart_read_session(): array
{
    $cart = $_SESSION['cart'] ?? [];
    if (!is_array($cart)) {
        return [];
    }

    $clean = [];
    foreach ($cart as $articleId => $qty) {
        $id = (int) $articleId;
        $quantity = (int) $qty;
        if ($id > 0 && $quantity > 0) {
            $clean[$id] = $quantity;
        }
    }

    return $clean;
}

function cart_write_session(array $cart): void
{
    $_SESSION['cart'] = $cart;
}

function cart_read_user(int $userId): array
{
    if ($userId <= 0) {
        return [];
    }

    $pdo = db();
    if (!table_exists($pdo, 'cart_reservations')) {
        return [];
    }

    cart_prune_expired_reservations();

    $sql = 'SELECT ' . qi('article_id') . ', ' . qi('qty')
        . ' FROM ' . qi('cart_reservations')
        . ' WHERE ' . qi('user_id') . ' = :user_id'
        . ' AND ' . qi('expires_at') . ' > NOW()';
    $stmt = $pdo->prepare($sql);
    $stmt->bindValue(':user_id', $userId, PDO::PARAM_INT);
    $stmt->execute();

    $cart = [];
    foreach ($stmt->fetchAll() as $row) {
        $articleId = (int) ($row['article_id'] ?? 0);
        $qty = (int) ($row['qty'] ?? 0);
        if ($articleId > 0 && $qty > 0) {
            $cart[$articleId] = $qty;
        }
    }

    return $cart;
}

function cart_stock_limit_map(array $articleIds, ?int $excludeUserId = null): array
{
    $articleIds = array_values(array_unique(array_filter(
        array_map('intval', $articleIds),
        static fn (int $id): bool => $id > 0
    )));
    if ($articleIds === []) {
        return [];
    }

    $articles = repo_find_articles_by_ids($articleIds);
    $reservedByOthers = repo_reserved_qty_by_article($articleIds, $excludeUserId);
    $limits = [];

    foreach ($articleIds as $articleId) {
        $rawStock = (int) ($articles[$articleId]['stock'] ?? 0);
        $reserved = (int) ($reservedByOthers[$articleId] ?? 0);
        $limits[$articleId] = max(0, $rawStock - $reserved);
    }

    return $limits;
}

function cart_normalize_with_stock(array $cart, ?int $excludeUserId = null): array
{
    if ($cart === []) {
        return [];
    }

    $limits = cart_stock_limit_map(array_keys($cart), $excludeUserId);
    $normalized = [];

    foreach ($cart as $articleId => $qty) {
        $id = (int) $articleId;
        $quantity = (int) $qty;
        if ($id <= 0 || $quantity <= 0) {
            continue;
        }

        $limit = (int) ($limits[$id] ?? 0);
        if ($limit <= 0) {
            continue;
        }

        $normalized[$id] = min($limit, $quantity);
    }

    return $normalized;
}

function cart_write_user(int $userId, array $cart): array
{
    if ($userId <= 0) {
        return [];
    }

    $pdo = db();
    cart_reservations_ensure_table($pdo);
    cart_prune_expired_reservations();

    $normalized = cart_normalize_with_stock($cart, $userId);
    $now = date('Y-m-d H:i:s');
    $expiresAt = date('Y-m-d H:i:s', time() + cart_reservation_ttl_seconds());

    $pdo->beginTransaction();

    try {
        $upsertSql = 'INSERT INTO ' . qi('cart_reservations') . ' ('
            . qi('user_id') . ', ' . qi('article_id') . ', ' . qi('qty') . ', '
            . qi('expires_at') . ', ' . qi('created_at') . ', ' . qi('updated_at')
            . ') VALUES (:user_id, :article_id, :qty, :expires_at, :created_at, :updated_at) '
            . 'ON DUPLICATE KEY UPDATE '
            . qi('qty') . ' = VALUES(' . qi('qty') . '), '
            . qi('expires_at') . ' = VALUES(' . qi('expires_at') . '), '
            . qi('updated_at') . ' = VALUES(' . qi('updated_at') . ')';

        if ($normalized !== []) {
            $upsertStmt = $pdo->prepare($upsertSql);

            foreach ($normalized as $articleId => $qty) {
                $upsertStmt->bindValue(':user_id', $userId, PDO::PARAM_INT);
                $upsertStmt->bindValue(':article_id', (int) $articleId, PDO::PARAM_INT);
                $upsertStmt->bindValue(':qty', (int) $qty, PDO::PARAM_INT);
                $upsertStmt->bindValue(':expires_at', $expiresAt, PDO::PARAM_STR);
                $upsertStmt->bindValue(':created_at', $now, PDO::PARAM_STR);
                $upsertStmt->bindValue(':updated_at', $now, PDO::PARAM_STR);
                $upsertStmt->execute();
            }
        }

        if ($normalized === []) {
            $deleteSql = 'DELETE FROM ' . qi('cart_reservations')
                . ' WHERE ' . qi('user_id') . ' = :user_id';
            $deleteStmt = $pdo->prepare($deleteSql);
            $deleteStmt->bindValue(':user_id', $userId, PDO::PARAM_INT);
            $deleteStmt->execute();
        } else {
            $placeholders = [];
            $params = [':user_id' => $userId];
            $index = 0;
            foreach (array_keys($normalized) as $articleId) {
                $key = ':article_' . $index;
                $placeholders[] = $key;
                $params[$key] = (int) $articleId;
                $index++;
            }

            $deleteSql = 'DELETE FROM ' . qi('cart_reservations')
                . ' WHERE ' . qi('user_id') . ' = :user_id'
                . ' AND ' . qi('article_id') . ' NOT IN (' . implode(', ', $placeholders) . ')';
            $deleteStmt = $pdo->prepare($deleteSql);
            foreach ($params as $name => $value) {
                $deleteStmt->bindValue($name, $value, PDO::PARAM_INT);
            }
            $deleteStmt->execute();
        }

        $pdo->commit();
    } catch (Throwable $exception) {
        if ($pdo->inTransaction()) {
            $pdo->rollBack();
        }

        throw $exception;
    }

    return $normalized;
}

function cart_read(): array
{
    $userId = cart_current_user_id();
    if ($userId > 0) {
        return cart_read_user($userId);
    }

    return cart_read_session();
}

function cart_write(array $cart): void
{
    $userId = cart_current_user_id();
    if ($userId > 0) {
        cart_write_user($userId, $cart);
        return;
    }

    cart_write_session($cart);
}

function cart_add(int $articleId, int $qty): array
{
    if ($articleId <= 0 || $qty <= 0) {
        throw new InvalidArgumentException('article_id et qty sont requis.');
    }

    $article = repo_find_article_for_cart($articleId);
    if ($article === null) {
        throw new RuntimeException('Article introuvable.');
    }

    $cart = cart_read();
    $existing = (int) ($cart[$articleId] ?? 0);
    $rawStock = (int) ($article['stock'] ?? 0);
    if ($rawStock <= 0) {
        throw new RuntimeException('Article en rupture de stock.');
    }

    $cart[$articleId] = $existing + $qty;
    $cart = cart_normalize_with_stock($cart, cart_current_user_id() ?: null);

    cart_write($cart);

    return cart_payload($cart, cart_current_user_id() ?: null);
}

function cart_payload(array $cart, ?int $userId = null): array
{
    $vatRate = 0.20;

    if ($cart === []) {
        return [
            'items' => [],
            'count' => 0,
            'subtotal_ht' => 0.0,
            'tva_rate' => $vatRate,
            'tva_amount' => 0.0,
            'total_ttc' => 0.0,
            'total' => 0.0,
        ];
    }

    $articleIds = array_keys($cart);
    $articles = repo_find_articles_by_ids($articleIds);
    $stockLimits = cart_stock_limit_map($articleIds, $userId !== null && $userId > 0 ? $userId : null);
    $items = [];
    $count = 0;
    $total = 0.0;

    foreach ($cart as $articleId => $qty) {
        if (!isset($articles[$articleId])) {
            continue;
        }

        $article = $articles[$articleId];
        $unitPrice = (float) ($article['prix'] ?? 0);
        $lineTotal = normalize_decimal($unitPrice * $qty);

        $items[] = [
            'article_id' => (int) $article['id'],
            'reference' => (string) ($article['reference'] ?? ''),
            'nom' => (string) ($article['nom'] ?? ''),
            'prix_unitaire' => normalize_decimal($unitPrice),
            'qty' => (int) $qty,
            'line_total' => $lineTotal,
            'stock' => (int) ($stockLimits[(int) $articleId] ?? 0),
            'image' => (string) ($article['image'] ?? ''),
        ];

        $count += (int) $qty;
        $total += $lineTotal;
    }

    $subtotalHt = normalize_decimal($total);
    $tvaAmount = normalize_decimal($subtotalHt * $vatRate);
    $totalTtc = normalize_decimal($subtotalHt + $tvaAmount);

    return [
        'items' => $items,
        'count' => $count,
        'subtotal_ht' => $subtotalHt,
        'tva_rate' => $vatRate,
        'tva_amount' => $tvaAmount,
        'total_ttc' => $totalTtc,
        'total' => $totalTtc,
    ];
}

function cart_get(): array
{
    $userId = cart_current_user_id();
    $cart = cart_read();

    return cart_payload($cart, $userId > 0 ? $userId : null);
}

function cart_sync(array $lines): array
{
    $cart = [];
    foreach ($lines as $line) {
        if (!is_array($line)) {
            continue;
        }
        $articleId = (int) ($line['article_id'] ?? $line['id'] ?? 0);
        $qty = (int) ($line['qty'] ?? $line['quantity'] ?? 0);
        if ($articleId <= 0 || $qty <= 0) {
            continue;
        }
        $cart[$articleId] = ($cart[$articleId] ?? 0) + $qty;
    }

    cart_write($cart);

    $userId = cart_current_user_id();

    return cart_payload(cart_read(), $userId > 0 ? $userId : null);
}

function cart_remove_article_ids(array $articleIds): void
{
    $cart = cart_read();

    foreach ($articleIds as $articleId) {
        unset($cart[(int) $articleId]);
    }

    cart_write($cart);
}

function cart_clear(): void
{
    cart_write([]);
}

function cart_reserved_qty_for_user(int $userId, array $articleIds): array
{
    if ($userId <= 0) {
        return [];
    }

    $articleIds = array_values(array_unique(array_filter(
        array_map('intval', $articleIds),
        static fn (int $id): bool => $id > 0
    )));
    if ($articleIds === []) {
        return [];
    }

    $pdo = db();
    if (!table_exists($pdo, 'cart_reservations')) {
        return [];
    }

    cart_prune_expired_reservations();

    $placeholders = [];
    $params = [':user_id' => $userId];
    foreach ($articleIds as $index => $articleId) {
        $key = ':article_' . $index;
        $placeholders[] = $key;
        $params[$key] = $articleId;
    }

    $sql = 'SELECT ' . qi('article_id') . ' AS article_id, SUM(' . qi('qty') . ') AS qty '
        . 'FROM ' . qi('cart_reservations')
        . ' WHERE ' . qi('user_id') . ' = :user_id'
        . ' AND ' . qi('expires_at') . ' > NOW()'
        . ' AND ' . qi('article_id') . ' IN (' . implode(', ', $placeholders) . ')'
        . ' GROUP BY ' . qi('article_id');

    $stmt = $pdo->prepare($sql);
    foreach ($params as $name => $value) {
        $stmt->bindValue($name, $value, PDO::PARAM_INT);
    }
    $stmt->execute();

    $map = [];
    foreach ($stmt->fetchAll() as $row) {
        $articleId = (int) ($row['article_id'] ?? 0);
        $qty = (int) ($row['qty'] ?? 0);
        if ($articleId > 0 && $qty > 0) {
            $map[$articleId] = $qty;
        }
    }

    return $map;
}
