import { Inject, Injectable, InternalServerErrorException } from '@nestjs/common';
import { CommandHandler, EventBus, type ICommandHandler } from '@nestjs/cqrs';
import { type Prisma } from '@prisma/client';
import {
  CacheService,
  CachePrefix,
  DomainException,
  LoggerService,
  PrismaService,
  ValidationException,
} from '@modules/shared';
import { type ListingEntity } from '../../../domain/entities/listing.entity';
import { LISTING_REPOSITORY, type IListingRepository } from '../../../domain/repositories/listing.repository';
import { BulkUpdateListingsCommand } from './bulk-update-listings.command';

export const BULK_UPDATE_MAX_ITEMS = 100;

export interface BulkUpdateItemResult {
  id: string;
  success: boolean;
  reason?: string;
}

export interface BulkUpdateListingsResult {
  succeeded: boolean;
  totalRequested: number;
  totalSucceeded: number;
  totalFailed: number;
  items: BulkUpdateItemResult[];
}

interface PreparedListingChange {
  id: string;
  data: Prisma.ListingUncheckedUpdateInput;
  events: ReturnType<ListingEntity['clearDomainEvents']>;
  /** Status snapshot used to know which caches to invalidate after commit. */
  newStatus: string;
}

@Injectable()
@CommandHandler(BulkUpdateListingsCommand)
export class BulkUpdateListingsHandler
  implements ICommandHandler<BulkUpdateListingsCommand>
{
  constructor(
    @Inject(LISTING_REPOSITORY) private readonly listingRepo: IListingRepository,
    private readonly prisma: PrismaService,
    private readonly eventBus: EventBus,
    private readonly cache: CacheService,
    private readonly logger: LoggerService,
  ) {}

  async execute(command: BulkUpdateListingsCommand): Promise<BulkUpdateListingsResult> {
    const { ids, patch, userId, userRole } = command;

    // ─── Up-front input validation ─────────────────────────────
    if (!Array.isArray(ids) || ids.length === 0) {
      throw new ValidationException('Phải cung cấp ít nhất một listing id', { ids });
    }
    if (ids.length > BULK_UPDATE_MAX_ITEMS) {
      throw new ValidationException(
        `Vượt quá giới hạn bulk-update (${BULK_UPDATE_MAX_ITEMS} listing/lần)`,
        { count: ids.length, max: BULK_UPDATE_MAX_ITEMS },
      );
    }

    // Reject duplicates so per-item results map 1:1 to caller input
    const uniqueIds = new Set(ids);
    if (uniqueIds.size !== ids.length) {
      throw new ValidationException('Danh sách id chứa giá trị trùng lặp', {
        duplicates: ids.filter((id, i) => ids.indexOf(id) !== i),
      });
    }

    const hasAnyPatch =
      patch.priceVND !== undefined ||
      patch.status !== undefined ||
      patch.featured !== undefined;
    if (!hasAnyPatch) {
      throw new ValidationException('Patch trống — không có trường nào để cập nhật', {});
    }
    if (patch.featured === true) {
      const days = patch.featuredDays;
      if (days === undefined || !Number.isInteger(days) || days <= 0) {
        throw new ValidationException(
          'featuredDays phải là số nguyên dương khi featured=true',
          { featuredDays: days },
        );
      }
    }

    // ─── Per-item authorization + domain prep (no writes yet) ──
    const items: BulkUpdateItemResult[] = [];
    const prepared: PreparedListingChange[] = [];
    const isAdmin = userRole === 'ADMIN';

    for (const id of ids) {
      const listing = await this.listingRepo.findById(id);
      if (!listing) {
        items.push({ id, success: false, reason: 'NOT_FOUND' });
        continue;
      }

      const isOwner = listing.sellerId === userId;
      const isAgent = listing.agentId !== null && listing.agentId === userId;
      if (!isOwner && !isAgent && !isAdmin) {
        items.push({ id, success: false, reason: 'FORBIDDEN' });
        continue;
      }

      try {
        // priceVND
        if (patch.priceVND !== undefined) {
          listing.updateContent({ priceVND: patch.priceVND });
        }

        // status
        if (patch.status !== undefined && patch.status !== listing.status) {
          listing.transitionTo(patch.status);
        }

        // featured
        let featuredUntilOverride: Date | null | undefined;
        if (patch.featured !== undefined) {
          if (patch.featured) {
            const days = patch.featuredDays!;
            featuredUntilOverride = new Date(Date.now() + days * 24 * 60 * 60 * 1000);
          } else {
            featuredUntilOverride = null;
          }
        }

        const data: Prisma.ListingUncheckedUpdateInput = {
          status: listing.status,
          priceVND: listing.price.amountVND,
          pricePerM2: listing.pricePerM2,
        };
        if (featuredUntilOverride !== undefined) {
          data.featuredUntil = featuredUntilOverride;
        }

        prepared.push({
          id,
          data,
          events: listing.clearDomainEvents(),
          newStatus: listing.status,
        });
        items.push({ id, success: true });
      } catch (err) {
        const reason = err instanceof DomainException ? err.message : 'INVALID';
        items.push({ id, success: false, reason });
      }
    }

    const totalFailed = items.filter((i) => !i.success).length;
    const totalSucceeded = items.length - totalFailed;

    // ─── All-or-nothing: any failure → rollback (no DB writes) ─
    if (totalFailed > 0) {
      this.logger.warn(
        `Bulk-update aborted: ${totalFailed}/${items.length} item(s) invalid`,
        this.constructor.name,
      );
      return {
        succeeded: false,
        totalRequested: ids.length,
        totalSucceeded: 0,
        totalFailed: items.length, // nothing committed
        items: items.map((i) =>
          i.success
            ? { id: i.id, success: false, reason: 'ROLLED_BACK' }
            : i,
        ),
      };
    }

    // ─── Atomic write phase ───────────────────────────────────
    try {
      await this.prisma.$transaction(
        prepared.map(({ id, data }) =>
          this.prisma.listing.update({ where: { id }, data }),
        ),
      );
    } catch (error) {
      this.logger.error(
        `Bulk-update transaction failed: ${error instanceof Error ? error.message : error}`,
        error instanceof Error ? error.stack : undefined,
        this.constructor.name,
      );
      throw new InternalServerErrorException('Không thể thực hiện cập nhật hàng loạt');
    }

    // ─── Post-commit side effects (events + cache) ────────────
    for (const { events } of prepared) {
      for (const event of events) this.eventBus.publish(event);
    }

    await Promise.all([
      ...prepared.map(({ id }) =>
        this.cache.invalidate(CacheService.buildKey(CachePrefix.LISTING, id)),
      ),
      this.cache.invalidateByPrefix(CachePrefix.SEARCH),
    ]);

    this.logger.log(
      `Bulk-update committed: ${totalSucceeded} listing(s) by user=${userId}`,
      this.constructor.name,
    );

    return {
      succeeded: true,
      totalRequested: ids.length,
      totalSucceeded,
      totalFailed: 0,
      items,
    };
  }
}
