import 'package:drift/drift.dart';
import 'package:niogu_app/core/database/app_database.dart' hide Outlet;
import 'package:niogu_app/core/database/tables/outlets.dart';
import 'package:niogu_app/core/database/tables/sync_queues.dart';
import 'package:niogu_app/core/database/tables/tenants.dart';
import 'package:niogu_app/core/database/tables/transactions.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/bcrypt.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/user_role.dart';
import 'package:niogu_app/core/utils/log_message.dart';
import 'package:niogu_app/features/outlets/domain/entities/outlet.dart';
import 'package:niogu_app/features/stock_in/domain/entities/stock_in_transfer.dart';

part 'outlet_dao.g.dart';

@DriftAccessor(tables: [Users, Outlets, Transactions, Tenants, SyncQueues])
class OutletDao extends DatabaseAccessor<AppDatabase> with _$OutletDaoMixin {
  OutletDao(AppDatabase db) : super(db);

  Future<bool> getIsMainOutlet() async {
    final outletId = await SystemSetting.getCurrentOutletId();

    final query = selectOnly(outlets)
      ..addColumns([outlets.isMainOutlet])
      ..where(outlets.localId.equals(outletId!))
      ..limit(1);

    final result = await query.getSingle();

    return result.read(outlets.isMainOutlet) ?? false;
  }

  Stream<double> watchOutletCompleteness() async* {
    final currentOutletId = await SystemSetting.getCurrentOutletId();
    final query = selectOnly(outlets)
      ..addColumns([
        outlets.localBannerPath,
        outlets.name,
        outlets.phoneNumber,
        outlets.email,
        outlets.fullAddress,
      ])
      ..where(outlets.localId.equals(currentOutletId!))
      ..limit(1);

    yield* query.watchSingle().map((row) {
      double percent = 0.0;

      if (row.read(outlets.localBannerPath) != null) {
        percent += 0.2;
      }

      if (row.read(outlets.phoneNumber) != null) {
        percent += 0.2;
      }

      if (row.read(outlets.name) != null) {
        percent += 0.2;
      }

      if (row.read(outlets.email) != null) {
        percent += 0.2;
      }

      if (row.read(outlets.fullAddress) != null) {
        percent += 0.2;
      }

      return percent;
    });
  }

  Stream<List<UserDeletedByOutlet>> watchUserDeletedByOutlet() async* {
    final query = selectOnly(users)
      ..addColumns([users.outletId, users.localId.count()])
      ..groupBy([users.outletId])
      ..where(users.deletedAt.isNotNull());

    yield* query.watch().map((rows) {
      return rows.map((row) {
        return UserDeletedByOutlet(
          outletId: row.read(users.outletId)!,
          amount: row.read(users.localId.count()) ?? 0,
        );
      }).toList();
    });
  }

  Stream<int> watchLimitAddOutlet() async* {
    final query = selectOnly(tenants)
      ..addColumns([tenants.limitOutlet])
      ..limit(1);

    yield* query.watchSingle().map((row) {
      return row.read(tenants.limitOutlet)!;
    });
  }

  Stream<List<DisplayOutlet>> watchAllDisplayOutlets() async* {
    final query =
        selectOnly(outlets).join([
            leftOuterJoin(users, users.outletId.equalsExp(outlets.localId)),
            leftOuterJoin(
              transactions,
              transactions.outletId.equalsExp(outlets.localId),
            ),
          ])
          ..addColumns([
            outlets.localId,
            outlets.localBannerPath,
            outlets.name,
            outlets.isMainOutlet,
            outlets.isActive,
            outlets.fullAddress,
            users.localId.count(distinct: true),
            transactions.localId.count(distinct: true),
          ])
          ..groupBy([outlets.localId])
          ..orderBy([OrderingTerm.asc(outlets.createdAt)])
          ..where(outlets.deletedAt.isNull());

    yield* query.watch().map((rows) {
      return rows.map((row) {
        final String id = row.read(outlets.localId)!;

        final String? bannerPath = row.read(outlets.localBannerPath);

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

        final bool isMainOutlet = row.read(outlets.isMainOutlet) ?? false;

        final bool isActive = row.read(outlets.isActive) ?? false;

        final String? fullAddress = row.read(outlets.fullAddress);

        int userStaffAmount =
            (row.read(users.localId.count(distinct: true)) ?? 0);

        final int transactionAmount =
            row.read(transactions.localId.count(distinct: true)) ?? 0;

        return DisplayOutlet(
          id: id,
          bannerPath: bannerPath,
          name: name,
          isMainOutlet: isMainOutlet,
          isActive: isActive,
          fullAddress: fullAddress,
          userStaffAmount: userStaffAmount,
          transactionAmount: transactionAmount,
        );
      }).toList();
    });
  }

