import { BadRequestException, Inject, Injectable } from '@nestjs/common';
import { InventoryService } from '../player/inventory.service';
import { SocialService } from '../social/social.service';
import { SaveRoomDecorPlacementDto } from './dto/save_room_decor_placement.dto';
import { ResetRoomDecorPlacementsDto } from './dto/reset_room_decor_placements.dto';
import {
  ROOM_DECOR_BED_DEFAULT_SCALE,
  ROOM_DECOR_DEFINITIONS,
  ROOM_DECOR_LEGACY_DEFAULTS,
  ROOM_DECOR_LEGACY_BED_MAX_SCALE,
  ROOM_DECOR_LEGACY_WALL_DECOR_SCALES,
  ROOM_DECOR_ROOM_SCALE,
  RoomDecorDefinition,
  findRoomDecorDefinition,
} from './room_decor_catalog';

interface UserRoomDecorRow {
  decor_id: string;
  placed: boolean;
  position_x: number | null;
  position_y: number | null;
  scale: number;
  rotation_degrees?: number;
  z_index: number;
  mutation_revision?: number;
}

interface IRoomDecorPlacementsService {
  getSnapshot(userId: bigint): Promise<{ revision: number; placements: UserRoomDecorRow[] }>;
  savePlacement(userId: bigint, placement: SaveRoomDecorPlacementDto): Promise<{ revision: number; placement: UserRoomDecorRow }>;
  resetPlacements(userId: bigint, mutation: ResetRoomDecorPlacementsDto): Promise<{ revision: number; placements: UserRoomDecorRow[] }>;
}

interface RoomDecorPayloadPlacement {
  position_x: number | null;
  position_y: number | null;
  scale: number;
  rotation_degrees: number;
}

@Injectable()
export class RoomDecorService {
  constructor(
    @Inject('IRoomDecorPlacementsService') private readonly roomDecorPlacementsService: IRoomDecorPlacementsService,
    private readonly inventoryService: InventoryService,
    private readonly socialService: SocialService,
  ) {}

  async getInventory(userId: bigint) {
    const [inventory, snapshot] = await Promise.all([
      this.inventoryService.listInventory(userId, 'room_decor'),
      this.roomDecorPlacementsService.getSnapshot(userId),
    ]);
    const placements = snapshot.placements;
    const placementByDecorId = new Map(placements.map((row) => [row.decor_id, row]));
    const rows = inventory.room_decor_ids.map((decorId) => {
      const definition = findRoomDecorDefinition(decorId);
      const placement = placementByDecorId.get(decorId);
      return placement ?? {
        decor_id: decorId,
        placed: false,
        position_x: definition?.default_position.x ?? null,
        position_y: definition?.default_position.y ?? null,
        scale: definition?.default_scale ?? 1,
        rotation_degrees: definition?.default_rotation_degrees ?? 0,
        z_index: definition?.default_z_index ?? 0,
      };
    });
    return {
      items: rows
        .filter((row) => findRoomDecorDefinition(row.decor_id))
        .map((row) => this.toPayload(row)),
      definitions: ROOM_DECOR_DEFINITIONS,
      revision: snapshot.revision,
    };
  }

  async savePlacement(userId: bigint, placement: SaveRoomDecorPlacementDto) {
    const decorId = placement.decor_id?.trim() || '';
    const definition = findRoomDecorDefinition(decorId);
    if (!definition) {
      throw new BadRequestException('摆件不存在或暂未开放');
    }
    if (!(await this.inventoryService.hasAsset(userId, 'room_decor', decorId))) {
      throw new BadRequestException('尚未拥有该房间摆件');
    }
    const rotationDegrees = placement.rotation_degrees ?? definition.default_rotation_degrees;
    if (!definition.rotation_steps.includes(rotationDegrees)) {
      throw new BadRequestException('该摆件不支持此旋转角度');
    }
    const positionX = this.snapToGrid(placement.position_x ?? definition.default_position.x, definition.grid_size);
    const positionY = this.snapToGrid(placement.position_y ?? definition.default_position.y, definition.grid_size);
    if (placement.placed && !this.isWithinPlacementBounds(positionX, positionY, definition)) {
      throw new BadRequestException(definition.placement_surface === 'wall'
        ? '墙面挂饰只能摆放在墙面网格内'
        : '地面家具只能摆放在地面网格内');
    }
    const result = await this.roomDecorPlacementsService.savePlacement(userId, {
      ...placement,
      decor_id: decorId,
      position_x: positionX,
      position_y: positionY,
      scale: placement.scale ?? definition.default_scale,
      rotation_degrees: rotationDegrees,
      z_index: placement.z_index ?? definition.default_z_index,
    });
    return {
      ...this.toPayload(result.placement),
      layout_revision: result.revision,
    };
  }

  async getRoomView(viewerId: bigint, ownerId: bigint) {
    const owner = await this.socialService.getRoomOwnerProfile(viewerId, ownerId);
    const [inventory, snapshot] = await Promise.all([
      this.inventoryService.listInventory(ownerId, 'room_decor'),
      this.roomDecorPlacementsService.getSnapshot(ownerId),
    ]);
    const placements = snapshot.placements;
    const ownedDecorIds = new Set(inventory.room_decor_ids);
    return {
      owner,
      read_only: true,
      items: placements
        .filter((row) => row.placed && ownedDecorIds.has(row.decor_id) && Boolean(findRoomDecorDefinition(row.decor_id)))
        .map((row) => this.toPayload(row)),
      definitions: ROOM_DECOR_DEFINITIONS,
      revision: snapshot.revision,
    };
  }

