import 'package:drift/drift.dart';
import 'package:niogu_app/core/database/app_database.dart';
import 'package:niogu_app/core/database/tables/campaigns.dart';
import 'package:niogu_app/core/database/tables/categories.dart';
import 'package:niogu_app/core/database/tables/outlet_campaigns.dart';
import 'package:niogu_app/core/database/tables/products.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/campaign_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/utils/log_message.dart';
import 'package:niogu_app/features/online_store/domain/entities/online_store.dart';

part 'campaign_dao.g.dart';

@DriftAccessor(
  tables: [Products, Categories, Campaigns, OutletCampaigns, SyncQueues],
)
class CampaignDao extends DatabaseAccessor<AppDatabase>
    with _$CampaignDaoMixin {
  CampaignDao(AppDatabase db) : super(db);

  Stream<List<DisplayCampaign>> watchAllCampaigns() async* {
    final query =
        selectOnly(campaigns).join([
            innerJoin(
              outletCampaigns,
              outletCampaigns.campaignId.equalsExp(campaigns.localId),
            ),
            innerJoin(
              outlets,
              outlets.localId.equalsExp(outletCampaigns.outletId) &
                  outlets.deletedAt.isNull(),
            ),
          ])
          ..addColumns([
            campaigns.localId,
            campaigns.localBannerPath,
            campaigns.title,
            campaigns.startDate,
            campaigns.endDate,
            campaigns.isActive,
            outletCampaigns.localId.count(),
          ])
          ..groupBy([campaigns.localId])
          ..where(campaigns.deletedAt.isNull())
          ..where(outletCampaigns.deletedAt.isNull());

    yield* query.watch().map((rows) {
      return rows.map((row) {
        return DisplayCampaign(
          id: row.read(campaigns.localId)!,
          imagePath: row.read(campaigns.localBannerPath)!,
          name: row.read(campaigns.title)!,
          startDate: row.read(campaigns.startDate),
          endDate: row.read(campaigns.endDate),
          isActive: row.read(campaigns.isActive) ?? false,
          outletUseAmount: row.read(outletCampaigns.localId.count()) ?? 0,
        );
      }).toList();
    });
  }

  Future<CampaignDetail> getCampaignById(String id) async {
    final campaignQuery = selectOnly(campaigns)
      ..addColumns([
        campaigns.localId,
        campaigns.localBannerPath,
        campaigns.title,
        campaigns.startDate,
        campaigns.endDate,
        campaigns.actionType,
        campaigns.actionReferenceId,
        campaigns.isActive,
      ])
      ..where(campaigns.localId.equals(id))
      ..limit(1);

    final campaignRow = await campaignQuery.getSingle();

    final campaignId = campaignRow.read(campaigns.localId)!;

    final actionType = campaignRow.read(campaigns.actionType);

    final actionId = campaignRow.read(campaigns.actionReferenceId);

    CampaignType? campaignType;

    String? linkId;

    String? linkName;

    if (actionType != null && actionId != null) {
      campaignType = CampaignType.values.byName(actionType);

      if (campaignType == CampaignType.product) {
        final linkQuery = selectOnly(products)
          ..addColumns([products.localId, products.name])
          ..where(products.localId.equals(actionId))
          ..limit(1);

        final linkRow = await linkQuery.getSingle();

        linkId = linkRow.read(products.localId)!;

        linkName = linkRow.read(products.name) ?? '';
      } else {
        final linkQuery = selectOnly(categories)
          ..addColumns([categories.localId, categories.name])
          ..where(categories.localId.equals(actionId))
          ..limit(1);

        final linkRow = await linkQuery.getSingle();

        linkId = linkRow.read(categories.localId)!;

        linkName = linkRow.read(categories.name) ?? '';
      }
    }

    final outletCampaignQuery =
        selectOnly(outletCampaigns).join([
            innerJoin(
              outlets,
              outlets.localId.equalsExp(outletCampaigns.outletId),
            ),
          ])
          ..addColumns([
            outletCampaigns.localId,
            outletCampaigns.outletId,
            outlets.name,
          ])
          ..where(outletCampaigns.campaignId.equals(campaignId))
          ..where(outletCampaigns.deletedAt.isNull());

    final outletCampaignRows = await outletCampaignQuery.get();

    final targetOutlets = outletCampaignRows.map((row) {
      return SelectedTargetOutlet(
        id: row.read(outletCampaigns.localId)!,
        outletId: row.read(outletCampaigns.outletId)!,
        name: row.read(outlets.name) ?? '',
      );
    }).toList();

    return CampaignDetail(
      id: campaignId,
      imagePath: campaignRow.read(campaigns.localBannerPath)!,
      name: campaignRow.read(campaigns.title) ?? '',
      startDate: campaignRow.read(campaigns.startDate),
      endDate: campaignRow.read(campaigns.endDate),
      campaignType: campaignType,
      linkId: linkId,
      linkName: linkName,
      targetOutlets: targetOutlets,
      isActive: campaignRow.read(campaigns.isActive) ?? false,
    );
  }

  Future<void> saveCampaign(
    UpsertCampaign campaign,
    List<String>? deleteTargetOutlets,
  ) async {
    try {
      await transaction(() async {
        final now = DateTime.now();

        await batch((batch) async {
          batch.insert(
            campaigns,
            CampaignsCompanion(
              localId: Value(campaign.id),
              title: Value(campaign.name),
              localBannerPath: Value(campaign.imagePath),
              startDate: Value(campaign.startDate),
              endDate: Value(campaign.endDate),
              actionType: Value(campaign.campaignType?.type),
              actionReferenceId: Value(campaign.linkId),
            ),
            onConflict: DoUpdate((_) {
              return CampaignsCompanion(
                title: Value(campaign.name),
                localBannerPath: Value(campaign.imagePath),
                startDate: Value(campaign.startDate),
                endDate: Value(campaign.endDate),
                actionType: Value(campaign.campaignType?.type),
                actionReferenceId: Value(campaign.linkId),
                isActive: Value(campaign.isActive),
                syncStatus: Value(SyncStatus.dirty.status),
                updatedAt: Value(now),
              );
            }),
          );

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

          if (existing != null) {
            CustomSyncQueue.updateSyncQueue(
              type: TableType.campaigns,
              id: campaign.id,
              action: SyncAction.update,
              batch: batch,
              syncQueues: syncQueues,
              data: {
                'uuid': campaign.id,
                'title': campaign.name,
                'server_banner_url': campaign.imagePath,
                'start_date': campaign.startDate,
                'end_date': campaign.endDate,
                'action_type': campaign.campaignType?.type,
                'action_reference_id': campaign.linkId,
                'is_active': campaign.isActive,
                'updated_at': now.toIso8601String(),
              },
            );
          } else {
            CustomSyncQueue.addToSyncQueue(
              type: TableType.campaigns,
              id: campaign.id,
              action: SyncAction.create,
              batch: batch,
              syncQueues: syncQueues,
              data: {
                'uuid': campaign.id,
                'title': campaign.name,
                'server_banner_url': campaign.imagePath,
                'start_date': campaign.startDate,
                'end_date': campaign.endDate,
                'action_type': campaign.campaignType?.type,
                'action_reference_id': campaign.linkId,
                'created_at': now.toIso8601String(),
                'updated_at': now.toIso8601String(),
              },
            );
          }

          if (deleteTargetOutlets != null && deleteTargetOutlets.isNotEmpty) {
            batch.update(
              outletCampaigns,
              OutletCampaignsCompanion(
                syncStatus: Value(SyncStatus.dirty.status),
                deletedAt: Value(now),
              ),
              where: (tbl) => tbl.localId.isIn(deleteTargetOutlets),
            );

            CustomSyncQueue.addToSyncQueue(
              type: TableType.outlet_campaigns,
              id: 'BULK_DELETE_${now.millisecondsSinceEpoch}',
              action: SyncAction.bulk_update,
              batch: batch,
              syncQueues: syncQueues,
              data: {
                'outlet_ids': deleteTargetOutlets,
                'deleted_at': now.toIso8601String(),
              },
            );
          }

          for (final outlet in campaign.targetOutlets) {
            final outletCampign = OutletCampaignsCompanion(
              localId: Value(outlet.id),
              campaignId: Value(campaign.id),
              outletId: Value(outlet.outletId),
            );
            batch.insert(
              outletCampaigns,
              outletCampign,
              onConflict: DoUpdate((_) {
                return outletCampign.copyWith(
                  syncStatus: Value(SyncStatus.dirty.status),
                  updatedAt: Value(now),
                );
              }),
            );

            CustomSyncQueue.addToSyncQueue(
              type: TableType.outlet_campaigns,
              id: outlet.id,
              action: SyncAction.update_or_create,
              batch: batch,
              syncQueues: syncQueues,
              data: {
                'uuid': outlet.id,
                'campaign_id': campaign.id,
                'outlet_id': outlet.outletId,
                'updated_at': now.toIso8601String(),
              },
            );
          }
        });
      });
    } catch (e, st) {
      LogMessage.log.e(e.toString(), error: e, stackTrace: st);
      rethrow;
    }
  }

  Future<void> deleteCampaign(String campaignId) async {
    try {
      await transaction(() async {
        final now = DateTime.now();

        await batch((batch) {
          batch.update(
            campaigns,
            CampaignsCompanion(
              syncStatus: Value(SyncStatus.dirty.status),
              deletedAt: Value(now),
            ),
            where: (tbl) => tbl.localId.equals(campaignId),
          );

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

          batch.update(
            outletCampaigns,
            OutletCampaignsCompanion(
              syncStatus: Value(SyncStatus.dirty.status),
              deletedAt: Value(now),
            ),
            where: (tbl) => tbl.campaignId.equals(campaignId),
          );

          CustomSyncQueue.addToSyncQueue(
            type: TableType.outlet_campaigns,
            id: campaignId,
            action: SyncAction.delete,
            batch: batch,
            syncQueues: syncQueues,
            data: {
              'campaign_id': campaignId,
              'deleted_at': now.toIso8601String(),
            },
          );

        });
      });
    } catch (e, st) {
      LogMessage.log.e(e.toString(), error: e, stackTrace: st);
      rethrow;
    }
  }
}