  Future<OutletDetail> getOutletById(String id) async {
    final outletQuery =
        selectOnly(outlets).join([
            leftOuterJoin(
              transactions,
              transactions.outletId.equalsExp(outlets.localId),
            ),
          ])
          ..addColumns([
            outlets.localId,
            outlets.localBannerPath,
            outlets.name,
            outlets.phoneNumber,
            outlets.email,
            outlets.fullAddress,
            outlets.latitude,
            outlets.longitude,
            outlets.isMainOutlet,
            outlets.isActive,
            transactions.localId.count(distinct: true),
          ])
          ..groupBy([outlets.localId])
          ..where(outlets.localId.equals(id) & outlets.deletedAt.isNull());

    final tenantQuery = selectOnly(tenants)
      ..addColumns([tenants.businessName])
      ..limit(1);

    final tenantResult = await tenantQuery.getSingle();

    final outletResult = await outletQuery.getSingle();

    final tenantName = tenantResult.read(tenants.businessName) ?? '';

    final String outletId = outletResult.read(outlets.localId)!;

    final String? bannerPath = outletResult.read(outlets.localBannerPath);

    final String name = outletResult.read(outlets.name) ?? '';

    final String phoneNumber = outletResult.read(outlets.phoneNumber) ?? '';

    final String? email = outletResult.read(outlets.email);

    final String? fullAddress = outletResult.read(outlets.fullAddress);

    final double? latitude = outletResult.read(outlets.latitude);

    final double? longitude = outletResult.read(outlets.longitude);

    final bool isMainOutlet = outletResult.read(outlets.isMainOutlet) ?? false;

    final bool isActive = outletResult.read(outlets.isActive) ?? false;

    final int transactionAmount =
        outletResult.read(transactions.localId.count(distinct: true)) ?? 0;

    return OutletDetail(
      id: outletId,
      bannerPath: bannerPath,
      tenantName: tenantName,
      name: name,
      phoneNumber: phoneNumber,
      email: email,
      fullAddress: fullAddress,
      latitude: latitude,
      longitude: longitude,
      isMainOutlet: isMainOutlet,
      isActive: isActive,
      transactionAmount: transactionAmount,
    );
  }

  Future<MainOutlet> getMainOutlet() async {
    final query = selectOnly(outlets)
      ..addColumns([outlets.localId, outlets.name])
      ..where(outlets.isMainOutlet);

    final result = await query.getSingle();

    final String id = result.read(outlets.localId)!;

    final String name = result.read(outlets.name) ?? '';

    return MainOutlet(id: id, name: name);
  }

  Stream<List<StaffAdminDetail>> watchAllStaffAdmins(String outletId) async* {
    final query =
        selectOnly(users).join([
            leftOuterJoin(
              transactions,
              transactions.userId.equalsExp(users.localId),
            ),
          ])
          ..addColumns([
            users.localId,
            users.name,
            users.phoneNumber,
            users.email,
            users.shiftName,
            users.shiftStartTime,
            users.shiftEndTime,
            users.isActive,
            transactions.localId.count(),
          ])
          ..groupBy([users.localId])
          ..where(
            users.outletId.equals(outletId) &
                users.role.equals(UserRole.owner.role).not() &
                users.deletedAt.isNull(),
          );

    yield* query.watch().map((rows) {
      return rows.map((row) {
        final String userId = row.read(users.localId)!;

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

        final String phoneNumber = row.read(users.phoneNumber) ?? '';

        final String email = row.read(users.email) ?? '';

        final int transactionAmount =
            row.read(transactions.localId.count()) ?? 0;

        final String shiftName = row.read(users.shiftName) ?? '';

        final String shiftStartTime = row.read(users.shiftStartTime) ?? '';

        final String shiftEndTime = row.read(users.shiftEndTime) ?? '';

        final bool isActive = row.read(users.isActive) ?? false;

        return StaffAdminDetail(
          id: userId,
          name: name,
          phoneNumber: phoneNumber,
          email: email,
          isActive: isActive,
          transactionAmount: transactionAmount,
          shiftName: shiftName,
          shiftStartTime: shiftStartTime,
          shiftEndTime: shiftEndTime,
        );
      }).toList();
    });
  }

