import { FilterQuery, PipelineStage, Types } from 'mongoose';

import {
  IPartnerNetwork,
  PopulatedPartnerNetwork,
  QueryFilter,
  UpdatePartnerNetworkBody,
} from '@/modules/partnerNetwork/partnerNetwork.interface';
import { PartnerNetwork } from '@/modules/partnerNetwork/partnerNetwork.model';

import { PaginateOptions } from '@/shared/utils/plugins/paginate/paginate';
import { ApiError } from '@/shared/utils/errors';
import { defaultStatus } from '@/shared/utils/responseCode/httpStatusAlias';
import responseCodes from '@/shared/utils/responseCode/responseCode';
import { validatePartnerRequestIds } from './partnerNetwork.helper';
import User from '@/modules/user/user.model';
import {
  buildDiscoverPipeline,
  buildPartnerNetworkPipeline,
  computePotentialMatches,
} from '@/modules/partnerNetwork/partnerNetwork.pipeline';
import { getAvatarUrl, getObjectId } from '@/shared/utils/commonHelper';
import IndividualProperties from '../individualProperties/individualProperties.model';
import { TemplateName } from '@/shared/constants';
import config from '@/shared/config/config';
import { safeDeleteById } from '@/shared/utils/guard/ref-guard';
import { Status } from './partnerNetwork.constant';
import { sendEmailWithActiveTemplate } from '../communication/email/email.helper';
import { createNotification } from '../notification/notification.service';
import {
  NotificationStatus,
  UserType,
} from '../notification/notification.constant';

const { PartnerNetworkResponseCodes } = responseCodes;

