import { BadRequestException, ConflictException, Injectable } from '@nestjs/common';
import { SaveRoomDecorPlacementDto } from '../../../business/room_decor/dto/save_room_decor_placement.dto';
import { ResetRoomDecorPlacementsDto } from '../../../business/room_decor/dto/reset_room_decor_placements.dto';
import { RoomDecorPlacements } from './room_decor_placements.entity';

@Injectable()
export class RoomDecorPlacementsMemoryService {
  private placements: Map<bigint, RoomDecorPlacements> = new Map();
  private userDecorIndex: Map<string, bigint> = new Map();
  private currentId: bigint = BigInt(1);
  private layoutStates: Map<bigint, {
    revision: number;
    last_mutation_id: string | null;
    last_mutation_scope: string | null;
  }> = new Map();

  async listPlacements(userId: bigint): Promise<RoomDecorPlacements[]> {
    return Array.from(this.placements.values())
      .filter((row) => row.user_id === userId)
      .sort((a, b) => {
        const createdDiff = a.created_at.getTime() - b.created_at.getTime();
        return createdDiff !== 0 ? createdDiff : Number(a.id - b.id);
      });
  }

  async getSnapshot(userId: bigint): Promise<{ revision: number; placements: RoomDecorPlacements[] }> {
    const state = this.getLayoutState(userId);
    return { revision: state.revision, placements: await this.listPlacements(userId) };
  }

  async savePlacement(
    userId: bigint,
    placement: SaveRoomDecorPlacementDto,
  ): Promise<{ revision: number; placement: RoomDecorPlacements }> {
    const state = this.getLayoutState(userId);
    const decorId = this.normalizeDecorId(placement.decor_id);
    const mutationScope = `decor:${decorId}`;
    const key = this.indexKey(userId, decorId);
    const existingId = this.userDecorIndex.get(key);
    const row = existingId ? this.placements.get(existingId) as RoomDecorPlacements : new RoomDecorPlacements();
    if (placement.mutation_revision === state.revision
      && placement.mutation_id === state.last_mutation_id
      && mutationScope === state.last_mutation_scope) {
      if (!existingId) throw new ConflictException('装修 mutation 状态不完整，请刷新布局');
      return { revision: state.revision, placement: row };
    }
    this.assertNextRevision(state, placement.layout_revision, placement.mutation_revision);
    if (!existingId) {
      row.id = this.currentId++;
      row.user_id = userId;
      row.decor_id = decorId;
      row.created_at = new Date();
      this.userDecorIndex.set(key, row.id);
      this.placements.set(row.id, row);
    }
    row.placed = placement.placed;
    row.position_x = placement.placed ? Number(placement.position_x ?? row.position_x ?? 0) : null;
    row.position_y = placement.placed ? Number(placement.position_y ?? row.position_y ?? 0) : null;
    row.scale = Number(placement.scale ?? row.scale ?? 1);
    row.rotation_degrees = Number(placement.rotation_degrees ?? row.rotation_degrees ?? 0);
    row.z_index = Number(placement.z_index ?? row.z_index ?? 0);
    row.mutation_revision = placement.mutation_revision;
    row.updated_at = new Date();
    state.revision = placement.mutation_revision;
    state.last_mutation_id = placement.mutation_id;
    state.last_mutation_scope = mutationScope;
    return { revision: state.revision, placement: row };
  }

  async resetPlacements(
    userId: bigint,
    mutation: ResetRoomDecorPlacementsDto,
  ): Promise<{ revision: number; placements: RoomDecorPlacements[] }> {
    const state = this.getLayoutState(userId);
    if (!(mutation.mutation_revision === state.revision
      && mutation.mutation_id === state.last_mutation_id
      && state.last_mutation_scope === 'reset')) {
      this.assertNextRevision(state, mutation.layout_revision, mutation.mutation_revision);
      for (const row of this.placements.values()) {
        if (row.user_id !== userId) continue;
        row.placed = false;
        row.position_x = null;
        row.position_y = null;
        row.mutation_revision = mutation.mutation_revision;
        row.updated_at = new Date();
      }
      state.revision = mutation.mutation_revision;
      state.last_mutation_id = mutation.mutation_id;
      state.last_mutation_scope = 'reset';
    }
    return { revision: state.revision, placements: await this.listPlacements(userId) };
  }

  private getLayoutState(userId: bigint): {
    revision: number;
    last_mutation_id: string | null;
    last_mutation_scope: string | null;
  } {
    let state = this.layoutStates.get(userId);
    if (!state) {
      state = { revision: 0, last_mutation_id: null, last_mutation_scope: null };
      this.layoutStates.set(userId, state);
    }
    return state;
  }

  private assertNextRevision(
    state: { revision: number },
    layoutRevision: number,
    mutationRevision: number,
  ): void {
    if (layoutRevision !== state.revision || mutationRevision !== state.revision + 1) {
      throw new ConflictException({
        message: '装修布局版本已过期，请刷新后重试',
        current_revision: state.revision,
      });
    }
  }

  private indexKey(userId: bigint, decorId: string): string {
    return `${userId.toString()}:${decorId}`;
  }

  private normalizeDecorId(decorId: string): string {
    const normalized = (decorId || '').trim();
    if (!/^[A-Za-z0-9_:-]{1,100}$/.test(normalized)) {
      throw new BadRequestException('摆件ID格式不正确');
    }
    return normalized;
  }
}