  async resetPlacements(userId: bigint, mutation: ResetRoomDecorPlacementsDto) {
    await this.roomDecorPlacementsService.resetPlacements(userId, mutation);
    return this.getInventory(userId);
  }

  private toPayload(row: UserRoomDecorRow) {
    const definition = findRoomDecorDefinition(row.decor_id);
    const placement = this.normalizedPlacement(row, definition);
    return {
      decor_id: row.decor_id,
      name: definition?.name ?? row.decor_id,
      item_id: definition?.item_id ?? '',
      icon: definition?.icon ?? '',
      texture: definition?.texture ?? definition?.icon ?? '',
      placed: row.placed,
      position_x: placement.position_x,
      position_y: placement.position_y,
      scale: placement.scale,
      rotation_degrees: placement.rotation_degrees,
      z_index: row.z_index ?? definition?.default_z_index ?? 0,
      mutation_revision: row.mutation_revision ?? 0,
      category: definition?.category ?? 'floor',
      placement_surface: definition?.placement_surface ?? 'floor',
      placement_bounds: definition?.placement_bounds,
      grid_size: definition?.grid_size ?? 16,
      rotation_steps: definition?.rotation_steps ?? [0],
      stackable: definition?.stackable ?? false,
      default_position: definition?.default_position ?? { x: 0, y: 0 },
      default_scale: definition?.default_scale ?? 1,
      default_rotation_degrees: definition?.default_rotation_degrees ?? 0,
      default_z_index: definition?.default_z_index ?? 0,
    };
  }

  private normalizedPlacement(
    row: UserRoomDecorRow,
    definition?: RoomDecorDefinition,
  ): RoomDecorPayloadPlacement {
    const usesLegacyPlacement = this.usesLegacyPlacement(row);
    return {
      position_x: this.normalizedPositionValue(row.position_x, definition?.default_position.x ?? 0, usesLegacyPlacement),
      position_y: this.normalizedPositionValue(row.position_y, definition?.default_position.y ?? 0, usesLegacyPlacement),
      scale: this.normalizedScale(row, definition),
      rotation_degrees: this.normalizedRotation(row, definition),
    };
  }

  private normalizedScale(row: UserRoomDecorRow, definition?: Pick<RoomDecorDefinition, 'default_scale'>) {
    const scale = row.scale ?? definition?.default_scale ?? 1;
    if (!row.placed && definition) {
      return definition.default_scale;
    }
    if (this.usesLegacyPlacement(row)) {
      return definition?.default_scale ?? scale;
    }
    if (this.isBedDecor(row.decor_id) && scale <= ROOM_DECOR_LEGACY_BED_MAX_SCALE) {
      return ROOM_DECOR_BED_DEFAULT_SCALE;
    }
    if (row.decor_id === 'whale_floor_rug' && scale >= 0.22 && scale <= 0.30) {
      return definition?.default_scale ?? scale;
    }
    if (row.decor_id === 'dev_whale_bookshelf' && (Math.abs(scale - 0.4) <= 0.001 || (scale >= 0.51 && scale <= 0.53))) {
      // Preserve the old room-fit footprint after switching to the larger mall texture.
      return definition?.default_scale ?? scale;
    }
    if (this.isWallBadgeDecor(row.decor_id) && this.isLegacyWallDecorScale(scale)) {
      return definition?.default_scale ?? scale;
    }
    if (row.decor_id === 'whale_tail_lamp' && scale <= 0.2) {
      return definition?.default_scale ?? scale;
    }
    return scale;
  }

  private normalizedPositionValue(value: number | null, fallback: number, usesLegacyPlacement: boolean) {
    if (value === null || value === undefined) {
      return fallback;
    }
    return usesLegacyPlacement ? Math.round(value * ROOM_DECOR_ROOM_SCALE) : value;
  }

  private normalizedRotation(row: UserRoomDecorRow, definition?: RoomDecorDefinition): number {
    const rotation = row.rotation_degrees ?? definition?.default_rotation_degrees ?? 0;
    return definition?.rotation_steps.includes(rotation) ? rotation : definition?.default_rotation_degrees ?? 0;
  }

  private snapToGrid(value: number, gridSize: number): number {
    return Math.round(value / gridSize) * gridSize;
  }

  private isWithinPlacementBounds(x: number, y: number, definition: RoomDecorDefinition): boolean {
    const bounds = definition.placement_bounds;
    return x >= bounds.min_x && x <= bounds.max_x && y >= bounds.min_y && y <= bounds.max_y;
  }

  private usesLegacyPlacement(row: UserRoomDecorRow) {
    const legacy = ROOM_DECOR_LEGACY_DEFAULTS[row.decor_id];
    if (!legacy) {
      return false;
    }
    const scale = row.scale ?? legacy.scale;
    if (this.isBedDecor(row.decor_id) && scale <= ROOM_DECOR_LEGACY_BED_MAX_SCALE) {
      return true;
    }
    return Math.abs(scale - legacy.scale) <= 0.001;
  }

  private isBedDecor(decorId: string) {
    return decorId.endsWith('_bed');
  }

  private isWallBadgeDecor(decorId: string) {
    return decorId.startsWith('datawhale_') && decorId.endsWith('_badge');
  }

  private isLegacyWallDecorScale(scale: number) {
    return ROOM_DECOR_LEGACY_WALL_DECOR_SCALES.some((legacyScale) => Math.abs(scale - legacyScale) <= 0.001);
  }
}
