import { ApiError } from '@/shared/utils/errors';
import { defaultStatus } from '@/shared/utils/responseCode/httpStatusAlias';
import { Targets } from '@/modules/targets/targets.model';

import responseCodes from '@/shared/utils/responseCode/responseCode';
import {
  NewCreatedTargets,
  PersonalTarget,
  TargetMetric,
  TargetSummaryResponse,
  TeamMember,
  UpdateTargetsBody,
} from '@/modules/targets/targets.interface';
import { getObjectId } from '@/shared/utils/commonHelper';
import { Team } from '../teams/teams.model';
import { TargetType } from '@/shared/constants/enum.constant';
import dayjs from 'dayjs';
import type { Types } from 'mongoose';
import User from '@/modules/user/user.model';
import { getActivityAchievementsForMembers } from '@/modules/targets/targetActivityAchievements';
import {
  getSalesAchievementsByCalendarMonths,
  getSalesAchievementsForMembers,
} from '@/modules/targets/targetSalesAchievements';
import type { ITeamDoc } from '@/modules/teams/teams.interface';

const { TargetsResponseCodes } = responseCodes;

function mergeActivityIntoMetric(
  metric: TargetMetric,
  activityAchieved: number,
): TargetMetric {
  return { target: metric.target, achieved: activityAchieved };
}

function teamReceiverObjectIds(teamDoc: ITeamDoc): Types.ObjectId[] {
  const members = Array.isArray(teamDoc.members)
    ? teamDoc.members.map((m) => getObjectId(String(m)))
    : [];
  const leadId = teamDoc.lead?.toString();
  const memberStrings = members.map(String);
  const all =
    leadId && !memberStrings.includes(leadId)
      ? [...members, getObjectId(leadId)]
      : members;
  return all;
}

export const createTargets = async (data: NewCreatedTargets) => {
  const {
    targetType,
    companyId,
    team,
    period,
    salesAmount = 0,
    unitsSold = 0,
    meetings = 0,
    siteVisits = 0,
    calls = 0,
    createdBy,
  } = data;

  if (targetType === TargetType.Individual) {
    const { member } = data;
    if (!member)
      throw new ApiError(
        defaultStatus.BAD_REQUEST,
        'Member is required for individual target',
        true,
        '',
        TargetsResponseCodes.PAYMENTPLAN_ERROR,
      );

    const created = await Targets.create({
      companyId,
      targetType,
      period,
      team,
      member,
      salesAmount: { target: salesAmount, achieved: 0 },
      unitsSold: { target: unitsSold, achieved: 0 },
      meetings: { target: meetings, achieved: 0 },
      siteVisits: { target: siteVisits, achieved: 0 },
      calls: { target: calls, achieved: 0 },
      createdBy,
    });

    return created;
  }

  // Whole Team Target: assign to all members + team lead
  const teamDoc = await Team.findById(team).lean();

  if (
    !teamDoc ||
    !Array.isArray(teamDoc.members) ||
    teamDoc.members.length === 0
  )
    throw new ApiError(
      defaultStatus.BAD_REQUEST,
      'Team not found or has no members',
      true,
      '',
      TargetsResponseCodes.PAYMENTPLAN_ERROR,
    );

  const members = teamDoc.members.map(String);
  const leadId = teamDoc.lead?.toString();

  const allTargetReceivers =
    leadId && !members.includes(leadId) ? [...members, leadId] : members;

  const count = allTargetReceivers.length;

  // Divide each metric equally (you can round if needed)
  const perMemberPayload = allTargetReceivers.map((memberId) => ({
    companyId,
    targetType: TargetType.WholeTeam,
    period,
    team,
    member: memberId,
    salesAmount: { target: Math.floor(salesAmount / count), achieved: 0 },
    unitsSold: { target: Math.floor(unitsSold / count), achieved: 0 },
    meetings: { target: Math.floor(meetings / count), achieved: 0 },
    siteVisits: { target: Math.floor(siteVisits / count), achieved: 0 },
    calls: { target: Math.floor(calls / count), achieved: 0 },
    createdBy,
  }));

  const created = await Targets.insertMany(perMemberPayload);

  return created;
};