export const queryPartnerNetwork = async (
  authUserId: Types.ObjectId | string,
  companyId: Types.ObjectId | string,
  type: string,
  rawFilter: QueryFilter,
  options: PaginateOptions,
) => {
  const { search, company, ...rest } = rawFilter;
  const currentUserId = getObjectId(authUserId);
  const currentCompanyId = getObjectId(companyId);

  const matchStage: FilterQuery<IPartnerNetwork> = {
    _id: { $ne: currentUserId },
    isDeleted: false,
    isEmailVerified: true,
    status: Status.ACTIVE,
    ...rest,
  };

  let pipeline: PipelineStage[];

  const currentUserProperties = await IndividualProperties.find({
    createdBy: currentUserId,
    status: 'active',
  })
    .select(['listingType', 'propertyType', 'city', 'area'])
    .populate({
      path: 'propertyType',
      select: 'name',
    });

  const mappedProperties = currentUserProperties.map((prop) => ({
    _id: prop._id,
    listingType: prop.listingType,
    city: prop.city,
    categoryName: prop.propertyType?.name || null,
  }));

  if (type === 'discover') {
    const existingRequests = await PartnerNetwork.find({
      companyId: getObjectId(currentCompanyId),
      $or: [
        { senderId: getObjectId(currentUserId) },
        { receiverId: getObjectId(currentUserId) },
      ],
    }).select(['senderId', 'receiverId']);

    // Extract counterpart userIds
    const excludeIds = existingRequests.map((req) =>
      req.senderId.toString() === currentUserId.toString()
        ? req.receiverId.toString()
        : req.senderId.toString(),
    );

    // Add $nin condition to exclude these users
    if (excludeIds.length > 0)
      matchStage._id = {
        $ne: currentUserId,
        $nin: excludeIds.map((id) => getObjectId(id)),
      };

    matchStage['company.id'] = { $ne: currentCompanyId };

    pipeline = buildDiscoverPipeline({
      search,
      matchStage,
      companyType: 'broker',
    });

    const user = await User.paginate(
      {},
      {
        ...options,
        aggregation: pipeline,
      },
    );

    const userIds = user.results.map((u) => u.id);

    const propertyPipeline: PipelineStage[] = [
      {
        $match: {
          createdBy: { $in: userIds.map((id) => getObjectId(id)) },
          isSharing: true,
        },
      },
      {
        $lookup: {
          from: 'categories',
          localField: 'propertyType',
          foreignField: '_id',
          as: 'categoryInfo',
        },
      },
      {
        $unwind: {
          path: '$categoryInfo',
          preserveNullAndEmptyArrays: true,
        },
      },
      {
        $lookup: {
          from: 'cities',
          localField: 'city',
          foreignField: '_id',
          as: 'cityInfo',
        },
      },
      {
        $unwind: {
          path: '$cityInfo',
          preserveNullAndEmptyArrays: true,
        },
      },
      {
        $project: {
          title: 1,
          price: 1,
          location: 1,
          createdBy: 1,
          categoryName: '$categoryInfo.name',
          city: '$cityInfo._id',
          listingType: 1,
        },
      },
    ];

    const properties = await IndividualProperties.aggregate(propertyPipeline);

    // console.log(properties);

    const propertyMap = new Map<
      string,
      { count: number; categoryNames: Set<string> }
    >();

    for (const prop of properties) {
      const userId = prop.createdBy.toString();
      if (!propertyMap.has(userId))
        propertyMap.set(userId, { count: 0, categoryNames: new Set() });

      const record = propertyMap.get(userId)!;
      record.count++;
      if (prop.categoryName) record.categoryNames.add(prop.categoryName);
    }

    const updatedUsers = user.results.map((u) => {
      const data = propertyMap.get(u.id.toString()) || {
        count: 0,
        categoryNames: new Set(),
      };

      const userProps = properties.filter(
        (p) => p.createdBy.toString() === u.id.toString(),
      );

      const potentialMatches = computePotentialMatches(
        userProps,
        mappedProperties,
      );

      return {
        ...u,
        totalProperties: data.count,
        categories: Array.from(data.categoryNames),
        potentialMatches,
      };
    });

    return {
      ...user,
      results: updatedUsers,
    };
  } else {
    const partnerNetworkMatchStage: FilterQuery<IPartnerNetwork> = {
      status: type,
      ...rest,
    };
    pipeline = buildPartnerNetworkPipeline({
      search,
      matchStage: partnerNetworkMatchStage,
      currentUserId: authUserId as string,
    });

    const partnerNetwork = await PartnerNetwork.paginate(
      {},
      {
        ...options,
        aggregation: pipeline,
      },
    );
    const otherUserIds = partnerNetwork.results
      .map((p) => p.otherUserId?.toString())
      .filter(Boolean);

    const propertyPipeline: PipelineStage[] = [
      {
        $match: {
          createdBy: { $in: otherUserIds.map((id) => getObjectId(id)) },
          isSharing: true,
        },
      },
      {
        $lookup: {
          from: 'categories',
          localField: 'propertyType',
          foreignField: '_id',
          as: 'categoryInfo',
        },
      },
      {
        $unwind: {
          path: '$categoryInfo',
          preserveNullAndEmptyArrays: true,
        },
      },
      {
        $lookup: {
          from: 'cities',
          localField: 'city',
          foreignField: '_id',
          as: 'cityInfo',
        },
      },
      {
        $unwind: {
          path: '$cityInfo',
          preserveNullAndEmptyArrays: true,
        },
      },
      {
        $project: {
          createdBy: 1,
          cityName: '$cityInfo.name',
          categoryName: '$categoryInfo.name',
          city: '$cityInfo._id',
          listingType: 1,
          title: 1,
        },
      },
    ];

    const properties = await IndividualProperties.aggregate(propertyPipeline);

    const propertyMap = new Map<
      string,
      { count: number; categoryNames: Set<string> }
    >();

    for (const prop of properties) {
      const userId = prop.createdBy.toString();
      if (!propertyMap.has(userId))
        propertyMap.set(userId, { count: 0, categoryNames: new Set() });

      const record = propertyMap.get(userId)!;
      record.count++;
      if (prop.categoryName) record.categoryNames.add(prop.categoryName);
    }

    const updatedPartners = partnerNetwork.results.map((p) => {
      const userId = p.otherUserId?.toString();
      const data = propertyMap.get(userId) || {
        count: 0,
        categoryNames: new Set(),
      };

      const userProps = properties.filter(
        (prop) => prop.createdBy.toString() === userId,
      );

      const potentialMatches = computePotentialMatches(
        userProps,
        mappedProperties,
      );

      return {
        ...p,
        totalProperties: data.count,
        categories: Array.from(data.categoryNames),
        potentialMatches,
      };
    });

    return {
      ...partnerNetwork,
      results: updatedPartners,
    };
  }
};