  Future<bool> validatePhoneAndEmail({
    required String phoneNumber,
    String? email,
    String? excludeId,
  }) async {
    final query = selectOnly(outlets)
      ..addColumns([outlets.phoneNumber, outlets.email])
      ..where(
        outlets.phoneNumber.equals(phoneNumber) |
            (email != null
                ? outlets.email.equals(email)
                : const Constant(false)),
      )
      ..where(
        excludeId != null
            ? outlets.localId.isNotValue(excludeId)
            : const Constant(true),
      )
      ..where(outlets.deletedAt.isNull())
      ..limit(1);

    final result = await query.getSingleOrNull();

    return result != null;
  }

  Future<bool> validateAddUserStaff({
    required String firstPhoneNumber,
    required String firstEmail,
    String? secondPhoneNumber,
    String? secondEmail,
  }) async {
    final query = selectOnly(users)
      ..addColumns([users.name, users.phoneNumber, users.email])
      ..where(
        (users.phoneNumber.equals(firstPhoneNumber) |
                (secondPhoneNumber != null
                    ? users.phoneNumber.equals(secondPhoneNumber)
                    : const Constant(false))) |
            (users.email.equals(firstEmail) |
                (secondEmail != null
                    ? users.email.equals(secondEmail)
                    : const Constant(false))),
      )
      ..where(users.deletedAt.isNull())
      ..limit(1);

    final result = await query.getSingleOrNull();

    return result != null;
  }

