import 'package:drift/drift.dart';
import 'package:latlong2/latlong.dart';
import 'package:niogu_app/core/database/app_database.dart';
import 'package:niogu_app/core/database/tables/customer_addresses.dart';
import 'package:niogu_app/core/database/tables/customers.dart';
import 'package:niogu_app/core/database/tables/orders.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/customer_source.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/order_source.dart';
import 'package:niogu_app/core/enums/order_status.dart';
import 'package:niogu_app/core/enums/sync_status.dart';
import 'package:niogu_app/core/system/system_setting.dart';
import 'package:niogu_app/features/customer/domain/entities/customer.dart';
import 'package:niogu_app/features/pos/domain/entities/pos.dart';

part 'customer_dao.g.dart';

@DriftAccessor(tables: [Customers, CustomerAddresses, Orders, SyncQueues])
class CustomerDao extends DatabaseAccessor<AppDatabase>
    with _$CustomerDaoMixin {
  CustomerDao(AppDatabase db) : super(db);

  Stream<List<DisplayCustomers>> watchAllCustomers() async* {
    final query = selectOnly(customers)
      ..addColumns([customers.localId, customers.name])
      ..where(customers.deletedAt.isNull());

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

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

        return DisplayCustomers(id: id, name: name);
      }).toList();
    });
  }

  Future<CustomerDetail> getCustomerById(String id) async {
    final query = selectOnly(customers)
      ..addColumns([
        customers.localId,
        customers.name,
        customers.email,
        customers.phoneNumber,
        customers.source,
      ])
      ..where(customers.localId.equals(id) & customers.deletedAt.isNull())
      ..limit(1);
    /**
    final customerAddressQuery = selectOnly(customerAddresses)
      ..addColumns([
        customerAddresses.label,
        customerAddresses.fullAddress,
        customerAddresses.latitude,
        customerAddresses.longitude,
      ])
      ..orderBy([OrderingTerm.desc(customerAddresses.createdAt)])
      ..where(
        customerAddresses.customerId.equals(id) &
            customerAddresses.deletedAt.isNull(),
      );

    final orderQuery = selectOnly(orders)
      ..addColumns([
        orders.localId,
        orders.orderNumber,
        orders.source,
        orders.orderStatus,
        orders.createdAt,
      ])
      ..orderBy([OrderingTerm.desc(orders.createdAt)])
      ..where(orders.customerId.equals(id));
    */

    final result = await query.getSingle();

    final String localId = result.read(customers.localId) ?? '';

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

    final String email = result.read(customers.email) ?? '';

    final String phoneNumber = result.read(customers.phoneNumber) ?? '';

    final CustomerSource customerSource = CustomerSource.values.byName(
      (result.read(customers.source) ?? ''),
    );

    return CustomerDetail(
      id: localId,
      name: name,
      email: email,
      phoneNumber: phoneNumber,
      customerSource: customerSource,
    );
  }

  Stream<List<CustomerAddress>> watchAllCustomerAddresses(
    String customerId,
  ) async* {
    final query = selectOnly(customerAddresses)
      ..addColumns([
        customerAddresses.label,
        customerAddresses.fullAddress,
        customerAddresses.latitude,
        customerAddresses.longitude,
      ])
      ..orderBy([OrderingTerm.desc(customerAddresses.createdAt)])
      ..where(
        customerAddresses.customerId.equals(customerId) &
            customerAddresses.deletedAt.isNull(),
      );

    yield* query.watch().map((rows) {
      return rows.map((row) {
        final String label = row.read(customerAddresses.label) ?? '';

        final String fullAddress =
            row.read(customerAddresses.fullAddress) ?? '';

        final double latitude = row.read(customerAddresses.latitude) ?? 0.0;

        final double longitude = row.read(customerAddresses.longitude) ?? 0.0;

        return CustomerAddress(
          label: label,
          fullAddress: fullAddress,
          coordinate: LatLng(latitude, longitude),
        );
      }).toList();
    });
  }

  Stream<List<CustomerActivities>> watchAllCustomerActivities(
    String customerId,
  ) async* {
    final query = selectOnly(orders)
      ..addColumns([
        orders.localId,
        orders.orderNumber,
        orders.source,
        orders.orderStatus,
        orders.createdAt,
      ])
      ..orderBy([OrderingTerm.desc(orders.createdAt)])
      ..where(orders.customerId.equals(customerId));

    yield* query.watch().map((rows) {
      return rows.map((row) {
        final String localId = row.read(orders.localId) ?? '';

        final String orderNumber = (row.read(orders.orderNumber) ?? '')
            .toUniqueNumberCleaner();

        final OrderSource orderSource = OrderSource.values.byName(
          (row.read(orders.source) ?? ''),
        );

        final OrderStatus orderStatus = OrderStatus.values.byName(
          (row.read(orders.orderStatus) ?? ''),
        );

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

        return CustomerActivities(
          id: localId,
          orderNumber: orderNumber,
          orderSource: orderSource,
          orderStatus: orderStatus,
          orderDate: orderDate,
        );
      }).toList();
    });
  }

  Future<int> saveCustomer(CustomersCompanion customer) async {
    return await transaction(() async {
      final now = DateTime.now();

      final rowsAffected = await into(customers).insert(
        customer,
        onConflict: DoUpdate((old) {
          return customer.copyWith(
            syncStatus: Value(SyncStatus.dirty.status),
            createdAt: Value.absent(),
            updatedAt: Value(now),
          );
        }),
      );

      final payload = SyncPayload(
        table: TableType.customers.type,
        localId: customer.localId.value,
        action: SyncAction.update_or_create.action,
        data: {
          'uuid': customer.localId.value,
          'outlet_id': customer.outletId.value,
          'name': customer.name.value,
          'email': customer.email.value,
          'phone_number': customer.phoneNumber.value,
          'source': customer.source.value,
          'created_at': now.toIso8601String(),
          'updated_at': now.toIso8601String(),
        },
      ).toJson();

      final syncQueueComp = SyncQueuesCompanion(
        tableType: Value(TableType.customers.type),
        tableLocalId: customer.localId,
        action: Value(SyncAction.update_or_create.action),
        status: Value(SyncStatus.dirty.status),
        payload: Value(payload),
      );

      final existing =
          await (select(syncQueues)..where(
                (t) =>
                    t.tableType.equals(TableType.customers.type) &
                    t.tableLocalId.equals(customer.localId.value),
              ))
              .getSingleOrNull();

      if (existing != null) {
        await (update(
          syncQueues,
        )..where((t) => t.id.equals(existing.id))).write(syncQueueComp);
      } else {
        await into(syncQueues).insert(syncQueueComp);
      }

      return rowsAffected;
    });
  }

  Future<int> deleteCustomer(String id) async {
    return transaction(() async {
      final now = DateTime.now();

      final rowsAffected =
          (await update(customers)
                ..where((customer) => customer.localId.equals(id)))
              .write(
                CustomersCompanion(
                  syncStatus: Value(SyncStatus.dirty.status),
                  deletedAt: Value(now),
                ),
              );

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

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

      return rowsAffected;
    });
  }

  Stream<List<AlreadyCustomer>> watchAllAlreadyCustomers() async* {
    final outletId = await SystemSetting.getCurrentOutletId();

    final query =
        selectOnly(customers).join([
            leftOuterJoin(
              customerAddresses,
              customerAddresses.customerId.equalsExp(customers.localId),
            ),
          ])
          ..addColumns([
            customers.localId,
            customers.name,
            customers.phoneNumber,
            customerAddresses.fullAddress,
          ])
          ..where(
            customers.outletId.equals(outletId!) & customers.deletedAt.isNull(),
          )
          ..where(customerAddresses.deletedAt.isNull());

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

      for (final row in rows) {
        final customerId = row.read(customers.localId)!;

        final name = row.read(customers.name) ?? '';

        final phoneNumber = row.read(customers.phoneNumber) ?? '';

        groupedMap.putIfAbsent(
          customerId,
          () => AlreadyCustomer(
            id: customerId,
            name: name,
            phoneNumber: phoneNumber,
            addresses: [],
          ),
        );

        final address = row.read(customerAddresses.fullAddress) ?? '';

        groupedMap[customerId]!.addresses.add(address);
      }

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