export const connectPartnerNetwork = async ({
  senderId,
  receiverId,
  companyId,
}: {
  senderId: string;
  receiverId: string;
  companyId: string;
}) => {
  try {
    await validatePartnerRequestIds(senderId, receiverId);

    const partnerNetwork = await PartnerNetwork.create({
      senderId,
      receiverId,
      companyId,
    });

    const user = await PartnerNetwork.findById(partnerNetwork._id)
      .populate([
        { path: 'companyId', select: 'name address' },
        { path: 'receiverId', select: 'firstName lastName email profileImage' },
      ])
      .lean<PopulatedPartnerNetwork>();

    await sendEmailWithActiveTemplate({
      to: user.receiverId.email as string,
      companyId: companyId,
      scenario: TemplateName.PartnerNetworkRequest,
      templateParams: {
        partnerName: `${user.firstName} ${user.lastName}`,
        profileImage:
          user.profileImage?.toString().trim() ||
          getAvatarUrl(`${user.firstName ?? ''} ${user.lastName ?? ''}`.trim()),

        companyName: user.companyId.name,
        location: user.companyId.address,
        acceptRequest: `${config.clientUrl}/partner-network`,
      },
    });

    const appRedirect = {
      screenType: 'PartnerNetwork_Request',
      id: receiverId,
    };

    const description = `${user.companyId.name} wants to connect with you on Makanify.`;

    await createNotification(
      {
        title: 'New partner network request',
        description: description,
        userType: UserType.SELF,
        user: getObjectId(receiverId),
        status: NotificationStatus.SEND,
      },
      appRedirect,
    );

    return true;
  } catch (error) {
    if (error instanceof ApiError) throw error;
    throw new ApiError(
      defaultStatus.OK,
      'Failed to connect partner network',
      true,
      '',
      PartnerNetworkResponseCodes.PARTNER_NETWORK_ERROR,
    );
  }
};

export const updatePartnerNetworkRequest = async (
  id: string,
  data: UpdatePartnerNetworkBody,
): Promise<boolean> => {
  try {
    const partnerNetwork = await PartnerNetwork.findByIdAndUpdate(id, data, {
      new: true,
      runValidators: true,
    })
      .populate([
        { path: 'senderId', select: 'firstName lastName email profileImage _id' },
        {
          path: 'receiverId', select: 'firstName lastName email profileImage _id company',
          populate: { path: 'company.id', select: 'name' },
        },
        { path: 'companyId', select: 'name _id' },
      ])
      .lean<PopulatedPartnerNetwork>();

    if (data?.status === Status.ACCEPTED) {
      await sendEmailWithActiveTemplate({
        to: partnerNetwork.receiverId.email as string,
        companyId: partnerNetwork.companyId._id.toString(),
        scenario: TemplateName.PartnerNetworkAccepted,
        templateParams: {
          partnerName: `${partnerNetwork.receiverId.firstName} ${partnerNetwork.receiverId.lastName}`,
          profileImage:
            partnerNetwork.receiverId?.profileImage?.toString().trim() ||
            getAvatarUrl(
              `${partnerNetwork.receiverId.firstName ?? ''} ${partnerNetwork.receiverId.lastName ?? ''}`.trim(),
            ),
          companyName: partnerNetwork.companyId.name,
          partnerProperties: `${config.clientUrl}/partner-network`,
        },
      });

      const appRedirect = {
        screenType: 'PartnerNetwork_Accepted',
        id: partnerNetwork.receiverId._id.toString(), // Receiver is the new partner for the sender
      };

      const receiverCompany = (partnerNetwork.receiverId as any).company?.id?.name || partnerNetwork.companyId.name;
      const description = `You are now connected with ${receiverCompany}. Start collaborating on listings.`;

      await createNotification(
        {
          title: 'Partner Connected',
          description: description,
          userType: UserType.SELF,
          user: getObjectId(partnerNetwork.senderId._id.toString()),
          status: NotificationStatus.SEND,
        },
        appRedirect,
      );
    }

    return true;
  } catch (error) {
    if (error instanceof ApiError) throw error;
    throw new ApiError(
      defaultStatus.OK,
      'Failed to update partner network request',
      true,
      '',
      PartnerNetworkResponseCodes.PARTNER_NETWORK_ERROR,
    );
  }
};

export const deletePartnerNetworkById = async (id: string): Promise<void> => {
  try {
    await safeDeleteById(
      PartnerNetwork,
      id,
      PartnerNetworkResponseCodes.PARTNER_NETWORK_IN_USE,
    );
  } catch (err: unknown) {
    if (err instanceof ApiError) throw err;

    throw new ApiError(
      defaultStatus.INTERNAL_SERVER_ERROR,
      'Failed to delete partner network',
      true,
      '',
      PartnerNetworkResponseCodes.PARTNER_NETWORK_ERROR,
    );
  }
};