  Future<void> saveStaffAdmin({
    required String outletId,
    UpsertStaffAdmin? firstStaffAdmin,
    UpsertStaffAdmin? secondStaffAdmin,
  }) async {
    try {
      await transaction(() async {
        await batch((batch) async {
          final now = DateTime.now();

          if (firstStaffAdmin != null) {
            batch.insert(
              users,
              UsersCompanion(
                localId: Value(firstStaffAdmin.id),
                outletId: Value(outletId),
                name: Value(firstStaffAdmin.name),
                email: Value(firstStaffAdmin.email),
                phoneNumber: Value(firstStaffAdmin.phoneNumber),
                passwordHash: firstStaffAdmin.passwordHash != null
                    ? Value(Bcrypt.hashPassword(firstStaffAdmin.passwordHash!))
                    : const Value.absent(),
                role: Value(UserRole.admin.role),
                shiftName: Value(firstStaffAdmin.shiftName),
                shiftStartTime: Value(firstStaffAdmin.shiftStartTime),
                shiftEndTime: Value(firstStaffAdmin.shiftEndTime),
                isActive: Value(firstStaffAdmin.isActive),
              ),
              onConflict: DoUpdate(
                (old) => UsersCompanion(
                  shiftName: Value(firstStaffAdmin.shiftName),
                  shiftStartTime: Value(firstStaffAdmin.shiftStartTime),
                  shiftEndTime: Value(firstStaffAdmin.shiftEndTime),
                  isActive: Value(firstStaffAdmin.isActive),
                  syncStatus: Value(SyncStatus.dirty.status),
                  updatedAt: Value(now),
                ),
              ),
            );

            final existing =
                await (select(syncQueues)..where(
                      (queue) =>
                          queue.tableType.equals(TableType.users.type) &
                          queue.tableLocalId.equals(firstStaffAdmin.id),
                    ))
                    .getSingleOrNull();

            if (existing != null) {
              CustomSyncQueue.updateSyncQueue(
                type: TableType.users,
                id: firstStaffAdmin.id,
                action: SyncAction.update,
                batch: batch,
                syncQueues: syncQueues,
                data: {
                  'uuid': firstStaffAdmin.id,
                  'shift_name': firstStaffAdmin.shiftName,
                  'shift_start_time': firstStaffAdmin.shiftStartTime,
                  'shift_end_time': firstStaffAdmin.shiftEndTime,
                  'is_active': firstStaffAdmin.isActive,
                  'updated_at': now.toIso8601String(),
                },
              );
            } else {
              final passwordHash = firstStaffAdmin.passwordHash;
              CustomSyncQueue.addToSyncQueue(
                type: TableType.users,
                id: firstStaffAdmin.id,
                action: SyncAction.create,
                batch: batch,
                syncQueues: syncQueues,
                data: {
                  'uuid': firstStaffAdmin.id,
                  'outlet_id': outletId,
                  'name': firstStaffAdmin.name,
                  'email': firstStaffAdmin.email,
                  'phone_number': firstStaffAdmin.phoneNumber,
                  ...{
                    if (passwordHash != null)
                      'password': Bcrypt.hashPassword(passwordHash),
                  },
                  'role': UserRole.admin.role,
                  'shift_name': firstStaffAdmin.shiftName,
                  'shift_start_time': firstStaffAdmin.shiftStartTime,
                  'shift_end_time': firstStaffAdmin.shiftEndTime,
                  'is_active': firstStaffAdmin.isActive,
                  'created_at': now.toIso8601String(),
                  'updated_at': now.toIso8601String(),
                },
              );
            }
          }

          if (secondStaffAdmin != null) {
            batch.insert(
              users,
              UsersCompanion(
                localId: Value(secondStaffAdmin.id),
                outletId: Value(outletId),
                name: Value(secondStaffAdmin.name),
                email: Value(secondStaffAdmin.email),
                phoneNumber: Value(secondStaffAdmin.phoneNumber),
                passwordHash: secondStaffAdmin.passwordHash != null
                    ? Value(Bcrypt.hashPassword(secondStaffAdmin.passwordHash!))
                    : const Value.absent(),
                role: Value(UserRole.admin.role),
                shiftName: Value(secondStaffAdmin.shiftName),
                shiftStartTime: Value(secondStaffAdmin.shiftStartTime),
                shiftEndTime: Value(secondStaffAdmin.shiftEndTime),
                isActive: Value(secondStaffAdmin.isActive),
              ),
              onConflict: DoUpdate(
                (old) => UsersCompanion(
                  shiftName: Value(secondStaffAdmin.shiftName),
                  shiftStartTime: Value(secondStaffAdmin.shiftStartTime),
                  shiftEndTime: Value(secondStaffAdmin.shiftEndTime),
                  isActive: Value(secondStaffAdmin.isActive),
                  syncStatus: Value(SyncStatus.dirty.status),
                  updatedAt: Value(now),
                ),
              ),
            );

            final existing =
                await (select(syncQueues)..where(
                      (queue) =>
                          queue.tableType.equals(TableType.users.type) &
                          queue.tableLocalId.equals(secondStaffAdmin.id),
                    ))
                    .getSingleOrNull();

            if (existing != null) {
              CustomSyncQueue.updateSyncQueue(
                type: TableType.users,
                id: secondStaffAdmin.id,
                action: SyncAction.update,
                batch: batch,
                syncQueues: syncQueues,
                data: {
                  'uuid': secondStaffAdmin.id,
                  'shift_name': secondStaffAdmin.shiftName,
                  'shift_start_time': secondStaffAdmin.shiftStartTime,
                  'shift_end_time': secondStaffAdmin.shiftEndTime,
                  'is_active': secondStaffAdmin.isActive,
                  'updated_at': now.toIso8601String(),
                },
              );
            } else {
              final passwordHash = secondStaffAdmin.passwordHash;
              CustomSyncQueue.addToSyncQueue(
                type: TableType.users,
                id: secondStaffAdmin.id,
                action: SyncAction.create,
                batch: batch,
                syncQueues: syncQueues,
                data: {
                  'uuid': secondStaffAdmin.id,
                  'outlet_id': outletId,
                  'name': secondStaffAdmin.name,
                  'email': secondStaffAdmin.email,
                  'phone_number': secondStaffAdmin.phoneNumber,
                  ...{
                    if (passwordHash != null)
                      'password': Bcrypt.hashPassword(passwordHash),
                  },
                  'role': UserRole.admin.role,
                  'shift_name': secondStaffAdmin.shiftName,
                  'shift_start_time': secondStaffAdmin.shiftStartTime,
                  'shift_end_time': secondStaffAdmin.shiftEndTime,
                  'is_active': secondStaffAdmin.isActive,
                  'created_at': now.toIso8601String(),
                  'updated_at': now.toIso8601String(),
                },
              );
            }
          }
        });
      });
    } catch (e, st) {
      LogMessage.log.e(e.toString(), error: e, stackTrace: st);
      rethrow;
    }
  }

