import 'dart:convert';
import 'dart:math';

import 'package:drift/drift.dart';
import 'package:intl/intl.dart';
import 'package:niogu_app/core/database/app_database.dart';
import 'package:niogu_app/core/database/tables/orders.dart';
import 'package:niogu_app/core/database/tables/outlet_inventories.dart';
import 'package:niogu_app/core/database/tables/outlets.dart';
import 'package:niogu_app/core/database/tables/product_variants.dart';
import 'package:niogu_app/core/database/tables/products.dart';
import 'package:niogu_app/core/database/tables/purchase_items.dart';
import 'package:niogu_app/core/database/tables/purchases.dart';
import 'package:niogu_app/core/database/tables/raw_materials.dart';
import 'package:niogu_app/core/database/tables/stock_cards.dart';
import 'package:niogu_app/core/database/tables/sync_queues.dart';
import 'package:niogu_app/core/database/tables/transactions.dart';
import 'package:niogu_app/core/database/tables/units.dart';
import 'package:niogu_app/core/database/tables/users.dart';
import 'package:niogu_app/core/entities/custom_sync_queue.dart';
import 'package:niogu_app/core/enums/table_type.dart';
import 'package:niogu_app/core/utils/extension_format.dart';
import 'package:niogu_app/core/enums/item_type.dart';
import 'package:niogu_app/core/enums/stock_card_type.dart';
import 'package:niogu_app/core/enums/stock_type.dart';
import 'package:niogu_app/core/enums/sync_status.dart';
import 'package:niogu_app/core/system/system_setting.dart';
import 'package:niogu_app/core/enums/transaction_reference_type.dart';
import 'package:niogu_app/core/utils/log_message.dart';
import 'package:niogu_app/features/report/stock_report/domain/entities/stok_report.dart';
import 'package:niogu_app/features/stock_in/domain/entities/stock_in.dart';
import 'package:uuid/uuid.dart';

part 'stock_card_dao.g.dart';

