import 'package:drift/drift.dart';
import 'package:niogu_app/core/database/app_database.dart';
import 'package:niogu_app/core/database/tables/purchases.dart';
import 'package:niogu_app/core/database/tables/suppliers.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/table_type.dart';
import 'package:niogu_app/core/utils/extension_format.dart';
import 'package:niogu_app/core/enums/payment_status.dart';
import 'package:niogu_app/core/enums/sync_status.dart';
import 'package:niogu_app/features/stock_in/domain/entities/stock_in_purchase.dart';
import 'package:niogu_app/features/supplier/domain/entities/supplier.dart';

part 'supplier_dao.g.dart';

@DriftAccessor(tables: [Suppliers, Purchases, SyncQueues])
class SupplierDao extends DatabaseAccessor<AppDatabase>
    with _$SupplierDaoMixin {
  SupplierDao(AppDatabase db) : super(db);

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

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

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

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

  Future<SupplierDetail> getSupplierById(String id) async {
    final supplierQuery = selectOnly(suppliers)
      ..addColumns([
        suppliers.localId,
        suppliers.name,
        suppliers.email,
        suppliers.phoneNumber,
      ])
      ..where(suppliers.localId.equals(id) & suppliers.deletedAt.isNull())
      ..limit(1);

    final purchaseQuery = selectOnly(purchases)
      ..addColumns([
        purchases.localId,
        purchases.purchaseNumber,
        purchases.paymentStatus,
        purchases.createdAt,
      ])
      ..orderBy([OrderingTerm.desc(purchases.createdAt)])
      ..where(purchases.supplierId.equals(id));

    final supplierRow = await supplierQuery.getSingle();

    final String localId = supplierRow.read(suppliers.localId) ?? '';

    final String name = supplierRow.read(suppliers.name) ?? '';

    final String email = supplierRow.read(suppliers.email) ?? '';

    final String phoneNumber = supplierRow.read(suppliers.phoneNumber) ?? '';

    final supplierActivityRow = await purchaseQuery.get();

    final List<SupplierActivities> activities = supplierActivityRow.map((row) {
      final String localId = row.read(purchases.localId) ?? '';

      final String purchaseNumber = (row.read(purchases.purchaseNumber) ?? '')
          .toUniqueNumberCleaner();

      final String status = row.read(purchases.paymentStatus) ?? '';

      final PaymentStatus paymentStatus = PaymentStatus.values.byName(status);

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

      return SupplierActivities(
        id: localId,
        purchaseNumber: purchaseNumber,
        paymentStatus: paymentStatus,
        purchaseDate: purchaseDate,
      );
    }).toList();

    return SupplierDetail(
      id: localId,
      name: name,
      email: email,
      phoneNumber: phoneNumber,
      activities: activities,
    );
  }

  Future<int> saveSupplier(SuppliersCompanion supplier) async {
    return transaction(() async {
      final now = DateTime.now();

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

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

      final syncQueueComp = SyncQueuesCompanion(
        tableType: Value(TableType.suppliers.type),
        tableLocalId: supplier.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.suppliers.type) &
                    t.tableLocalId.equals(supplier.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> deleteSupplier(String id) async {
    return transaction(() async {
      final now = DateTime.now();

      final rowsAffected =
          (await update(suppliers)
                ..where((supplier) => supplier.localId.equals(id)))
              .write(
                SuppliersCompanion(
                  syncStatus: Value(SyncStatus.dirty.status),
                  deletedAt: Value(DateTime.now()),
                ),
              );

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

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

      return rowsAffected;
    });
  }

  Stream<List<AlreadySupplier>> watchAllAlreadySuppliers() async* {
    final query = selectOnly(suppliers)
      ..addColumns([
        suppliers.localId,
        suppliers.name,
        suppliers.phoneNumber,
        suppliers.address,
      ])
      ..where(suppliers.deletedAt.isNull());

    yield* query.watch().map((rows) {
      return rows.map((row) {
        return AlreadySupplier(
          id: row.read(suppliers.localId)!,
          name: row.read(suppliers.name) ?? '',
          phoneNumber: row.read(suppliers.phoneNumber) ?? '',
          address: row.read(suppliers.address) ?? '',
        );
      }).toList();
    });
  }
}