  Future<int> deleteStaffAdmin(String userId) async {
    return await transaction(() async {
      final now = DateTime.now();

      final rowsAffected =
          (await update(users)
                ..where((user) => user.localId.equals(userId)))
              .write(UsersCompanion(deletedAt: Value(now)));

      final payload = SyncPayload(
        table: TableType.users.type,
        localId: userId,
        action: SyncAction.delete.action,
        data: {'uuid': userId, 'deleted_at': now.toIso8601String()},
      ).toJson();

      await into(syncQueues).insert(
        SyncQueuesCompanion(
          tableType: Value(TableType.users.type),
          tableLocalId: Value(userId),
          action: Value(SyncAction.delete.action),
          status: Value(SyncStatus.dirty.status),
          payload: Value(payload),
        ),
      );

      return rowsAffected;
    });
  }

  Future<void> saveOutlet(UpsertOutlet upsertOutlet) async {
    try {
      await transaction(() async {
        await batch((batch) async {
          final now = DateTime.now();

          batch.insert(
            outlets,
            OutletsCompanion(
              localId: Value(upsertOutlet.id),
              name: Value(upsertOutlet.name),
              email: Value(upsertOutlet.email),
              phoneNumber: Value(upsertOutlet.phoneNumber),
              fullAddress: Value(upsertOutlet.fullAddress),
              latitude: Value(upsertOutlet.latitude),
              longitude: Value(upsertOutlet.longitude),
              localBannerPath: Value(upsertOutlet.bannerPath),
              isMainOutlet: Value(upsertOutlet.isMainOutlet),
              isActive: Value(upsertOutlet.isActive),
            ),
            onConflict: DoUpdate(
              (old) => OutletsCompanion(
                name: Value(upsertOutlet.name),
                email: Value(upsertOutlet.email),
                phoneNumber: Value(upsertOutlet.phoneNumber),
                fullAddress: Value(upsertOutlet.fullAddress),
                latitude: Value(upsertOutlet.latitude),
                longitude: Value(upsertOutlet.longitude),
                localBannerPath: Value(upsertOutlet.bannerPath),
                isActive: Value(upsertOutlet.isActive),
                syncStatus: Value(SyncStatus.dirty.status),
                updatedAt: Value(now),
              ),
            ),
          );

          final existing =
              await (select(syncQueues)..where(
                    (queue) =>
                        queue.tableType.equals(TableType.outlets.type) &
                        queue.tableLocalId.equals(upsertOutlet.id),
                  ))
                  .getSingleOrNull();

          if (existing != null) {
            CustomSyncQueue.updateSyncQueue(
              type: TableType.outlets,
              id: upsertOutlet.id,
              action: SyncAction.update,
              batch: batch,
              syncQueues: syncQueues,
              data: {
                'uuid': upsertOutlet.id,
                'name': upsertOutlet.name,
                'email': upsertOutlet.email,
                'phone_number': upsertOutlet.phoneNumber,
                'full_address': upsertOutlet.fullAddress,
                'latitude': upsertOutlet.latitude,
                'longitude': upsertOutlet.longitude,
                'server_banner_url': upsertOutlet.bannerPath,
                'is_active': upsertOutlet.isActive,
                'updated_at': now.toIso8601String(),
              },
            );
          } else {
            CustomSyncQueue.addToSyncQueue(
              type: TableType.outlets,
              id: upsertOutlet.id,
              action: SyncAction.create,
              batch: batch,
              syncQueues: syncQueues,
              data: {
                'uuid': upsertOutlet.id,
                'name': upsertOutlet.name,
                'email': upsertOutlet.email,
                'phone_number': upsertOutlet.phoneNumber,
                'full_address': upsertOutlet.fullAddress,
                'latitude': upsertOutlet.latitude,
                'longitude': upsertOutlet.longitude,
                'server_banner_url': upsertOutlet.bannerPath,
                'is_main_outlet': upsertOutlet.isMainOutlet,
                'is_active': upsertOutlet.isActive,
                'created_at': now.toIso8601String(),
                'updated_at': now.toIso8601String(),
              },
            );
          }

          if (upsertOutlet.isMainOutlet) {
            batch.update(
              outlets,
              OutletsCompanion(
                isMainOutlet: Value(upsertOutlet.isMainOutlet),
                syncStatus: Value(SyncStatus.dirty.status),
                updatedAt: Value(now),
              ),
              where: (tbl) => tbl.localId.equals(upsertOutlet.id),
            );

            CustomSyncQueue.addToSyncQueue(
              type: TableType.outlets,
              id: upsertOutlet.id,
              action: SyncAction.update,
              batch: batch,
              syncQueues: syncQueues,
              data: {
                'uuid': upsertOutlet.id,
                'is_main_outlet': upsertOutlet.isMainOutlet,
                'updated_at': now.toIso8601String(),
              },
            );

            batch.update(
              outlets,
              OutletsCompanion(
                isMainOutlet: Value(!upsertOutlet.isMainOutlet),
                syncStatus: Value(SyncStatus.dirty.status),
                updatedAt: Value(now),
              ),
              where: (tbl) => tbl.localId.equals(upsertOutlet.id).not(),
            );
          }

          if (upsertOutlet.firstStaffAdmin != null) {
            final userComp = UsersCompanion(
              localId: Value(upsertOutlet.firstStaffAdmin!.id),
              outletId: Value(upsertOutlet.id),
              name: Value(upsertOutlet.firstStaffAdmin!.name),
              email: Value(upsertOutlet.firstStaffAdmin!.email),
              phoneNumber: Value(upsertOutlet.firstStaffAdmin!.phoneNumber),
              passwordHash: upsertOutlet.firstStaffAdmin!.passwordHash != null
                  ? Value(
                      Bcrypt.hashPassword(
                        upsertOutlet.firstStaffAdmin!.passwordHash!,
                      ),
                    )
                  : const Value.absent(),
              role: Value(UserRole.admin.role),
              shiftName: Value(upsertOutlet.firstStaffAdmin!.shiftName),
              shiftStartTime: Value(
                upsertOutlet.firstStaffAdmin!.shiftStartTime,
              ),
              shiftEndTime: Value(upsertOutlet.firstStaffAdmin!.shiftEndTime),
              isActive: Value(upsertOutlet.firstStaffAdmin!.isActive),
            );

            batch.insert(users, userComp);

            CustomSyncQueue.addToSyncQueue(
              type: TableType.users,
              id: userComp.localId.value,
              action: SyncAction.create,
              batch: batch,
              syncQueues: syncQueues,
              data: {
                'uuid': userComp.localId.value,
                'outlet_id': userComp.outletId.value,
                'name': userComp.name.value,
                'email': userComp.email.value,
                'phone_number': userComp.phoneNumber.value,
                'password': userComp.passwordHash.value,
                'role': userComp.role.value,
                'shift_name': userComp.shiftName.value,
                'shift_start_time': userComp.shiftStartTime.value,
                'shift_end_time': userComp.shiftEndTime.value,
                'is_active': userComp.isActive.value,
                'created_at': now.toIso8601String(),
                'updated_at': now.toIso8601String(),
              },
            );
          }

          if (upsertOutlet.secondStaffAdmin != null) {
            final userComp = UsersCompanion(
              localId: Value(upsertOutlet.secondStaffAdmin!.id),
              outletId: Value(upsertOutlet.id),
              name: Value(upsertOutlet.secondStaffAdmin!.name),
              email: Value(upsertOutlet.secondStaffAdmin!.email),
              phoneNumber: Value(upsertOutlet.secondStaffAdmin!.phoneNumber),
              passwordHash: upsertOutlet.secondStaffAdmin!.passwordHash != null
                  ? Value(
                      Bcrypt.hashPassword(
                        upsertOutlet.secondStaffAdmin!.passwordHash!,
                      ),
                    )
                  : const Value.absent(),
              role: Value(UserRole.admin.role),
              shiftName: Value(upsertOutlet.secondStaffAdmin!.shiftName),
              shiftStartTime: Value(
                upsertOutlet.secondStaffAdmin!.shiftStartTime,
              ),
              shiftEndTime: Value(upsertOutlet.secondStaffAdmin!.shiftEndTime),
              isActive: Value(upsertOutlet.firstStaffAdmin!.isActive),
            );

            batch.insert(users, userComp);

            CustomSyncQueue.addToSyncQueue(
              type: TableType.users,
              id: userComp.localId.value,
              action: SyncAction.create,
              batch: batch,
              syncQueues: syncQueues,
              data: {
                'uuid': userComp.localId.value,
                'outlet_id': userComp.outletId.value,
                'name': userComp.name.value,
                'email': userComp.email.value,
                'phone_number': userComp.phoneNumber.value,
                'password': userComp.passwordHash.value,
                'role': userComp.role.value,
                'shift_name': userComp.shiftName.value,
                'shift_start_time': userComp.shiftStartTime.value,
                'shift_end_time': userComp.shiftEndTime.value,
                'is_active': userComp.isActive.value,
                'created_at': now.toIso8601String(),
                'updated_at': now.toIso8601String(),
              },
            );
          }
        });
      });
    } catch (e, st) {
      LogMessage.log.e(e.toString(), error: e, stackTrace: st);
      rethrow;
    }
  }