export const getTargetsById = async (id: string) => {
  const targets = await Targets.findById(id);
  if (!targets)
    throw new ApiError(
      defaultStatus.OK,
      'Targets not found',
      true,
      '',
      TargetsResponseCodes.PAYMENTPLAN_NOT_FOUND,
    );

  return targets;
};

export const updateTargets = async (
  id: string,
  updateData: UpdateTargetsBody,
): Promise<boolean | null> => {
  try {
    const result = await Targets.updateOne(
      { _id: getObjectId(id) },
      { $set: updateData },
    );

    if (result.matchedCount === 0)
      throw new ApiError(
        defaultStatus.OK,
        'Targets not found',
        false,
        '',
        TargetsResponseCodes.PAYMENTPLAN_NOT_FOUND,
      );

    return true;
  } catch (err: unknown) {
    if (err instanceof ApiError) throw err;
    throw new ApiError(
      defaultStatus.OK,
      'Failed to update targets',
      true,
      '',
      TargetsResponseCodes.PAYMENTPLAN_ERROR,
    );
  }
};

export const deleteTargets = async (id: string): Promise<void> => {
  try {
    const deletedAccount = await Targets.findByIdAndDelete(getObjectId(id));

    if (!deletedAccount)
      throw new ApiError(
        defaultStatus.NOT_FOUND,
        'Bank account not found',
        false,
        '',
        TargetsResponseCodes.PAYMENTPLAN_NOT_FOUND,
      );
  } catch (err: unknown) {
    if (err instanceof ApiError) throw err;

    throw new ApiError(
      defaultStatus.INTERNAL_SERVER_ERROR,
      'Failed to delete bank account',
      true,
      '',
      TargetsResponseCodes.PAYMENTPLAN_ERROR,
    );
  }
};

export const queryTargets = async (
  filter: Record<string, string> = {},
  options = {},
) => {
  try {
    const { search, ...rest } = filter;

    const query: Record<string, unknown> = {
      ...rest,
      ...(search && {
        name: { $regex: search, $options: 'i' },
      }),
    };

    return Targets.paginate(query, options);
  } catch (_error) {
    throw new ApiError(
      defaultStatus.INTERNAL_SERVER_ERROR,
      'Failed to query targets',
      true,
      '',
      TargetsResponseCodes.TARGET_ERROR,
    );
  }
};

