import { Inject } from '@nestjs/common';
import { CommandHandler, type EventBus, type ICommandHandler } from '@nestjs/cqrs';
import { createId } from '@paralleldrive/cuid2';
import { ValidationException, type CacheService, CachePrefix, type LoggerService } from '@modules/shared';
import { ListingEntity } from '../../../domain/entities/listing.entity';
import { PropertyEntity } from '../../../domain/entities/property.entity';
import { LISTING_REPOSITORY, type IListingRepository } from '../../../domain/repositories/listing.repository';
import { PROPERTY_REPOSITORY, type IPropertyRepository } from '../../../domain/repositories/property.repository';
import { DUPLICATE_DETECTOR, type IDuplicateDetector } from '../../../domain/services/duplicate-detector';
import { PRICE_VALIDATOR, type IPriceValidator } from '../../../domain/services/price-validator';
import { Address } from '../../../domain/value-objects/address.vo';
import { GeoPoint } from '../../../domain/value-objects/geo-point.vo';
import { Price } from '../../../domain/value-objects/price.vo';
import { CreateListingCommand } from './create-listing.command';

export interface DuplicateWarning {
  listingId: string;
  propertyId: string;
  title: string;
  address: string;
  district: string;
  distanceMeters: number;
  titleSimilarity: number;
}

export interface PriceWarning {
  pricePerM2: number;
  minPricePerM2: number;
  maxPricePerM2: number;
  reason: string;
}

export interface CreateListingResult {
  listingId: string;
  propertyId: string;
  status: string;
  duplicateWarnings: DuplicateWarning[];
  priceWarning?: PriceWarning;
}

@CommandHandler(CreateListingCommand)
export class CreateListingHandler implements ICommandHandler<CreateListingCommand> {
  constructor(
    @Inject(PROPERTY_REPOSITORY) private readonly propertyRepo: IPropertyRepository,
    @Inject(LISTING_REPOSITORY) private readonly listingRepo: IListingRepository,
    @Inject(DUPLICATE_DETECTOR) private readonly duplicateDetector: IDuplicateDetector,
    @Inject(PRICE_VALIDATOR) private readonly priceValidator: IPriceValidator,
    private readonly eventBus: EventBus,
    private readonly cache: CacheService,
    private readonly logger: LoggerService,
  ) {}

  async execute(command: CreateListingCommand): Promise<CreateListingResult> {
    // Validate value objects
    const addressResult = Address.create(command.address, command.ward, command.district, command.city);
    if (addressResult.isErr) throw new ValidationException(addressResult.unwrapErr());

    const geoPointResult = GeoPoint.create(command.latitude, command.longitude);
    if (geoPointResult.isErr) throw new ValidationException(geoPointResult.unwrapErr());

    const priceResult = Price.create(command.priceVND);
    if (priceResult.isErr) throw new ValidationException(priceResult.unwrapErr());

    const address = addressResult.unwrap();
    const geoPoint = geoPointResult.unwrap();
    const price = priceResult.unwrap();

    // Create property
    const propertyId = createId();
    const property = PropertyEntity.createNew(propertyId, {
      propertyType: command.propertyType,
      title: command.title,
      description: command.description,
      address,
      location: geoPoint,
      areaM2: command.areaM2,
      usableAreaM2: command.usableAreaM2 ?? null,
      bedrooms: command.bedrooms ?? null,
      bathrooms: command.bathrooms ?? null,
      floors: command.floors ?? null,
      floor: command.floor ?? null,
      totalFloors: command.totalFloors ?? null,
      direction: command.direction ?? null,
      yearBuilt: command.yearBuilt ?? null,
      legalStatus: command.legalStatus ?? null,
      amenities: command.amenities ?? null,
      nearbyPOIs: command.nearbyPOIs ?? null,
      metroDistanceM: command.metroDistanceM ?? null,
      projectName: command.projectName ?? null,
    });

    await this.propertyRepo.save(property);

    // Create listing
    const listingId = createId();
    const listing = ListingEntity.createNew(
      listingId,
      propertyId,
      command.sellerId,
      command.transactionType,
      price,
      command.areaM2,
      command.agentId,
      command.rentPriceMonthly,
      command.commissionPct,
    );

    await this.listingRepo.save(listing);

    // Publish domain events
    const events = [...property.clearDomainEvents(), ...listing.clearDomainEvents()];
    for (const event of events) {
      this.eventBus.publish(event);
    }

    await Promise.all([
      this.cache.invalidateByPrefix(CachePrefix.SEARCH),
      this.cache.invalidateByPrefix(CachePrefix.MARKET_DISTRICT),
      this.cache.invalidateByPrefix(CachePrefix.MARKET_REPORT),
    ]);

    // Duplicate detection — flag but never block creation
    let duplicateWarnings: DuplicateWarning[] = [];
    try {
      const candidates = await this.duplicateDetector.findDuplicates({
        excludePropertyId: propertyId,
        latitude: command.latitude,
        longitude: command.longitude,
        title: command.title,
        propertyType: command.propertyType,
      });

      duplicateWarnings = candidates.map((c) => ({
        listingId: c.listingId,
        propertyId: c.propertyId,
        title: c.title,
        address: c.address,
        district: c.district,
        distanceMeters: c.distanceMeters,
        titleSimilarity: c.titleSimilarity,
      }));
    } catch {
      this.logger.warn('Duplicate detection failed — listing created without warnings', 'CreateListingHandler');
    }

    // Price validation — flag but never block creation
    let priceWarning: PriceWarning | undefined;
    try {
      const priceResult = await this.priceValidator.validate({
        priceVND: command.priceVND,
        areaM2: command.areaM2,
        propertyType: command.propertyType,
        district: command.district,
      });

      if (priceResult.isSuspicious) {
        priceWarning = {
          pricePerM2: priceResult.pricePerM2,
          minPricePerM2: priceResult.minPricePerM2,
          maxPricePerM2: priceResult.maxPricePerM2,
          reason: priceResult.reason!,
        };
      }
    } catch {
      this.logger.warn('Price validation failed — listing created without price warning', 'CreateListingHandler');
    }

    return {
      listingId,
      propertyId,
      status: listing.status,
      duplicateWarnings,
      priceWarning,
    };
  }
}
