import { HttpStatus, Inject, InternalServerErrorException } from '@nestjs/common';
import { CommandHandler, EventBus, type ICommandHandler } from '@nestjs/cqrs';
import { createId } from '@paralleldrive/cuid2';
import { DomainException, ValidationException, CacheService, CachePrefix, LoggerService, ErrorCode } 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;
  addressMatch: boolean;
  sameAgent: boolean;
}

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

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

/** Radius (meters) used for both hard- and soft-duplicate detection. */
const DUPLICATE_RADIUS_METERS = 50;

@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> {
    try {
      // 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();

      // ---------------------------------------------------------------
      // Duplicate detection — runs BEFORE persistence so a hard duplicate
      // can be rejected with HTTP 409 without orphaning a Property row.
      // Detector failures are logged and do not block creation (we still
      // return [] in that case).
      // ---------------------------------------------------------------
      let duplicateCandidates: Awaited<ReturnType<IDuplicateDetector['findDuplicates']>> = [];
      try {
        duplicateCandidates = await this.duplicateDetector.findDuplicates({
          latitude: command.latitude,
          longitude: command.longitude,
          addressNormalized: address.normalized,
          agentId: command.agentId,
          propertyType: command.propertyType,
          radiusMeters: DUPLICATE_RADIUS_METERS,
        });
      } catch {
        this.logger.warn('Duplicate detection failed — proceeding without warnings', 'CreateListingHandler');
      }

      // Hard duplicate: same agent + same normalized address + within 50m.
      // We only escalate when the caller scoped the check to an agent —
      // anonymous/seller-only flows still get the soft warning.
      if (command.agentId) {
        const hard = duplicateCandidates.find(
          (c) => c.sameAgent && c.addressMatch && c.distanceMeters <= DUPLICATE_RADIUS_METERS,
        );
        if (hard) {
          throw new DomainException(
            ErrorCode.CONFLICT,
            'Tin đăng trùng lặp: cùng môi giới đã đăng bất động sản tại địa chỉ này',
            HttpStatus.CONFLICT,
            {
              duplicateListingId: hard.listingId,
              duplicatePropertyId: hard.propertyId,
              distanceMeters: hard.distanceMeters,
            },
          );
        }
      }

      // Create property
      const propertyId = createId();
      const extras = command.extras ?? {};
      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,
        furnishing: extras.furnishing ?? null,
        propertyCondition: extras.propertyCondition ?? null,
        balconyDirection: extras.balconyDirection ?? null,
        maintenanceFeeVND: extras.maintenanceFeeVND ?? null,
        parkingSlots: extras.parkingSlots ?? null,
        viewType: extras.viewType ?? [],
        petFriendly: extras.petFriendly ?? null,
        suitableFor: extras.suitableFor ?? [],
        whyThisLocation: extras.whyThisLocation ?? 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),
      ]);

      // Soft warnings: every nearby candidate that didn't trigger 409 above.
      const duplicateWarnings: DuplicateWarning[] = duplicateCandidates.map((c) => ({
        listingId: c.listingId,
        propertyId: c.propertyId,
        title: c.title,
        address: c.address,
        district: c.district,
        distanceMeters: c.distanceMeters,
        addressMatch: c.addressMatch,
        sameAgent: c.sameAgent,
      }));

      // 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,
      };
    } catch (error) {
      if (error instanceof DomainException) throw error;
      this.logger.error(
        `Failed to create listing: ${error instanceof Error ? error.message : error}`,
        error instanceof Error ? error.stack : undefined,
        this.constructor.name,
      );
      throw new InternalServerErrorException('Không thể tạo tin đăng bất động sản');
    }
  }
}
