import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
import { Types } from 'mongoose';
import { buildPaginatedResponse } from '../../common/utils/pagination.util';
import { resolveManagedFileUrl } from '../../common/utils/public-url.util';
import { UsersRepository } from '../users/users.repository';
import { BlockListQueryDto } from './dto/block-list-query.dto';
import { BlocksRepository } from './blocks.repository';

@Injectable()
export class BlocksService {
  constructor(
    private readonly blocksRepository: BlocksRepository,
    private readonly usersRepository: UsersRepository,
  ) {}

  async block(currentUserId: string, targetUserId: string) {
    this.assertValidTarget(currentUserId, targetUserId);
    const target = await this.usersRepository.findById(targetUserId);
    if (!target || target.isDisabled) {
      throw new NotFoundException('Target user not found');
    }

    await this.blocksRepository.create(currentUserId, targetUserId);
    return { blocked: true, targetUserId };
  }

  async unblock(currentUserId: string, targetUserId: string) {
    this.assertValidTarget(currentUserId, targetUserId);
    await this.blocksRepository.remove(currentUserId, targetUserId);
    return { blocked: false, targetUserId };
  }

  async getStatus(currentUserId: string, targetUserId: string) {
    this.assertValidTarget(currentUserId, targetUserId);
    const [iBlocked, blockedMe] = await Promise.all([
      this.blocksRepository.findOne(currentUserId, targetUserId),
      this.blocksRepository.findOne(targetUserId, currentUserId),
    ]);

    return { targetUserId, iBlocked: !!iBlocked, blockedMe: !!blockedMe };
  }

  async listBlockedUsers(currentUserId: string, query: BlockListQueryDto) {
    const page = query.page ?? 1;
    const limit = query.limit ?? 20;
    const skip = (page - 1) * limit;

    const [rows, total] = await Promise.all([
      this.blocksRepository.findBlockedUsers(currentUserId, skip, limit, query.search),
      this.blocksRepository.countBlockedUsers(currentUserId, query.search),
    ]);

    const items = rows.map((row) => this.serializeBlockedUser(row));

    return buildPaginatedResponse(items, {
      page,
      limit,
      total,
      offset: skip,
    });
  }

  async getBlockedIds(currentUserId: string): Promise<string[]> {
    return this.blocksRepository.findBlockedIds(currentUserId);
  }

  async getInvisibleUserIds(currentUserId: string): Promise<string[]> {
    return this.blocksRepository.findBlockingOrBlockedIds(currentUserId);
  }

  async assertNoBlockBetween(currentUserId: string, targetUserId: string): Promise<void> {
    this.assertValidTarget(currentUserId, targetUserId);
    const block = await this.blocksRepository.findAnyBetween(currentUserId, targetUserId);
    if (block) {
      throw new NotFoundException('User not found');
    }
  }

  private assertValidTarget(currentUserId: string, targetUserId: string): void {
    if (!Types.ObjectId.isValid(targetUserId)) {
      throw new BadRequestException('Invalid target user id');
    }
    if (currentUserId === targetUserId) {
      throw new BadRequestException('You cannot block yourself');
    }
  }

  private serializeBlockedUser(row: any) {
    const blockedUser = row.blockedId ?? {};
    const id = blockedUser._id?.toString?.() ?? blockedUser.id?.toString?.() ?? '';
    return {
      userId: id,
      name: blockedUser.name ?? '',
      username: blockedUser.username ?? '',
      avatar: resolveManagedFileUrl(blockedUser.avatar ?? ''),
      stageName: blockedUser.stageName ?? '',
      blockedAt: row.createdAt instanceof Date ? row.createdAt.toISOString() : (row.createdAt ?? null),
    };
  }
}