  Future<void> deleteOutlet(String outletId) async {
    try {
      await transaction(() async {
        await batch((batch) {
          final now = DateTime.now();

          batch.update(
            outlets,
            OutletsCompanion(isActive: Value(false), deletedAt: Value(now)),
            where: (tbl) => tbl.localId.equals(outletId),
          );

          CustomSyncQueue.updateSyncQueue(
            type: TableType.outlets,
            id: outletId,
            action: SyncAction.delete,
            batch: batch,
            syncQueues: syncQueues,
            data: {
              'uuid': outletId,
              'is_active': false,
              'deleted_at': now.toIso8601String(),
            },
          );

          batch.update(
            users,
            UsersCompanion(isActive: Value(false), deletedAt: Value(now)),
            where: (tbl) => tbl.outletId.equals(outletId),
          );

          CustomSyncQueue.addToSyncQueue(
            type: TableType.users,
            id: outletId,
            action: SyncAction.delete,
            batch: batch,
            syncQueues: syncQueues,
            data: {
              'outlet_id': outletId,
              'role': UserRole.admin.role,
              'is_active': false,
              'deleted_at': now.toIso8601String(),
            },
          );
        });
      });
    } catch (e, st) {
      LogMessage.log.e(e.toString(), error: e, stackTrace: st);
      rethrow;
    }
  }