export const getTargetSummary = async ({
  memberId,
  companyId,
  month,
  year,
  type,
  teamId,
}: {
  memberId: string;
  companyId: string;
  month: number;
  year: number;
  type: string;
  teamId?: string;
}): Promise<TargetSummaryResponse> => {
  try {
    const response: TargetSummaryResponse = {};
    const companyObjectId = getObjectId(companyId);

    const historicalMonths = Array.from({ length: 6 }).map((_, i) => {
      const date = dayjs(`${year}-${month}-01`).subtract(5 - i, 'month');
      return {
        label: date.format('MMM'),
        month: date.month() + 1,
        year: date.year(),
      };
    });

    const baseMatch = {
      companyId: companyObjectId,
    };

    const getMetricAggregation = () => ({
      $group: {
        _id: null,
        salesAmountTarget: { $sum: '$salesAmount.target' },
        salesAmountAchieved: { $sum: '$salesAmount.achieved' },
        unitsSoldTarget: { $sum: '$unitsSold.target' },
        unitsSoldAchieved: { $sum: '$unitsSold.achieved' },
        meetingsTarget: { $sum: '$meetings.target' },
        meetingsAchieved: { $sum: '$meetings.achieved' },
        siteVisitsTarget: { $sum: '$siteVisits.target' },
        siteVisitsAchieved: { $sum: '$siteVisits.achieved' },
        callsTarget: { $sum: '$calls.target' },
        callsAchieved: { $sum: '$calls.achieved' },
      },
    });

    if (type === 'individual') {
      const personalTargetMatch = {
        ...baseMatch,
        'period.month': month,
        'period.year': year,
        member: getObjectId(memberId),
      };

      const personalTargetsAggregation = await Targets.aggregate([
        { $match: personalTargetMatch },
        getMetricAggregation(),
        {
          $project: {
            _id: 0,
            salesAmount: {
              target: '$salesAmountTarget',
              achieved: '$salesAmountAchieved',
            },
            unitsSold: {
              target: '$unitsSoldTarget',
              achieved: '$unitsSoldAchieved',
            },
            meetings: {
              target: '$meetingsTarget',
              achieved: '$meetingsAchieved',
            },
            siteVisits: {
              target: '$siteVisitsTarget',
              achieved: '$siteVisitsAchieved',
            },
            calls: { target: '$callsTarget', achieved: '$callsAchieved' },
          },
        },
      ]);

      response.personal = personalTargetsAggregation.length
        ? (personalTargetsAggregation[0] as PersonalTarget)
        : null;

      const memberOid = getObjectId(memberId);
      const [{ totals: activityTotals }, { totals: salesTotals }] =
        await Promise.all([
          getActivityAchievementsForMembers(
            companyObjectId,
            [memberOid],
            month,
            year,
          ),
          getSalesAchievementsForMembers(
            companyObjectId,
            [memberOid],
            month,
            year,
          ),
        ]);

      if (response.personal) {
        const p = response.personal;
        response.personal = {
          ...p,
          salesAmount: mergeActivityIntoMetric(
            p.salesAmount,
            salesTotals.salesAmount,
          ),
          unitsSold: mergeActivityIntoMetric(
            p.unitsSold,
            salesTotals.unitsSold,
          ),
          calls: mergeActivityIntoMetric(p.calls, activityTotals.calls),
          meetings: mergeActivityIntoMetric(p.meetings, activityTotals.meetings),
          siteVisits: mergeActivityIntoMetric(
            p.siteVisits,
            activityTotals.siteVisits,
          ),
        };
      } else {
        response.personal = {
          salesAmount: mergeActivityIntoMetric(
            { target: 0, achieved: 0 },
            salesTotals.salesAmount,
          ),
          unitsSold: mergeActivityIntoMetric(
            { target: 0, achieved: 0 },
            salesTotals.unitsSold,
          ),
          meetings: mergeActivityIntoMetric(
            { target: 0, achieved: 0 },
            activityTotals.meetings,
          ),
          siteVisits: mergeActivityIntoMetric(
            { target: 0, achieved: 0 },
            activityTotals.siteVisits,
          ),
          calls: mergeActivityIntoMetric(
            { target: 0, achieved: 0 },
            activityTotals.calls,
          ),
        };
      }

      const historicalIndividualAggregation = await Targets.aggregate([
        {
          $match: {
            companyId: companyObjectId,
            member: getObjectId(memberId),
            $or: historicalMonths.map((m) => ({
              'period.month': m.month,
              'period.year': m.year,
            })),
          },
        },
        {
          $group: {
            _id: { month: '$period.month', year: '$period.year' },
            salesTarget: { $sum: '$salesAmount.target' },
            salesAchieved: { $sum: '$salesAmount.achieved' },
            unitsTarget: { $sum: '$unitsSold.target' },
            unitsAchieved: { $sum: '$unitsSold.achieved' },
          },
        },
        {
          $project: {
            _id: 0,
            month: '$_id.month',
            year: '$_id.year',
            salesTarget: 1,
            salesAchieved: 1,
            unitsTarget: 1,
            unitsAchieved: 1,
          },
        },
      ]);

      const salesByHistoricalMonth =
        await getSalesAchievementsByCalendarMonths(
          companyObjectId,
          [getObjectId(memberId)],
          historicalMonths,
        );

      response.graphData = historicalMonths.map((m) => {
        const monthData = historicalIndividualAggregation.find(
          (t) => t.month === m.month && t.year === m.year,
        );
        const salesM = salesByHistoricalMonth.get(
          `${m.year}-${m.month}`,
        ) ?? { salesAmount: 0, unitsSold: 0 };
        return {
          month: m.label,
          salesTarget: monthData?.salesTarget || 0,
          salesAchieved: salesM.salesAmount,
          unitsTarget: monthData?.unitsTarget || 0,
          unitsAchieved: salesM.unitsSold,
        };
      });
    }

    if (type === 'team') {
      const teamObjectId = teamId ? getObjectId(teamId) : null;
      if (!teamObjectId)
        throw new ApiError(
          defaultStatus.BAD_REQUEST,
          'Team ID is required for team target summary',
          true,
          '',
          TargetsResponseCodes.PAYMENTPLAN_ERROR,
        );

      const teamDoc = await Team.findById(teamObjectId).lean<ITeamDoc | null>();
      if (!teamDoc)
        throw new ApiError(
          defaultStatus.NOT_FOUND,
          'Team not found',
          true,
          '',
          TargetsResponseCodes.PAYMENTPLAN_NOT_FOUND,
        );

      const receiverIds = teamReceiverObjectIds(teamDoc);
      const [activityAchievement, salesAchievement] = await Promise.all([
        getActivityAchievementsForMembers(
          companyObjectId,
          receiverIds,
          month,
          year,
        ),
        getSalesAchievementsForMembers(
          companyObjectId,
          receiverIds,
          month,
          year,
        ),
      ]);

      const teamTargetMatch = {
        ...baseMatch,
        'period.month': month,
        'period.year': year,
        team: teamObjectId,
        targetType: TargetType.WholeTeam,
      };

      const teamTargetsAggregation = await Targets.aggregate([
        { $match: teamTargetMatch },
        {
          $lookup: {
            from: 'users',
            localField: 'member',
            foreignField: '_id',
            as: 'memberDetails',
          },
        },
        {
          $unwind: {
            path: '$memberDetails',
            preserveNullAndEmptyArrays: true,
          },
        },
        {
          $group: {
            _id: null,
            salesAmountTarget: { $sum: '$salesAmount.target' },
            salesAmountAchieved: { $sum: '$salesAmount.achieved' },
            unitsSoldTarget: { $sum: '$unitsSold.target' },
            unitsSoldAchieved: { $sum: '$unitsSold.achieved' },
            meetingsTarget: { $sum: '$meetings.target' },
            meetingsAchieved: { $sum: '$meetings.achieved' },
            siteVisitsTarget: { $sum: '$siteVisits.target' },
            siteVisitsAchieved: { $sum: '$siteVisits.achieved' },
            callsTarget: { $sum: '$calls.target' },
            callsAchieved: { $sum: '$calls.achieved' },
            // Grouping for team members list
            teamMembers: {
              $push: {
                memberId: { $toString: '$member' },
                name: {
                  $cond: {
                    if: {
                      $and: [
                        '$memberDetails.firstName',
                        '$memberDetails.lastName',
                      ],
                    },
                    then: {
                      $concat: [
                        '$memberDetails.firstName',
                        ' ',
                        '$memberDetails.lastName',
                      ],
                    },
                    else: 'Unknown',
                  },
                },
                salesAmount: '$salesAmount',
                unitsSold: '$unitsSold',
                meetings: '$meetings',
                siteVisits: '$siteVisits',
                calls: '$calls',
              },
            },
          },
        },
        {
          $project: {
            _id: 0,
            salesAmount: {
              target: '$salesAmountTarget',
              achieved: '$salesAmountAchieved',
            },
            unitsSold: {
              target: '$unitsSoldTarget',
              achieved: '$unitsSoldAchieved',
            },
            meetings: {
              target: '$meetingsTarget',
              achieved: '$meetingsAchieved',
            },
            siteVisits: {
              target: '$siteVisitsTarget',
              achieved: '$siteVisitsAchieved',
            },
            calls: { target: '$callsTarget', achieved: '$callsAchieved' },
            teamMembers: 1,
          },
        },
      ]);

      if (teamTargetsAggregation.length) {
        const aggregatedResult = teamTargetsAggregation[0];
        response.team = {
          salesAmount: mergeActivityIntoMetric(
            aggregatedResult.salesAmount,
            salesAchievement.totals.salesAmount,
          ),
          unitsSold: mergeActivityIntoMetric(
            aggregatedResult.unitsSold,
            salesAchievement.totals.unitsSold,
          ),
          meetings: mergeActivityIntoMetric(
            aggregatedResult.meetings,
            activityAchievement.totals.meetings,
          ),
          siteVisits: mergeActivityIntoMetric(
            aggregatedResult.siteVisits,
            activityAchievement.totals.siteVisits,
          ),
          calls: mergeActivityIntoMetric(
            aggregatedResult.calls,
            activityAchievement.totals.calls,
          ),
        };
        response.teamMembers = (
          aggregatedResult.teamMembers as TeamMember[]
        ).map((m) => {
          const mid = m.memberId ?? '';
          const a = activityAchievement.byMember.get(mid) ?? {
            calls: 0,
            meetings: 0,
            siteVisits: 0,
          };
          const s = salesAchievement.byMember.get(mid) ?? {
            unitsSold: 0,
            salesAmount: 0,
          };
          return {
            ...m,
            salesAmount: mergeActivityIntoMetric(m.salesAmount, s.salesAmount),
            unitsSold: mergeActivityIntoMetric(m.unitsSold, s.unitsSold),
            calls: mergeActivityIntoMetric(m.calls, a.calls),
            meetings: mergeActivityIntoMetric(m.meetings, a.meetings),
            siteVisits: mergeActivityIntoMetric(m.siteVisits, a.siteVisits),
          };
        });
      } else {
        const users = await User.find({ _id: { $in: receiverIds } })
          .select('firstName lastName')
          .lean();
        const userById = new Map<string, string>(
          users.map((u) => [
            u._id.toString(),
            (`${u.firstName ?? ''} ${u.lastName ?? ''}`.trim() || 'Unknown') as string,
          ]),
        );
        response.team = {
          salesAmount: mergeActivityIntoMetric(
            { target: 0, achieved: 0 },
            salesAchievement.totals.salesAmount,
          ),
          unitsSold: mergeActivityIntoMetric(
            { target: 0, achieved: 0 },
            salesAchievement.totals.unitsSold,
          ),
          meetings: mergeActivityIntoMetric(
            { target: 0, achieved: 0 },
            activityAchievement.totals.meetings,
          ),
          siteVisits: mergeActivityIntoMetric(
            { target: 0, achieved: 0 },
            activityAchievement.totals.siteVisits,
          ),
          calls: mergeActivityIntoMetric(
            { target: 0, achieved: 0 },
            activityAchievement.totals.calls,
          ),
        };
        response.teamMembers = receiverIds.map((rid) => {
          const key = rid.toString();
          const a = activityAchievement.byMember.get(key) ?? {
            calls: 0,
            meetings: 0,
            siteVisits: 0,
          };
          const s = salesAchievement.byMember.get(key) ?? {
            unitsSold: 0,
            salesAmount: 0,
          };
          return {
            memberId: key,
            name: userById.get(key) ?? 'Unknown',
            salesAmount: { target: 0, achieved: s.salesAmount },
            unitsSold: { target: 0, achieved: s.unitsSold },
            meetings: { target: 0, achieved: a.meetings },
            siteVisits: { target: 0, achieved: a.siteVisits },
            calls: { target: 0, achieved: a.calls },
          };
        });
      }

      const teamHistoricalAggregation = await Targets.aggregate([
        {
          $match: {
            companyId: companyObjectId,
            team: teamObjectId,
            targetType: TargetType.WholeTeam,
            $or: historicalMonths.map((m) => ({
              'period.month': m.month,
              'period.year': m.year,
            })),
          },
        },
        {
          $group: {
            _id: { month: '$period.month', year: '$period.year' },
            salesTarget: { $sum: '$salesAmount.target' },
            salesAchieved: { $sum: '$salesAmount.achieved' },
            unitsTarget: { $sum: '$unitsSold.target' },
            unitsAchieved: { $sum: '$unitsSold.achieved' },
          },
        },
        {
          $project: {
            _id: 0,
            month: '$_id.month',
            year: '$_id.year',
            salesTarget: 1,
            salesAchieved: 1,
            unitsTarget: 1,
            unitsAchieved: 1,
          },
        },
      ]);

      const teamSalesByHistoricalMonth =
        await getSalesAchievementsByCalendarMonths(
          companyObjectId,
          receiverIds,
          historicalMonths,
        );

      response.graphData = historicalMonths.map((m) => {
        const monthData = teamHistoricalAggregation.find(
          (t) => t.month === m.month && t.year === m.year,
        );
        const salesM = teamSalesByHistoricalMonth.get(
          `${m.year}-${m.month}`,
        ) ?? { salesAmount: 0, unitsSold: 0 };
        return {
          month: m.label,
          salesTarget: monthData?.salesTarget || 0,
          salesAchieved: salesM.salesAmount,
          unitsTarget: monthData?.unitsTarget || 0,
          unitsAchieved: salesM.unitsSold,
        };
      });
    }

    return response;
  } catch (_error) {
    throw new ApiError(
      defaultStatus.INTERNAL_SERVER_ERROR,
      'Failed to get targets',
      true,
      '',
      TargetsResponseCodes.TARGET_ERROR,
    );
  }
};