@DriftAccessor(
  tables: [
    Outlets,
    Users,
    Units,
    Products,
    ProductVariants,
    RawMaterials,
    Orders,
    Purchases,
    PurchaseItems,
    StockCards,
    OutletInventories,
    Transactions,
    SyncQueues,
  ],
)
class StockCardDao extends DatabaseAccessor<AppDatabase>
    with _$StockCardDaoMixin {
  StockCardDao(AppDatabase db) : super(db);

  Future<DateTime?> _getModeBoundary(String id) async {
    final lastModeChange =
        await (select(stockCards)
              ..where(
                (t) =>
                    t.outletInventoryId.equals(id) & t.modeChange.equals(true),
              )
              ..orderBy([
                (t) => OrderingTerm.desc(t.createdAt),
                (t) => OrderingTerm.desc(t.localId),
              ])
              ..limit(1))
            .getSingleOrNull();

    return lastModeChange?.createdAt;
  }

  bool _isBetween(DateTime date, DateTime startDate, DateTime endDate) {
    return date.isAfter(startDate) &&
        date.isBefore(endDate.add(const Duration(days: 1)));
  }

  Stream<List<StockReportItem>> watchAllStockReports({
    String? outletId,
    ItemType? itemType,
    DateTime? startDate,
    DateTime? endDate,
  }) {
    final bool hasFilter = startDate != null && endDate != null;

    final productUnits = alias(units, 'product_units');

    final rawMaterialUnits = alias(units, 'raw_material_units');

    final query =
        selectOnly(outletInventories).join([
            leftOuterJoin(
              stockCards,
              outletInventories.localId.equalsExp(stockCards.outletInventoryId),
            ),
            leftOuterJoin(
              productVariants,
              outletInventories.itemType.equals(ItemType.product_variant.type) &
                  outletInventories.itemId.equalsExp(productVariants.localId),
            ),
            leftOuterJoin(
              products,
              productVariants.productId.equalsExp(products.localId),
            ),
            leftOuterJoin(
              rawMaterials,
              outletInventories.itemType.equals(ItemType.raw_material.type) &
                  outletInventories.itemId.equalsExp(rawMaterials.localId),
            ),
            leftOuterJoin(
              productUnits,
              products.unitId.equalsExp(productUnits.localId),
            ),
            leftOuterJoin(
              rawMaterialUnits,
              rawMaterials.unitId.equalsExp(rawMaterialUnits.localId),
            ),
          ])
          ..addColumns([
            outletInventories.localId,
            outletInventories.itemType,
            outletInventories.stockType,
            outletInventories.stock,
            products.localId,
            products.name,
            products.hasVariant,
            products.localImagePath,
            productUnits.name,
            productVariants.name,
            productVariants.localImagePath,
            productVariants.isProductVariant,
            rawMaterials.name,
            rawMaterialUnits.name,
            stockCards.type,
            stockCards.qtyChange,
            stockCards.stockType,
            stockCards.createdAt,
            outletInventories.isDeleteByOwner,
            outletInventories.deletedAt,
          ])
          ..orderBy([OrderingTerm.asc(outletInventories.deletedAt)])
          ..where(
            outletId != null
                ? outletInventories.outletId.equals(outletId)
                : const Constant(true),
          )
          ..where(
            itemType != null
                ? outletInventories.itemType.equals(itemType.type)
                : const Constant(true),
          );

    return query.watch().asyncMap((rows) async {
      final Map<String, StockReportItem> groupedMap = {};

      final Map<String, List<MovementTemp>> movementTemps = {};

      final inventoryIds = rows
          .map((row) => row.read(outletInventories.localId)!)
          .toList();

      Map<String, double> initialStockMap = {};

      if (hasFilter && inventoryIds.isNotEmpty) {
        for (final invId in inventoryIds) {
          final lastCardBefore =
              await (select(stockCards)
                    ..where(
                      (t) =>
                          t.outletInventoryId.equals(invId) &
                          t.createdAt.isSmallerThanValue(startDate),
                    )
                    ..orderBy([
                      (t) => OrderingTerm.desc(t.createdAt),
                      (t) => OrderingTerm.desc(t.localId),
                    ])
                    ..limit(1))
                  .getSingleOrNull();

          initialStockMap[invId] = lastCardBefore?.currentStock ?? 0.0;
        }
      }

      for (final row in rows) {
        final invId = row.read(outletInventories.localId)!;

        final itemType = ItemType.values.byName(
          (row.read(outletInventories.itemType) ?? ''),
        );

        final isDeleteByOwner =
            row.read(outletInventories.isDeleteByOwner) ?? false;

        final bool hasBeenDelete =
            row.read(outletInventories.deletedAt) != null;

        final String name = itemType == ItemType.product_variant
            ? row.read(products.name) ?? ''
            : row.read(rawMaterials.name) ?? '';

        String? variantName;

        List<String> productImages = [];

        if (itemType == ItemType.product_variant) {
          final hasVariant = row.read(products.hasVariant) ?? false;

          final isProductVariant =
              row.read(productVariants.isProductVariant) ?? false;

          final dynamic rawImages = row.read(products.localImagePath);

          if ((hasVariant && isProductVariant) ||
              (hasBeenDelete & !hasVariant && isProductVariant))
            variantName = row.read(productVariants.name);

          if (hasVariant && isProductVariant && !hasBeenDelete) {
            final String imagePath =
                row.read(productVariants.localImagePath) ?? '';
            productImages.add(imagePath);
          } else if (!hasVariant && !isProductVariant && !hasBeenDelete) {
            if (rawImages is String) {
              productImages = (jsonDecode(rawImages) as List).cast<String>();
            } else if (rawImages is List) {
              productImages = (rawImages).cast<String>();
            }
          }
        }

        String? localImagePath;

        if (productImages.isNotEmpty && productImages.last.isNotEmpty) {
          localImagePath = productImages.last;
        }

        final currentStockType = StockType.values.byName(
          (row.read(outletInventories.stockType) ?? ''),
        );

        final type = row.read(stockCards.type) ?? '';

        final StockCardType stockCardType = StockCardType.values.byName(type);

        final qtyChange = row.read(stockCards.qtyChange) ?? 0.0;

        final stockType = StockType.values.byName(
          (row.read(stockCards.stockType) ?? ''),
        );

        final bool isUnlimited = currentStockType == StockType.unlimited;

        final masterStock = row.read(outletInventories.stock) ?? 0.0;

        final unit = itemType == ItemType.product_variant
            ? (row.read(productUnits.name) ?? '')
            : (row.read(rawMaterialUnits.name) ?? '');

        final createdAt = row.read(stockCards.createdAt) ?? DateTime.now();

        movementTemps.putIfAbsent(invId, () => []);

        movementTemps[invId]!.add(
          MovementTemp(
            type: stockCardType,
            initialStock: masterStock,
            qty: qtyChange,
            date: createdAt,
            stockType: stockType,
          ),
        );

        groupedMap.putIfAbsent(
          invId,
          () => StockReportItem(
            id: invId,
            itemType: itemType,
            name: name,
            variantName: variantName,
            imageProduct: localImagePath,
            isUnlimited: isUnlimited,
            initialStock: 0.0,
            totalStockIn: 0.0,
            totalStockOut: 0.0,
            lastStock: 0.0,
            unit: unit,
            isDeleteByOwner: isDeleteByOwner,
            hasBeenDelete: hasBeenDelete,
          ),
        );
      }

      final Map<String, double> initialStockMaps = {};

      final Map<String, double> totalStockInMaps = {};

      final Map<String, double> totalStockOutMaps = {};

      final Map<String, double> lastStockMaps = {};

      for (final entry in movementTemps.entries) {
        final invId = entry.key;

        final items = entry.value;

        final boundary = await _getModeBoundary(invId);

        double initialStock = 0.0;

        double totalStockIn = 0.0;

        double totalStockOut = 0.0;

        double lastStock = 0.0;

        for (final item in items) {
          final stockType = item.stockType;

          final qtyChange = item.qty;

          final createdAt = item.date;

          final type = item.type;

          final inRange = hasFilter && boundary != null
              ? createdAt.isAfter(boundary) &&
                    _isBetween(createdAt, startDate, endDate)
              : hasFilter && boundary == null
              ? _isBetween(createdAt, startDate, endDate)
              : !hasFilter && boundary != null
              ? createdAt.isAfter(boundary)
              : true;

          final conditions = [StockCardType.sale, StockCardType.adjustment_out];

          bool incomingCondition = inRange && !conditions.contains(type);

          bool outgoingCondition = inRange && conditions.contains(type);

          bool initialStockFilter = false;
          if (hasFilter) {
            initialStockFilter =
                type == StockCardType.initial_stock &&
                createdAt.isBefore(startDate);

            incomingCondition =
                incomingCondition &&
                stockType == StockType.fixed &&
                _isBetween(createdAt, startDate, endDate) &&
                !initialStockFilter;

            outgoingCondition =
                outgoingCondition && _isBetween(createdAt, startDate, endDate);
          }
          if (incomingCondition) {
            totalStockIn += qtyChange;
          }

          if (outgoingCondition) {
            totalStockOut += qtyChange;
          }

          if (hasFilter) {
            initialStock = initialStockMap[invId] ?? 0.0;

            lastStock = initialStock + totalStockIn;

            if (stockType == StockType.fixed) {
              lastStock = lastStock - totalStockOut;
            }
          } else {
            lastStock = item.initialStock;
          }
        }

        initialStockMaps[invId] = initialStock;

        totalStockInMaps[invId] = totalStockIn;

        totalStockOutMaps[invId] = totalStockOut;

        lastStockMaps[invId] = lastStock;
      }

      for (final entry in groupedMap.entries) {
        final id = entry.key;

        final value = entry.value;

        groupedMap[id] = value.copyWith(
          initialStock: initialStockMaps[id],
          totalStockIn: totalStockInMaps[id],
          totalStockOut: totalStockOutMaps[id],
          lastStock: lastStockMaps[id],
        );
      }

      return groupedMap.values.toList();
    });
  }

  Future<StockReportDetail> getStockReportById({
    required String id,
    required ItemType itemType,
    DateTime? startDate,
    DateTime? endDate,
  }) async {
    final bool hasFilter = startDate != null && endDate != null;

    final List<Join> joins = [
      innerJoin(
        outletInventories,
        stockCards.outletInventoryId.equalsExp(outletInventories.localId),
      ),
      innerJoin(outlets, outlets.localId.equalsExp(outletInventories.outletId)),
      innerJoin(users, users.localId.equalsExp(stockCards.userId)),
    ];

    final List<Column> columns = [];

    if (itemType == ItemType.product_variant) {
      joins.addAll([
        innerJoin(
          productVariants,
          outletInventories.itemType.equals(ItemType.product_variant.type) &
              outletInventories.itemId.equalsExp(productVariants.localId),
        ),
        innerJoin(
          products,
          productVariants.productId.equalsExp(products.localId),
        ),
        innerJoin(units, products.unitId.equalsExp(units.localId)),
      ]);
      columns.addAll([
        productVariants.name,
        products.name,
        products.hasVariant,
        productVariants.isProductVariant,
        outletInventories.deletedAt,
      ]);
    } else {
      joins.addAll([
        innerJoin(
          rawMaterials,
          outletInventories.itemType.equals(ItemType.raw_material.type) &
              outletInventories.itemId.equalsExp(rawMaterials.localId),
        ),
        innerJoin(units, rawMaterials.unitId.equalsExp(units.localId)),
      ]);
      columns.addAll([rawMaterials.name]);
    }

    final boundary = await _getModeBoundary(id);

    final query =
        selectOnly(stockCards).join([
            ...joins,
            leftOuterJoin(
              orders,
              stockCards.type.equals(StockCardType.sale.type) &
                  stockCards.referenceId.equalsExp(orders.localId),
            ),
            leftOuterJoin(
              purchases,
              stockCards.type.equals(StockCardType.purchase.type) &
                  stockCards.referenceId.equalsExp(purchases.localId),
            ),
          ])
          ..addColumns([
            outletInventories.localId,
            outletInventories.stockType,
            outletInventories.stock,
            outlets.name,
            stockCards.type,
            stockCards.qtyChange,
            stockCards.currentStock,
            stockCards.stockType,
            stockCards.modeChange,
            stockCards.modeBeforeChange,
            stockCards.modeAfterChange,
            stockCards.referenceId,
            orders.orderNumber,
            purchases.purchaseNumber,
            stockCards.documentNumber,
            stockCards.notes,
            stockCards.createdAt,
            users.name,
            units.name,
            ...columns,
          ])
          ..orderBy([OrderingTerm.asc(stockCards.createdAt)])
          ..where(stockCards.outletInventoryId.equals(id));

    final results = await query.get();

    final Map<String, StockReportDetail> groupedMap = {};

    Map<String, double> initialStockMap = {};

    if (hasFilter && results.isNotEmpty) {
      final invId = results.first.read(outletInventories.localId)!;

      final lastCardBefore =
          await (select(stockCards)
                ..where(
                  (t) =>
                      t.outletInventoryId.equals(invId) &
                      t.createdAt.isSmallerThanValue(startDate),
                )
                ..orderBy([
                  (t) => OrderingTerm.desc(t.createdAt),
                  (t) => OrderingTerm.desc(t.localId),
                ])
                ..limit(1))
              .getSingleOrNull();

      initialStockMap[invId] = lastCardBefore?.currentStock ?? 0.0;
    }

    for (final row in results) {
      final invId = row.read(outletInventories.localId)!;

      String itemName = '';

      String? variantName;

      if (itemType == ItemType.product_variant) {
        itemName = (row.read(products.name) ?? '');

        final hasVariant = row.read(products.hasVariant) ?? false;

        final isProductVariant =
            row.read(productVariants.isProductVariant) ?? false;

        final bool hasBeenDelete =
            row.read(outletInventories.deletedAt) != null;

        if ((hasVariant && isProductVariant) ||
            (hasBeenDelete & !hasVariant && isProductVariant))
          variantName = row.read(productVariants.name);
      } else {
        itemName = (row.read(rawMaterials.name) ?? '');
      }

      final isUnlimited =
          (row.read(outletInventories.stockType) ?? '') == 'unlimited';

      final initialStock = hasFilter ? (initialStockMap[invId] ?? 0.0) : 0.0;

      final lastStock = row.read(outletInventories.stock) ?? 0.0;

      final createdAt = row.read(stockCards.createdAt)!;

      groupedMap.putIfAbsent(
        invId,
        () => StockReportDetail(
          itemName: itemName,
          variantName: variantName,
          isUnlimited: isUnlimited,
          outletName: row.read(outlets.name) ?? '',
          startDate: startDate,
          endDate: endDate,
          initialStock: initialStock,
          totalStockIn: 0,
          totalStockOut: 0,
          lastStock: lastStock,
          unit: row.read(units.name) ?? '',
          createdAt: createdAt,
          movements: [],
        ),
      );

      final type = row.read(stockCards.type) ?? '';

      final qtyChange = row.read(stockCards.qtyChange) ?? 0.0;

      String description = '';

      double stockIn = 0;

      double stockOut = 0;

      bool isStockIn = false;

      final isMovementUnlimited =
          (row.read(stockCards.stockType) ?? '') == 'unlimited';

      switch (type) {
        case 'initial_stock':
        case 'production':
        case 'purchase':
        case 'transfer_in':
        case 'adjustment_in':
          stockIn = qtyChange;
          isStockIn = true;
          break;

        case 'sale':
        case 'adjustment_out':
          stockOut = qtyChange;
          isStockIn = false;
          break;
      }

      final documentNumber = row.read(stockCards.documentNumber);

      switch (type) {
        case 'initial_stock':
          description = 'Pembuatan Stok Baru';
          break;
        case 'sale':
          final orderNumber = row.read(orders.orderNumber);
          description =
              'Penjualan ${orderNumber != null ? '(${orderNumber.toUniqueNumberCleaner()})' : ''}';
          break;
        case 'production':
          description = 'Produksi';
          break;
        case 'purchase':
          final purchaseNumber = row.read(purchases.purchaseNumber);
          description =
              'Pembelian ${purchaseNumber != null ? '(${purchaseNumber.toUniqueNumberCleaner()})' : ''}';
          break;
        case 'transfer_in':
          description =
              'Transfer Masuk ${documentNumber != null && documentNumber.isNotEmpty ? '(Ref : $documentNumber)' : ''}';
          break;
        case 'adjustment_in':
          description = 'Penyesuaian Masuk';
          break;
        case 'adjustment_out':
          description = 'Penyesuaian Keluar';
          break;
        default:
          description = '';
      }

      final notes = row.read(stockCards.notes);

      final stockCardType = StockCardType.values.byName(type);

      final referenceId = row.read(stockCards.referenceId);

      final modeChange = row.read(stockCards.modeChange) ?? false;

      String? beforeChange = row.read(stockCards.modeBeforeChange);

      if (beforeChange != null) {
        beforeChange = beforeChange == 'unlimited' ? 'Selalu Ada' : 'Tetap';
      }

      String? afterChange = row.read(stockCards.modeAfterChange);

      if (afterChange != null) {
        afterChange = afterChange == 'unlimited' ? 'Selalu Ada' : 'Tetap';
      }

      groupedMap[invId]!.movements.add(
        StockMovement(
          date: createdAt,
          description: description,
          notes: notes,
          userName: row.read(users.name) ?? '',
          type: stockCardType,
          referenceId: referenceId,
          isStockIn: isStockIn,
          isUnlimited: isMovementUnlimited,
          modeChange: modeChange,
          beforeChange: beforeChange,
          afterChange: afterChange,
          stockIn: stockIn,
          stockOut: stockOut,
          remainingStock: row.read(stockCards.currentStock) ?? 0,
        ),
      );
    }

    for (final entry in groupedMap.entries) {
      final invId = entry.key;

      final detail = entry.value;

      double totalIn = 0;

      double totalOut = 0;

      for (final m in detail.movements) {
        final inRange = hasFilter && boundary != null
            ? m.date.isAfter(boundary) && _isBetween(m.date, startDate, endDate)
            : hasFilter && boundary == null
            ? _isBetween(m.date, startDate, endDate)
            : !hasFilter && boundary != null
            ? m.date.isAfter(boundary)
            : true;

        if (inRange) {
          totalIn += m.stockIn;
          totalOut += m.stockOut;
        }
      }

      final lastStock = hasFilter
          ? detail.initialStock + totalIn - totalOut
          : detail.lastStock;

      groupedMap[invId] = detail.copyWith(
        totalStockIn: totalIn,
        totalStockOut: totalOut,
        lastStock: lastStock,
      );
    }

    return groupedMap.values.single;
  }

  Future<String> _generatePurchaseNumber() async {
    final now = DateTime.now();

    final formatter = DateFormat('yyMMdd');

    final dateCode = formatter.format(now);

    final tenantCode = await SystemSetting.getTenantCode();

    final chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';

    final randomPart = List.generate(
      3,
      (index) => chars[Random().nextInt(chars.length)],
    ).join();

    final prefix = 'PO-$tenantCode-$dateCode-';

    final lastPurchase =
        await (select(purchases)
              ..where((t) => t.purchaseNumber.like('$prefix%'))
              ..orderBy([(t) => OrderingTerm.desc(t.purchaseNumber)])
              ..limit(1))
            .getSingleOrNull();

    int nextSequence = 1;

    if (lastPurchase != null) {
      final parts = lastPurchase.purchaseNumber.split('-');
      if (parts.isNotEmpty) {
        final lastPart = parts.last;
        if (lastPart.length >= 5) {
          final lastSeqStr = lastPart.substring(lastPart.length - 5);
          nextSequence = int.parse(lastSeqStr) + 1;
        }
      }
    }

    // Format baru: PO - [TenantCode] - [Tanggal] - [Random3] [Urutan5]
    // Contoh: PO-ZL6B25KQ-260328-XYZ00001
    final finalSequence =
        '$randomPart${nextSequence.toString().padLeft(5, '0')}';

    return '$prefix$finalSequence';
  }

  Future<void> updateStockItem({
    SuppliersCompanion? supplier,
    PurchasesCompanion? purchase,
    required List<PurchaseItemsCompanion> purchaseItem,
    required List<UpdateStockItem> items,
  }) async {
    try {
      await transaction(() async {
        final now = DateTime.now();

        final currentUserId = await SystemSetting.getCurrentUserId();

        await batch((batch) async {
          if (supplier != null) {
            batch.insert(suppliers, supplier);

            CustomSyncQueue.addToSyncQueue(
              type: TableType.suppliers,
              id: supplier.localId.value,
              action: SyncAction.create,
              batch: batch,
              syncQueues: syncQueues,
              data: {
                'uuid': supplier.localId.value,
                'name': supplier.name.value,
                'phone_number': supplier.phoneNumber.value,
                'created_at': now.toIso8601String(),
                'updated_at': now.toIso8601String(),
              },
            );
          }

          if (purchase != null) {
            final purchaseNumber = await _generatePurchaseNumber();

            batch.insert(
              purchases,
              purchase.copyWith(purchaseNumber: Value(purchaseNumber)),
            );

            CustomSyncQueue.addToSyncQueue(
              type: TableType.purchases,
              id: purchase.localId.value,
              action: SyncAction.create,
              batch: batch,
              syncQueues: syncQueues,
              data: {
                'uuid': purchase.localId.value,
                'outlet_id': purchase.outletId.value,
                'outlet_name_snapshot': purchase.outletNameSnapshot.value,
                'purchase_number': purchaseNumber,
                'supplier_id': purchase.supplierId.value,
                'supplier_name_snapshot': purchase.supplierNameSnapshot.value,
                'supplier_phone_number_snapshot':
                    purchase.supplierPhoneNumberSnapshot.value,
                'user_id': purchase.userId.value,
                'admin_name_snapshot': purchase.adminNameSnapshot.value,
                'purchase_status': purchase.purchaseStatus.value,
                'total_purchase': purchase.totalPurchase.value,
                'discount': purchase.discount.value,
                'tax': purchase.tax.value,
                'payment_status': purchase.paymentStatus.value,
                'total_amount': purchase.totalAmount.value,
                'amount_paid': purchase.amountPaid.value,
                'change_amount': purchase.changeAmount.value,
                'under_payment_amount': purchase.underPaymentAmount.value,
                'debt_amount': purchase.debtAmount.value,
                'payment_proof_url': purchase.paymentProofPath.value,
                'notes': purchase.notes.value,
                'created_at': now.toIso8601String(),
                'updated_at': now.toIso8601String(),
              },
            );

            final transaction = TransactionsCompanion(
              localId: Value(const Uuid().v7().toString()),
              outletId: purchase.outletId,
              userId: Value(currentUserId!),
              referenceType: Value(TransactionReferenceType.purchases.type),
              referenceId: purchase.localId,
              referenceNumber: Value(purchaseNumber),
              stakeholder: purchase.supplierNameSnapshot,
              transactionType: Value(TransactionType.expense.type),
              category: Value("Pembelian Barang"),
              amount: purchase.totalAmount,
              note: purchase.notes,
            );

            batch.insert(transactions, transaction);

            CustomSyncQueue.addToSyncQueue(
              type: TableType.transactions,
              id: transaction.localId.value,
              action: SyncAction.create,
              batch: batch,
              syncQueues: syncQueues,
              data: {
                'uuid': transaction.localId.value,
                'outlet_id': transaction.outletId.value,
                'user_id': transaction.userId.value,
                'reference_type': transaction.referenceType.value,
                'reference_id': transaction.referenceId.value,
                'reference_number': transaction.referenceNumber.value,
                'stakeholder': transaction.stakeholder.value,
                'transaction_type': transaction.transactionType.value,
                'category': transaction.category.value,
                'amount': transaction.amount.value,
                'note': transaction.note.value,
                'created_at': now.toIso8601String(),
                'updated_at': now.toIso8601String(),
              },
            );

            for (final item in purchaseItem) {
              batch.insert(purchaseItems, item);

              CustomSyncQueue.addToSyncQueue(
                type: TableType.purchase_items,
                id: item.localId.value,
                action: SyncAction.create,
                batch: batch,
                syncQueues: syncQueues,
                data: {
                  'uuid': item.localId.value,
                  'purchase_id': item.purchaseId.value,
                  'item_type': item.itemType.value,
                  'item_id': item.itemId.value,
                  'quantity': item.quantity.value,
                  'item_image_url_snapshot': item.itemImageSnapshot.value,
                  'item_name_snapshot': item.itemNameSnapshot.value,
                  'item_name_variant_snapshot':
                      item.itemVariantNameSnapshot.value,
                  'purchase_price_snapshot': item.purchasePriceSnaphot.value,
                  'subtotal': item.subtotal.value,
                  'created_at': now.toIso8601String(),
                  'updated_at': now.toIso8601String(),
                },
              );
            }
          }

          for (final item in items) {
            batch.update(
              outletInventories,
              OutletInventoriesCompanion(
                stock: Value(item.updateStock),
                costPrice: Value(item.costPrice),
                purchasePrice: Value(item.purchasePrice),
                syncStatus: Value(SyncStatus.dirty.status),
                updatedAt: Value(DateTime.now()),
              ),
              where: (inventory) {
                return inventory.localId.equals(item.outletInventoryId);
              },
            );

            CustomSyncQueue.addToSyncQueue(
              type: TableType.outlet_inventories,
              id: item.outletInventoryId,
              action: SyncAction.update,
              batch: batch,
              syncQueues: syncQueues,
              data: {
                'uuid': item.outletInventoryId,
                'cost_price': item.costPrice,
                'purchase_price': item.purchasePrice,
                'updated_at': now.toIso8601String(),
              },
            );

            final card = StockCardsCompanion(
              localId: Value(item.id),
              outletInventoryId: Value(item.outletInventoryId),
              userId: Value(currentUserId!),
              type: Value(item.stockCardType.type),
              receiveFromId: Value(item.receiveFromId),
              qtyChange: Value(item.qtyChange.abs()),
              currentStock: Value(item.currentStock),
              stockType: Value(StockType.fixed.type),
              costPrice: Value(item.costPrice),
              referenceId: Value(item.referenceId),
              documentNumber: Value(item.documentNumber),
              notes: Value(item.notes),
              syncStatus: Value(item.syncStatus.status),
            );

            batch.insert(stockCards, card);

            CustomSyncQueue.addToSyncQueue(
              type: TableType.stock_cards,
              id: card.localId.value,
              action: SyncAction.create,
              batch: batch,
              syncQueues: syncQueues,
              data: {
                'uuid': card.localId.value,
                'outlet_inventory_id': card.outletInventoryId.value,
                'user_id': card.userId.value,
                'type': card.type.value,
                'receive_from_id': card.receiveFromId.value,
                'qty_change': card.qtyChange.value,
                'current_stock': card.currentStock.value,
                'stock_type': card.stockType.value,
                'cost_price': card.costPrice.value,
                'reference_id': card.referenceId.value,
                'document_number': card.documentNumber.value,
                'notes': card.notes.value,
                'created_at': now.toIso8601String(),
              },
            );
          }
        });
      });
    } catch (e, st) {
      LogMessage.log.e(e.toString(), error: e, stackTrace: st);
      rethrow;
    }
  }
}
