import 'dart:convert';

import 'package:drift/drift.dart';
import 'package:niogu_app/core/database/app_database.dart' hide StockCard;
import 'package:niogu_app/core/database/tables/outlet_inventories.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/raw_materials.dart';
import 'package:niogu_app/core/database/tables/receipt_items.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/entities/custom_sync_queue.dart';
import 'package:niogu_app/core/enums/item_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/enums/table_type.dart';
import 'package:niogu_app/core/system/system_setting.dart';
import 'package:niogu_app/core/enums/user_role.dart';
import 'package:niogu_app/core/utils/log_message.dart';
import 'package:niogu_app/features/goods/products/domain/entities/product.dart';
import 'package:niogu_app/features/goods/raw_materials/domain/entities/raw_material.dart';
import 'package:niogu_app/features/stock_in/domain/entities/stock_in.dart';

part 'outlet_inventory_dao.g.dart';

@DriftAccessor(
  tables: [
    Products,
    ProductVariants,
    RawMaterials,
    ReceiptItems,
    OutletInventories,
    StockCards,
    SyncQueues,
  ],
)
class OutletInventoryDao extends DatabaseAccessor<AppDatabase>
    with _$OutletInventoryDaoMixin {
  OutletInventoryDao(AppDatabase db) : super(db);

  Stream<List<DisplayItem>> watchAllItems(ItemType itemType) async* {
    final outletId = await SystemSetting.getCurrentOutletId();

    final List<Join> joins = [];

    final List<Column> columns = [
      outletInventories.localId,
      outletInventories.itemId,
      outletInventories.stock,
      outletInventories.costPrice,
      outletInventories.purchasePrice,
      units.name,
      outletInventories.sellingPrice,
    ];

    if (itemType == ItemType.product_variant) {
      joins.addAll([
        innerJoin(
          productVariants,
          productVariants.localId.equalsExp(outletInventories.itemId) &
              outletInventories.itemType.equals(ItemType.product_variant.type),
        ),
        innerJoin(
          products,
          products.localId.equalsExp(productVariants.productId),
        ),
        innerJoin(units, units.localId.equalsExp(products.unitId)),
      ]);

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

    final query = selectOnly(outletInventories).join(joins)
      ..addColumns(columns)
      ..where(
        itemType == ItemType.product_variant
            ? outletInventories.stockType.equals(StockType.fixed.type)
            : const Constant(true),
      )
      ..where(
        outletInventories.outletId.equals(outletId!) &
            outletInventories.deletedAt.isNull(),
      )
      ..where(
        itemType == ItemType.product_variant
            ? productVariants.deletedAt.isNull()
            : const Constant(true),
      )
      ..where(
        itemType == ItemType.product_variant
            ? products.deletedAt.isNull()
            : const Constant(true),
      )
      ..where(
        itemType == ItemType.raw_material
            ? rawMaterials.deletedAt.isNull()
            : const Constant(true),
      );

    yield* query.watch().map((rows) {
      final Map<String, DisplayItem> groupedMap = {};

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

        final itemId = row.read(outletInventories.itemId)!;

        bool hasVariant = false;

        List<String> productImages = [];

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

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

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

        final String? localImagePath =
            productImages.isNotEmpty && productImages.last.isNotEmpty
            ? productImages.last
            : null;

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

        final String? variantName =
            itemType == ItemType.product_variant && hasVariant
            ? row.read(productVariants.name)
            : null;

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

        final double costPrice = row.read(outletInventories.costPrice) ?? 0.0;

        final double purchasePrice =
            row.read(outletInventories.purchasePrice) ?? 0.0;

        final String unit = row.read(units.name) ?? '';

        final double sellingPrice =
            row.read(outletInventories.sellingPrice) ?? 0.0;

        groupedMap[outletInventoryId] = DisplayItem(
          id: outletInventoryId,
          itemId: itemId,
          localImagePath: localImagePath,
          name: name,
          variantName: variantName,
          remainingStock: remainingStock,
          costPrice: costPrice,
          purchasePrice: purchasePrice,
          unit: unit,
          sellingPrice: sellingPrice,
        );
      }

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

  Future<void> saveOutletInventory({
    ProductsCompanion? product,
    List<ProductVariantsCompanion>? variants,
    List<DeleteProductVariant>? deleteVariants,
    RawMaterialsCompanion? rawMaterial,
    List<ReceiptItemsCompanion>? items,
    List<DeleteProductUsageMaterial>? deleteItems,
    required List<OutletInventoriesCompanion> inventories,
    required List<StockCardsCompanion> cards,
    bool modeChange = false,
    String? modeBeforeChange,
    String? modeAfterChange,
  }) async {
    try {
      await transaction(() async {
        final now = DateTime.now();

        final currentUserRole = await SystemSetting.getCurrentUserRole();

        final isDeleteByOwner = currentUserRole == UserRole.owner;

        await batch((batch) {
          if (product != null && variants != null) {
            batch.insert(
              products,
              product,
              onConflict: DoUpdate((old) {
                return product.copyWith(
                  syncStatus: Value(SyncStatus.dirty.status),
                  createdAt: const Value.absent(),
                  updatedAt: Value(now),
                );
              }),
            );

            CustomSyncQueue.addToSyncQueue(
              type: TableType.products,
              id: product.localId.value,
              action: SyncAction.update_or_create,
              batch: batch,
              syncQueues: syncQueues,
              data: {
                'uuid': product.localId.value,
                'category_id': product.categoryId.value,
                'unit_id': product.unitId.value,
                'name': product.name.value,
                'server_image_url': product.localImagePath.value,
                'has_variant': product.hasVariant.value,
                'created_at': now.toIso8601String(),
                'updated_at': now.toIso8601String(),
              },
            );

            if (deleteVariants != null) {
              for (var data in deleteVariants) {
                batch.update(
                  productVariants,
                  ProductVariantsCompanion(
                    syncStatus: Value(SyncStatus.dirty.status),
                    deletedAt: Value(now),
                  ),
                  where: (variant) {
                    return variant.localId.equals(data.productVariantId);
                  },
                );

                CustomSyncQueue.addToSyncQueue(
                  type: TableType.product_variants,
                  id: data.productVariantId,
                  action: SyncAction.delete,
                  batch: batch,
                  syncQueues: syncQueues,
                  data: {
                    'uuid': data.productVariantId,
                    'deleted_at': now.toIso8601String(),
                  },
                );

                batch.update(
                  outletInventories,
                  OutletInventoriesCompanion(
                    isDeleteByOwner: Value(isDeleteByOwner),
                    syncStatus: Value(SyncStatus.dirty.status),
                    deletedAt: Value(now),
                  ),
                  where: (inventory) {
                    return inventory.itemType.equals(
                          ItemType.product_variant.type,
                        ) &
                        inventory.itemId.equals(data.productVariantId);
                  },
                );

                CustomSyncQueue.addToSyncQueue(
                  type: TableType.outlet_inventories,
                  id: data.productVariantId,
                  action: SyncAction.delete,
                  batch: batch,
                  syncQueues: syncQueues,
                  data: {
                    'item_id': data.productVariantId,
                    'item_type': ItemType.product_variant.type,
                    'is_delete_by_owner': isDeleteByOwner,
                    'deleted_at': now.toIso8601String(),
                  },
                );

                batch.update(
                  receiptItems,
                  ReceiptItemsCompanion(
                    syncStatus: Value(SyncStatus.dirty.status),
                    deletedAt: Value(now),
                  ),
                  where: (item) {
                    return item.productVariantId.equals(data.productVariantId);
                  },
                );

                CustomSyncQueue.addToSyncQueue(
                  type: TableType.receipt_items,
                  id: data.productVariantId,
                  action: SyncAction.delete,
                  batch: batch,
                  syncQueues: syncQueues,
                  data: {
                    'product_variant_id': data.productVariantId,
                    'deleted_at': now.toIso8601String(),
                  },
                );
              }
            }

            for (var variant in variants) {
              batch.insert(
                productVariants,
                variant,
                onConflict: DoUpdate((old) {
                  return variant.copyWith(
                    syncStatus: Value(SyncStatus.dirty.status),
                    createdAt: const Value.absent(),
                    updatedAt: Value(now),
                  );
                }),
              );

              CustomSyncQueue.addToSyncQueue(
                type: TableType.product_variants,
                id: variant.localId.value,
                action: SyncAction.update_or_create,
                batch: batch,
                syncQueues: syncQueues,
                data: {
                  'uuid': variant.localId.value,
                  'product_id': variant.productId.value,
                  'name': variant.name.value,
                  'server_image_url': variant.localImagePath.value,
                  'is_product_variant': variant.isProductVariant.value,
                  'created_at': now.toIso8601String(),
                  'updated_at': now.toIso8601String(),
                },
              );
            }
          } else if (rawMaterial != null && items != null) {
            batch.insert(
              rawMaterials,
              rawMaterial,
              onConflict: DoUpdate((old) {
                return rawMaterial.copyWith(
                  syncStatus: Value(SyncStatus.dirty.status),
                  createdAt: const Value.absent(),
                  updatedAt: Value(now),
                );
              }),
            );

            CustomSyncQueue.addToSyncQueue(
              type: TableType.raw_materials,
              id: rawMaterial.localId.value,
              action: SyncAction.update_or_create,
              batch: batch,
              syncQueues: syncQueues,
              data: {
                'uuid': rawMaterial.localId.value,
                'unit_id': rawMaterial.unitId.value,
                'name': rawMaterial.name.value,
                'created_at': now.toIso8601String(),
                'updated_at': now.toIso8601String(),
              },
            );

            if (deleteItems != null) {
              for (var data in deleteItems) {
                batch.update(
                  receiptItems,
                  ReceiptItemsCompanion(
                    syncStatus: Value(SyncStatus.dirty.status),
                    deletedAt: Value(now),
                  ),
                  where: (item) {
                    return item.rawMaterialId.equals(data.rawMaterialId) &
                        item.productVariantId.equals(data.productUsageId);
                  },
                );

                CustomSyncQueue.addToSyncQueue(
                  type: TableType.receipt_items,
                  id: data.rawMaterialId,
                  action: SyncAction.delete,
                  batch: batch,
                  syncQueues: syncQueues,
                  data: {
                    'raw_material_id': data.rawMaterialId,
                    'product_variant_id': data.productUsageId,
                    'deleted_at': now.toIso8601String(),
                  },
                );
              }
            }

            for (final item in items) {
              batch.insert(
                receiptItems,
                item,
                onConflict: DoUpdate((old) {
                  return item.copyWith(
                    quantity: item.quantity,
                    syncStatus: Value(SyncStatus.dirty.status),
                    createdAt: const Value.absent(),
                    updatedAt: Value(now),
                  );
                }),
              );

              CustomSyncQueue.addToSyncQueue(
                type: TableType.receipt_items,
                id: item.localId.value,
                action: SyncAction.update_or_create,
                batch: batch,
                syncQueues: syncQueues,
                data: {
                  'uuid': item.localId.value,
                  'product_variant_id': item.productVariantId.value,
                  'raw_material_id': item.rawMaterialId.value,
                  'quantity': item.quantity.value,
                  'created_at': now.toIso8601String(),
                  'updated_at': now.toIso8601String(),
                },
              );
            }
          }

          for (var inventory in inventories) {
            batch.insert(
              outletInventories,
              inventory,
              onConflict: DoUpdate((old) {
                return inventory.copyWith(
                  syncStatus: Value(SyncStatus.dirty.status),
                  createdAt: const Value.absent(),
                  updatedAt: Value(DateTime.now()),
                );
              }),
            );

            CustomSyncQueue.addToSyncQueue(
              type: TableType.outlet_inventories,
              id: inventory.localId.value,
              action: SyncAction.update_or_create,
              batch: batch,
              syncQueues: syncQueues,
              data: {
                'uuid': inventory.localId.value,
                'outlet_id': inventory.outletId.value,
                'item_type': inventory.itemType.value,
                'item_id': inventory.itemId.value,
                'stock_type': inventory.stockType.value,
                'stock': inventory.stock.value,
                'selling_price': inventory.sellingPrice.value,
                'cost_price': inventory.costPrice.value,
                'purchase_price': inventory.purchasePrice.value,
                'is_delete_by_owner': inventory.isDeleteByOwner.value,
                'created_at': now.toIso8601String(),
                'updated_at': now.toIso8601String(),
              },
            );
          }

          if (cards.isNotEmpty) {
            for (var card in cards) {
              if (modeChange &&
                  modeBeforeChange != null &&
                  modeAfterChange != null) {
                card = card.copyWith(
                  modeChange: Value(true),
                  modeBeforeChange: Value(modeBeforeChange),
                  modeAfterChange: Value(modeAfterChange),
                );
              }
              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,
                  'qty_change': card.qtyChange.value,
                  'current_stock': card.currentStock.value,
                  'cost_price': card.costPrice.value,
                  'stock_type': card.stockType.value,
                  'mode_change': card.modeChange.value,
                  'mode_before_change': card.modeBeforeChange.value,
                  'mode_after_change': card.modeAfterChange.value,
                  'created_at': now.toIso8601String(),
                },
              );
            }
          }
        });
      });
    } catch (e, st) {
      LogMessage.log.e(e.toString(), error: e, stackTrace: st);
      rethrow;
    }
  }

  Future<void> deleteOutletInventory({
    String? productId,
    String? rawMaterialId,
    String? outletId,
    bool canDeleteAll = false,
  }) async {
    try {
      await transaction(() async {
        final now = DateTime.now();

        final currentUserRole = await SystemSetting.getCurrentUserRole();

        final isDeletedByOwner = currentUserRole == UserRole.owner;

        if (productId != null) {
          final productVariantQuery =
              selectOnly(productVariants).join([
                  innerJoin(
                    outletInventories,
                    outletInventories.itemType.equals(
                          ItemType.product_variant.type,
                        ) &
                        outletInventories.itemId.equalsExp(
                          productVariants.localId,
                        ),
                  ),
                ])
                ..addColumns([
                  productVariants.localId,
                  outletInventories.localId,
                  productVariants.serverImageUrl,
                ])
                ..where(productVariants.productId.equals(productId))
                ..where(
                  outletId != null && !canDeleteAll
                      ? outletInventories.outletId.equals(outletId)
                      : const Constant(true),
                )
                ..where(productVariants.deletedAt.isNull())
                ..where(outletInventories.deletedAt.isNull());

          final productVariantRows = await productVariantQuery.get();

          final List<DeleteProduct> deleteProducts = productVariantRows.map((
            row,
          ) {
            final String productVariantId = row.read(productVariants.localId)!;

            final String outletInventoryId = row.read(
              outletInventories.localId,
            )!;

            return DeleteProduct(
              productVariantId: productVariantId,
              outletInventoryId: outletInventoryId,
            );
          }).toList();

          await batch((batch) async {
            for (var data in deleteProducts) {
              batch.update(
                outletInventories,
                OutletInventoriesCompanion(
                  isDeleteByOwner: Value(isDeletedByOwner),
                  syncStatus: Value(SyncStatus.dirty.status),
                  deletedAt: Value(now),
                ),
                where: (inventory) {
                  return inventory.localId.equals(data.outletInventoryId) &
                      inventory.itemType.equals(ItemType.product_variant.type) &
                      inventory.itemId.equals(data.productVariantId);
                },
              );

              CustomSyncQueue.addToSyncQueue(
                type: TableType.outlet_inventories,
                id: data.outletInventoryId,
                action: SyncAction.delete,
                batch: batch,
                syncQueues: syncQueues,
                data: {
                  'uuid': data.outletInventoryId,
                  'item_type': ItemType.product_variant.type,
                  'item_id': data.productVariantId,
                  'is_delete_by_owner': isDeletedByOwner,
                  'deleted_at': now.toIso8601String(),
                },
              );

              if (outletId == null && canDeleteAll) {
                batch.update(
                  productVariants,
                  ProductVariantsCompanion(
                    syncStatus: Value(SyncStatus.dirty.status),
                    deletedAt: Value(now),
                  ),
                  where: (variant) {
                    return variant.localId.equals(data.productVariantId) &
                        variant.productId.equals(productId);
                  },
                );

                CustomSyncQueue.addToSyncQueue(
                  type: TableType.product_variants,
                  id: data.productVariantId,
                  action: SyncAction.delete,
                  batch: batch,
                  syncQueues: syncQueues,
                  data: {
                    'uuid': data.productVariantId,
                    'product_id': productId,
                    'deleted_at': now.toIso8601String(),
                  },
                );
              }
            }

            if (outletId == null && canDeleteAll) {
              batch.update(
                products,
                ProductsCompanion(
                  syncStatus: Value(SyncStatus.dirty.status),
                  deletedAt: Value(now),
                ),
                where: (product) {
                  return product.localId.equals(productId);
                },
              );

              CustomSyncQueue.addToSyncQueue(
                type: TableType.products,
                id: productId,
                action: SyncAction.delete,
                batch: batch,
                syncQueues: syncQueues,
                data: {'uuid': productId, 'deleted_at': now.toIso8601String()},
              );
            }
          });
        } else if (rawMaterialId != null) {
          final rawMaterialQuery =
              selectOnly(rawMaterials).join([
                  innerJoin(
                    outletInventories,
                    outletInventories.itemType.equals(
                          ItemType.raw_material.type,
                        ) &
                        outletInventories.itemId.equalsExp(
                          rawMaterials.localId,
                        ),
                  ),
                  innerJoin(
                    receiptItems,
                    receiptItems.rawMaterialId.equalsExp(rawMaterials.localId),
                  ),
                ])
                ..addColumns([
                  rawMaterials.localId,
                  outletInventories.localId,
                  receiptItems.localId,
                ])
                ..where(rawMaterials.localId.equals(rawMaterialId))
                ..where(
                  outletId != null && !canDeleteAll
                      ? outletInventories.outletId.equals(outletId)
                      : const Constant(true),
                )
                ..where(rawMaterials.deletedAt.isNull())
                ..where(outletInventories.deletedAt.isNull())
                ..where(receiptItems.deletedAt.isNull());

          final rawMaterialRows = await rawMaterialQuery.get();

          final List<DeleteRawMaterial>
          deleteRawMaterials = rawMaterialRows.map((row) {
            final String rawMaterialId = row.read(rawMaterials.localId) ?? '';
            final String outletInventoryId =
                row.read(outletInventories.localId) ?? '';
            final String receiptItemId = row.read(receiptItems.localId) ?? '';

            return DeleteRawMaterial(
              rawMaterialId: rawMaterialId,
              outletInventoryId: outletInventoryId,
              receiptItemId: receiptItemId,
            );
          }).toList();

          await batch((batch) {
            for (var data in deleteRawMaterials) {
              batch.update(
                outletInventories,
                OutletInventoriesCompanion(
                  isDeleteByOwner: Value(isDeletedByOwner),
                  syncStatus: Value(SyncStatus.dirty.status),
                  deletedAt: Value(now),
                ),
                where: (inventory) {
                  return inventory.localId.equals(data.outletInventoryId) &
                      inventory.itemType.equals(ItemType.raw_material.type) &
                      inventory.itemId.equals(data.rawMaterialId);
                },
              );

              CustomSyncQueue.addToSyncQueue(
                type: TableType.outlet_inventories,
                id: data.outletInventoryId,
                action: SyncAction.delete,
                batch: batch,
                syncQueues: syncQueues,
                data: {
                  'uuid': data.outletInventoryId,
                  'item_type': ItemType.raw_material.type,
                  'item_id': data.rawMaterialId,
                  'is_delete_by_owner': isDeletedByOwner,
                  'deleted_at': now.toIso8601String(),
                },
              );

              if (outletId == null && canDeleteAll) {
                batch.update(
                  receiptItems,
                  ReceiptItemsCompanion(
                    syncStatus: Value(SyncStatus.dirty.status),
                    deletedAt: Value(now),
                  ),
                  where: (item) {
                    return item.localId.equals(data.receiptItemId) &
                        item.rawMaterialId.equals(data.rawMaterialId);
                  },
                );

                CustomSyncQueue.addToSyncQueue(
                  type: TableType.receipt_items,
                  id: data.receiptItemId,
                  action: SyncAction.delete,
                  batch: batch,
                  syncQueues: syncQueues,
                  data: {
                    'receipt_item_id': data.receiptItemId,
                    'raw_material_id': data.rawMaterialId,
                    'deleted_at': now.toIso8601String(),
                  },
                );
              }
            }

            if (outletId == null && canDeleteAll) {
              batch.update(
                rawMaterials,
                RawMaterialsCompanion(
                  syncStatus: Value(SyncStatus.dirty.status),
                  deletedAt: Value(now),
                ),
                where: (material) {
                  return material.localId.equals(rawMaterialId);
                },
              );

              CustomSyncQueue.addToSyncQueue(
                type: TableType.raw_materials,
                id: rawMaterialId,
                action: SyncAction.delete,
                batch: batch,
                syncQueues: syncQueues,
                data: {
                  'uuid': rawMaterialId,
                  'deleted_at': now.toIso8601String(),
                },
              );
            }
          });
        }
      });
    } catch (e) {
      rethrow;
    }
  }
}
