import 'package:drift/drift.dart';
import 'package:niogu_app/core/database/app_database.dart' hide Unit;
import 'package:niogu_app/core/database/tables/sync_queues.dart';
import 'package:niogu_app/core/database/tables/units.dart';
import 'package:niogu_app/core/entities/custom_sync_queue.dart';
import 'package:niogu_app/core/enums/sync_status.dart';
import 'package:niogu_app/core/enums/table_type.dart';
import 'package:niogu_app/features/goods/units/domain/entities/unit.dart';

part 'unit_dao.g.dart';

@DriftAccessor(tables: [Units, SyncQueues])
class UnitDao extends DatabaseAccessor<AppDatabase> with _$UnitDaoMixin {
  UnitDao(AppDatabase db) : super(db);

  Stream<List<Unit>> watchAllUnits() async* {
    final query = selectOnly(units)
      ..addColumns([units.localId, units.name])
      ..orderBy([OrderingTerm.desc(units.createdAt)]);

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

  Future<List<Unit>> getAllUnits() async {
    final query = selectOnly(units)
      ..addColumns([units.localId, units.name])
      ..orderBy([OrderingTerm.desc(units.createdAt)]);

    final results = await query.get();

    return results.map((row) {
      final String id = row.read(units.localId) ?? '';
      final String name = row.read(units.name) ?? '';
      return Unit(id: id, name: name);
    }).toList();
  }

  Future<Unit> getUnitById(String id) async {
    final query = selectOnly(units)
      ..addColumns([units.localId, units.name])
      ..where(units.localId.equals(id))
      ..limit(1);

    final result = await query.getSingle();

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

    return Unit(id: localId, name: name);
  }

  Future<int> insertUnit(UnitsCompanion unit) async {
    return await transaction(() async {
      final now = DateTime.now();

      final id = await into(units).insert(unit);

      final payload = SyncPayload(
        table: TableType.units.type,
        localId: unit.localId.value,
        action: SyncAction.create.action,
        data: {
          'uuid': unit.localId.value,
          'name': unit.name.value,
          'created_at': now.toIso8601String(),
          'updated_at': now.toIso8601String(),
        },
      ).toJson();

      await into(syncQueues).insert(
        SyncQueuesCompanion(
          tableType: Value(TableType.units.type),
          tableLocalId: unit.localId,
          action: Value(SyncAction.create.action),
          status: Value(SyncStatus.dirty.status),
          payload: Value(payload),
        ),
      );

      return id;
    });
  }

  Future<int> updateUnit(String id, String name) async {
    return await transaction(() async {
      final now = DateTime.now();

      final rowsAffected =
          (await update(units)
                ..where((unit) => unit.localId.equals(id)))
              .write(
                UnitsCompanion(
                  name: Value(name),
                  syncStatus: Value(SyncStatus.dirty.status),
                  updatedAt: Value(now),
                ),
              );

      final payload = SyncPayload(
        table: TableType.units.type,
        localId: id,
        action: SyncAction.update.action,
        data: {'uuid': id, 'name': name, 'updated_at': now.toIso8601String()},
      ).toJson();

      final syncQueueComp = SyncQueuesCompanion(
        tableType: Value(TableType.units.type),
        tableLocalId: Value(id),
        action: Value(SyncAction.update.action),
        status: Value(SyncStatus.dirty.status),
        payload: Value(payload),
      );

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

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