import { Inject, InternalServerErrorException } from '@nestjs/common';
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs';
import {
  CacheService,
  CachePrefix,
  CacheTTL,
  DomainException,
  NotFoundException,
  LoggerService,
} from '@modules/shared';
import {
  VALUATION_REPOSITORY,
  type IValuationRepository,
} from '../../../domain/repositories/valuation.repository';
import { type Comparable } from '../../../domain/services/avm-service';
import { generateConfidenceExplanation } from '../../../infrastructure/services/confidence-explanation.helper';
import { ValuationExplanationQuery } from './valuation-explanation.query';

/** A single feature contribution in the explanation. */
export interface FeatureContribution {
  feature: string;
  importance: number;
}

export interface ValuationExplanationDto {
  valuationId: string;
  propertyId: string;
  modelVersion: string;
  confidence: number;
  confidenceExplanation: string;
  estimatedPrice: string;
  pricePerM2: number;
  topDrivers: FeatureContribution[];
  comparables: Comparable[];
  valuedAt: string;
}

/**
 * Extracts feature importances from the stored features payload. The payload
 * shape varies by model (residential v1 comparable-weighted, residential v2
 * ensemble, industrial). We defensively inspect the known keys and fall back
 * to deriving importance from `features.features` (key => weight) objects.
 */
function extractTopDrivers(features: unknown, limit = 5): FeatureContribution[] {
  if (features == null || typeof features !== 'object') return [];
  const asRecord = features as Record<string, unknown>;

  // Preferred: pre-computed drivers array (industrial AVM, AI service output).
  const drivers = asRecord['drivers'] ?? asRecord['top_drivers'] ?? asRecord['topDrivers'];
  if (Array.isArray(drivers)) {
    return drivers
      .map((d) => {
        if (!d || typeof d !== 'object') return null;
        const rec = d as Record<string, unknown>;
        const feature = rec['feature'] ?? rec['name'];
        const importance = rec['importance'] ?? rec['contribution'] ?? rec['weight'];
        if (typeof feature !== 'string' || typeof importance !== 'number') return null;
        return { feature, importance } as FeatureContribution;
      })
      .filter((d): d is FeatureContribution => d !== null)
      .sort((a, b) => Math.abs(b.importance) - Math.abs(a.importance))
      .slice(0, limit);
  }

  // Fallback: features is an object of {feature: importance}.
  const entries = Object.entries(asRecord).filter(
    ([, v]) => typeof v === 'number',
  ) as Array<[string, number]>;
  return entries
    .sort((a, b) => Math.abs(b[1]) - Math.abs(a[1]))
    .slice(0, limit)
    .map(([feature, importance]) => ({ feature, importance }));
}

function extractComparables(comparables: unknown): Comparable[] {
  if (!Array.isArray(comparables)) return [];
  return comparables.filter(
    (c): c is Comparable => c != null && typeof c === 'object',
  );
}

@QueryHandler(ValuationExplanationQuery)
export class ValuationExplanationHandler
  implements IQueryHandler<ValuationExplanationQuery> {
  constructor(
    @Inject(VALUATION_REPOSITORY)
    private readonly valuationRepo: IValuationRepository,
    private readonly cache: CacheService,
    private readonly logger: LoggerService,
  ) {}

  async execute(query: ValuationExplanationQuery): Promise<ValuationExplanationDto> {
    try {
      const cacheKey = CacheService.buildKey(
        CachePrefix.VALUATION,
        'explain',
        query.valuationId,
      );

      return await this.cache.getOrSet(
        cacheKey,
        async () => {
          const entity = await this.valuationRepo.findById(query.valuationId);
          if (!entity) {
            throw new NotFoundException('Valuation', query.valuationId);
          }

          const comparables = extractComparables(entity.comparables);
          const topDrivers = extractTopDrivers(entity.features);

          return {
            valuationId: entity.id,
            propertyId: entity.propertyId,
            modelVersion: entity.modelVersion,
            confidence: entity.confidence,
            confidenceExplanation: generateConfidenceExplanation(
              entity.confidence,
              comparables.length,
            ),
            estimatedPrice: entity.estimatedPrice.toString(),
            pricePerM2: entity.pricePerM2,
            topDrivers,
            comparables,
            valuedAt: entity.createdAt.toISOString(),
          };
        },
        CacheTTL.MARKET_DATA,
        'valuation_explanation',
      );
    } catch (error) {
      if (error instanceof DomainException) throw error;
      this.logger.error(
        `Valuation explanation failed for ${query.valuationId}: ${error instanceof Error ? error.message : error}`,
        error instanceof Error ? error.stack : undefined,
        this.constructor.name,
      );
      throw new InternalServerErrorException(
        'Không thể lấy giải thích định giá. Vui lòng thử lại sau.',
      );
    }
  }
}