  Stream<List<Outlet>> watchAllOutlets() async* {
    final query = selectOnly(outlets)
      ..addColumns([
        outlets.localId,
        outlets.name,
        outlets.localPhotoPath,
        outlets.isMainOutlet,
        outlets.isActive,
      ])
      ..where(outlets.deletedAt.isNull());

    yield* query.watch().map((rows) {
      return rows.map((row) {
        final String id = row.read(outlets.localId) ?? '';
        final String name = row.read(outlets.name) ?? '';
        final String photoPath = row.read(outlets.localPhotoPath) ?? '';
        final bool isMainOutlet = row.read(outlets.isMainOutlet) ?? false;
        final bool isActive = row.read(outlets.isActive) ?? false;

        return Outlet(
          id: id,
          name: name,
          photoPath: photoPath,
          isMainOutlet: isMainOutlet,
          isActive: isActive,
        );
      }).toList();
    });
  }

  Stream<List<SourceOutlet>> watchAllSourceOutlets(bool isShowAll) async* {
    final currentOutletId = await SystemSetting.getCurrentOutletId();

    final query = selectOnly(outlets)
      ..addColumns([outlets.localId, outlets.name])
      ..where(
        (isShowAll
                ? const Constant(true)
                : outlets.localId.isNotValue(currentOutletId!)) &
            outlets.deletedAt.isNull(),
      );

    yield* query.watch().map((rows) {
      return rows.map((row) {
        final String id = row.read(outlets.localId) ?? '';
        final String name = row.read(outlets.name) ?? '';
        return SourceOutlet(id: id, name: name);
      }).toList();
    });
  }
}
