import {
  BadRequestException,
  ForbiddenException,
  Injectable,
  NotFoundException,
} from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model, Types } from 'mongoose';
import { PaginationQueryDto } from '../../common/dto/pagination-query.dto';
import { buildPaginatedResponse } from '../../common/utils/pagination.util';
import { BlocksRepository } from '../blocks/blocks.repository';
import { NotificationsService } from '../notifications/notifications.service';
import { PostsRepository } from '../posts/posts.repository';
import { UsersRepository } from '../users/users.repository';
import { CreateCollaborationRequestDto } from './dto/create-collaboration-request.dto';
import {
  CollaborationRequest,
  CollaborationRequestDocument,
} from './schemas/collaboration-request.schema';

@Injectable()
export class CollaborationRequestsService {
  constructor(
    @InjectModel(CollaborationRequest.name)
    private readonly collaborationRequestModel: Model<CollaborationRequestDocument>,
    private readonly postsRepository: PostsRepository,
    private readonly usersRepository: UsersRepository,
    private readonly blocksRepository: BlocksRepository,
    private readonly notificationsService: NotificationsService,
  ) {}

  async create(requesterId: string, postId: string, dto: CreateCollaborationRequestDto) {
    const targetUserId = dto.targetUserId;
    if (!Types.ObjectId.isValid(postId) || !Types.ObjectId.isValid(targetUserId)) {
      throw new BadRequestException('Invalid collaboration request');
    }
    if (requesterId === targetUserId) {
      throw new BadRequestException('You cannot invite yourself');
    }

    const [post, targetUser, block] = await Promise.all([
      this.postsRepository.findById(postId),
      this.usersRepository.findById(targetUserId),
      this.blocksRepository.findAnyBetween(requesterId, targetUserId),
    ]);
    if (!post) {
      throw new NotFoundException('Post not found');
    }
    if (post.authorId.toString() !== requesterId) {
      throw new ForbiddenException('Only the post owner can invite collaborators');
    }
    if (!targetUser || targetUser.isDisabled) {
      throw new NotFoundException('Target user not found');
    }
    if (block) {
      throw new BadRequestException('You cannot invite this user');
    }

    const filter = {
      postId: new Types.ObjectId(postId),
      requesterId: new Types.ObjectId(requesterId),
      targetUserId: new Types.ObjectId(targetUserId),
      status: 'pending',
    };
    const collaborationDetails = this.buildCollaborationDetails(dto);
    const existing = await this.collaborationRequestModel.findOne(filter).exec();
    const request = existing
      ? await this.updateExistingPendingRequest(existing.id, collaborationDetails)
      : await this.collaborationRequestModel
          .findOneAndUpdate(
            filter,
            {
              $setOnInsert: {
                postId: new Types.ObjectId(postId),
                requesterId: new Types.ObjectId(requesterId),
                targetUserId: new Types.ObjectId(targetUserId),
                status: 'pending',
                ...collaborationDetails,
              },
            },
            { new: true, upsert: true, setDefaultsOnInsert: true },
          )
          .exec();

    if (!existing) {
      await this.notificationsService.create({
        actorId: requesterId,
        recipientId: targetUserId,
        type: 'collaboration_request',
        referenceId: postId,
        resourceType: 'post',
        deepLink: `/posts/${postId}`,
        metadata: collaborationDetails,
      });
    }

    return { message: 'Collaboration request sent', request };
  }

  async getMine(targetUserId: string, query: PaginationQueryDto) {
    const page = query.page ?? 1;
    const limit = query.limit ?? 20;
    const skip = (page - 1) * limit;
    const filter = { targetUserId: new Types.ObjectId(targetUserId), status: 'pending' };
    const [items, total] = await Promise.all([
      this.collaborationRequestModel
        .find(filter)
        .populate({
          path: 'requesterId',
          select: 'name username stageName avatar isVerified isDisabled',
        })
        .populate({ path: 'postId' })
        .sort({ createdAt: -1 })
        .skip(skip)
        .limit(limit)
        .exec(),
      this.collaborationRequestModel.countDocuments(filter).exec(),
    ]);
    return buildPaginatedResponse(items, { page, limit, total, offset: skip });
  }

  async approve(targetUserId: string, requestId: string) {
    const request = await this.updateStatus(targetUserId, requestId, 'approved');
    await this.postsRepository.updateById(request.postId.toString(), {
      $addToSet: { collaboratorIds: request.targetUserId },
    });
    return { approved: true, request };
  }

  async reject(targetUserId: string, requestId: string) {
    const request = await this.updateStatus(targetUserId, requestId, 'rejected');
    return { rejected: true, request };
  }

  private buildCollaborationDetails(dto: CreateCollaborationRequestDto) {
    return {
      ...(dto.collaborationType ? { collaborationType: dto.collaborationType } : {}),
      ...(typeof dto.message === 'string' ? { message: dto.message.trim() } : {}),
      ...(typeof dto.attachmentUrl === 'string' ? { attachmentUrl: dto.attachmentUrl.trim() } : {}),
      ...(dto.attachmentType ? { attachmentType: dto.attachmentType } : {}),
    };
  }

  private async updateExistingPendingRequest(
    requestId: string,
    collaborationDetails: Record<string, unknown>,
  ) {
    if (!Object.keys(collaborationDetails).length) {
      return this.collaborationRequestModel.findById(requestId).exec();
    }

    return this.collaborationRequestModel
      .findByIdAndUpdate(requestId, { $set: collaborationDetails }, { new: true })
      .exec();
  }

  private async updateStatus(
    targetUserId: string,
    requestId: string,
    status: 'approved' | 'rejected',
  ) {
    if (!Types.ObjectId.isValid(requestId)) {
      throw new BadRequestException('Invalid collaboration request id');
    }
    const request = await this.collaborationRequestModel
      .findOneAndUpdate(
        {
          _id: new Types.ObjectId(requestId),
          targetUserId: new Types.ObjectId(targetUserId),
          status: 'pending',
        },
        { status },
        { new: true },
      )
      .exec();
    if (!request) {
      throw new NotFoundException('Collaboration request not found');
    }
    return request;
  }
}
