/* eslint-disable @typescript-eslint/no-explicit-any */
import { Types } from 'mongoose';
import { LeadStage } from '../master/leadStage/leadStage.model';
import * as leadService from '@/modules/lead/lead.service';
import * as contactService from '@/modules/contacts/contacts.service';
import * as activityService from '@/modules/activity/activity.service';
import * as tasksService from '@/modules/tasks/tasks.service';
import * as rulesService from '@/modules/rules/rules.service';
import { updateLeadScoreFromActivity } from '../leadScore/leadScore.service';
import { ICaptureLeadBase } from '@/modules/captureLeads/captureLead.interface';
import { TaskActivityType } from '../tasks/tasks.constant';
import { getObjectId } from '@/shared/utils/commonHelper';
import {
  LeadInterestType,
  LeadPriority,
} from '@/shared/constants/enum.constant';
import { Team } from '@/modules/teams/teams.model';
import { Lead } from '@/modules/lead/lead.model';
import { CaptureLead } from '@/modules/captureLeads/captureLeads.model';
import { Company } from '@/modules/company/company.model';
import User from '@/modules/user/user.model';
import { createNotification } from '@/modules/notification/notification.service';
import {
  NotificationStatus,
  UserType,
} from '../notification/notification.constant';
import Configuration from '@/modules/master/property/configuration/configuration.model';
import IndividualProperties from '@/modules/individualProperties/individualProperties.model';
import { WebhookRequest } from './webhook.model';
import { IWebhookRequest } from './webhook.interface';

const findPropertyBySourceAndProjectId = async (
  sourceId: Types.ObjectId,
  sourceProjectId: string,
): Promise<Types.ObjectId | null> => {
  if (!sourceProjectId || !sourceId) return null;

  const property = await IndividualProperties.findOne({
    'source.id': sourceId,
    'source.sourceProjectId': sourceProjectId,
  }).select('_id');

  return property ? (property._id as Types.ObjectId) : null;
};

interface BulkImportInput {
  companyId: string;
  captureLead: ICaptureLeadBase;
  leads: Array<{
    firstName?: string;
    lastName?: string;
    phone?: string;
    email?: string;
    message?: string;
    projectId?: string;
    projectName?: string;
    locality?: string;
    url?: string;
    notes?: string;
  }>;
  isCheckCustomFields?: boolean;
}

const normalizeAssignedTo = (assignedTo: any) => {
  if (Array.isArray(assignedTo)) {
    const first = assignedTo[0];
    return first?._id ? first._id : first;
  } else if (assignedTo?._id) {
    return assignedTo._id;
  }
  return assignedTo;
};

// Resolve assignee based on assignmentMode
const resolveAssignee = async (
  captureLead: any,
  companyId: string,
): Promise<Types.ObjectId> => {
  // Helper: return active user IDs for this company
  const getActiveCompanyUsers = async (): Promise<string[]> => {
    const users = await User.find({
      'company.id': getObjectId(companyId),
      status: 'active',
    })
      .select('_id')
      .lean();
    return users.map((u: any) => String(u._id));
  };

  const activeUserSet = new Set<string>(await getActiveCompanyUsers());

  // Helper to get candidate assignees list
  const getCandidates = async (): Promise<string[]> => {
    // Prefer explicitly selected users
    if (
      Array.isArray(captureLead.assignedTo) &&
      captureLead.assignedTo.length
    ) {
      const ids = captureLead.assignedTo.map((u: any) =>
        u && u._id ? String(u._id) : String(u),
      );
      // filter to active users in this company only
      return ids.filter((id) => activeUserSet.has(String(id)));
    }

    // Else fallback to team members+lead
    if (captureLead.team) {
      const team = await Team.findById(captureLead.team).lean();
      if (team) {
        const members = [
          ...(team.lead ? [String(team.lead)] : []),
          ...(team.members || []).map((m: any) => String(m)),
        ];
        // filter by active users in company
        return members.filter((id) => activeUserSet.has(String(id)));
      }
    }
    return [];
  };

  const mode = captureLead.assignmentMode as
    | 'equal'
    | 'roundRobin'
    | 'specificUser';

  const candidates = await getCandidates();

  // specificUser: pick first explicit user
  if (mode === 'specificUser') {
    const target = normalizeAssignedTo(captureLead.assignedTo);
    if (target && activeUserSet.has(String(target)))
      return getObjectId(target as string);
  }

  // roundRobin over candidates, persist rrIndex
  if (mode === 'roundRobin' && candidates.length) {
    try {
      if (captureLead?._id) {
        const updated = await CaptureLead.findByIdAndUpdate(
          captureLead._id,
          { $inc: { rrIndex: 1 } },
          { new: true, lean: true },
        );
        const idx = Math.max(
          0,
          ((updated?.rrIndex ?? 1) - 1) % candidates.length,
        );
        return getObjectId(candidates[idx]);
      }
    } catch (_e) {
      void 0; // noop
    }
    const fallbackIdx =
      Math.abs(Number(captureLead.rrIndex || 0)) % candidates.length;
    return getObjectId(candidates[fallbackIdx]);
  }

  // equal: choose least-loaded user (by current lead count)
  if (mode === 'equal' && candidates.length) {
    const agg = await Lead.aggregate([
      {
        $match: {
          company: getObjectId(companyId),
          assignedTo: { $in: candidates.map((id) => getObjectId(id)) },
        },
      },
      { $group: { _id: '$assignedTo', count: { $sum: 1 } } },
    ]);

    const counts = new Map<string, number>(
      agg.map((a: any) => [String(a._id), a.count]),
    );
    let best = candidates[0];
    let bestCount = counts.get(best) ?? 0;
    for (const id of candidates) {
      const c = counts.get(id) ?? 0;
      if (c < bestCount) {
        best = id;
        bestCount = c;
      }
    }
    return getObjectId(best);
  }

  // fallback: createdBy or first candidate or company owner or any active user
  if (candidates.length) return getObjectId(candidates[0]);

  // createdBy must belong to the same company
  if (captureLead.createdBy) {
    const creator = await User.findOne({
      _id: captureLead.createdBy,
      'company.id': getObjectId(companyId),
      status: 'active',
    })
      .select('_id')
      .lean();
    if (creator?._id) return getObjectId(creator._id as any);
  }

  try {
    // company owner (createdBy)
    if (captureLead.company) {
      const company = await Company.findById(captureLead.company)
        .select('createdBy')
        .lean();
      if (company?.createdBy) {
        const owner = await User.findOne({
          _id: company.createdBy,
          'company.id': getObjectId(companyId),
          status: 'active',
        })
          .select('_id')
          .lean();
        if (owner?._id) return getObjectId(owner._id as any);
      }

      // any active user in this company
      const anyUser = await User.findOne({
        'company.id': getObjectId(companyId),
        status: 'active',
      })
        .select('_id')
        .lean();
      if (anyUser?._id) return getObjectId(anyUser._id as unknown as string);
    }
  } catch (_e) {
    void 0; // noop
  }

  // If still nothing, signal a 400-style operational error
  throw new Error('No active company users available for assignment');
};

export const leadWebhookService = {
  bulkImportLeads: async ({
    companyId,
    captureLead,
    leads,
    isCheckCustomFields,
  }: BulkImportInput) => {
    const defaultStage = await LeadStage.findOne({
      company: companyId,
      position: 1,
      isActive: true,
    });

    if (!defaultStage) throw new Error('Default Lead Stage not found');

    // Pre-compute batch assignees for Equal Distribution to guarantee deterministic split
    let equalBatchAssignees: Types.ObjectId[] | null = null;
    if (captureLead.assignmentMode === 'equal') {
      // Build active user set for the company
      const activeUsers = new Set<string>(
        (
          await User.find({
            'company.id': getObjectId(companyId),
            status: 'active',
          })
            .select('_id')
            .lean()
        ).map((u: any) => String(u._id)),
      );

      // Determine candidates from assignedTo or team (filtered to active company users)
      let candidates: string[] = [];
      if (
        Array.isArray((captureLead as any).assignedTo) &&
        (captureLead as any).assignedTo.length
      ) {
        candidates = (captureLead as any).assignedTo
          .map((u: any) => (u && u._id ? String(u._id) : String(u)))
          .filter((id: string) => activeUsers.has(id));
      } else if ((captureLead as any).team) {
        const team = await Team.findById((captureLead as any).team).lean();
        if (team) {
          const list = [
            ...(team.lead ? [String(team.lead)] : []),
            ...(team.members || []).map((m: any) => String(m)),
          ];
          candidates = list.filter((id) => activeUsers.has(id));
        }
      }

      if (!candidates.length)
        throw new Error(
          'No active company users available for equal distribution',
        );

      // Deterministic cycling by index across this batch
      equalBatchAssignees = leads.map((_, i) =>
        getObjectId(candidates[i % candidates.length]),
      );
    }

    // Check if task creation is enabled for this company
    const rules = await rulesService.getRules(companyId, 'create_task_on_lead');

    await Promise.all(
      leads.map(async (row, idx) => {
        // 1️⃣ Create or find contact
        const contact = await contactService.findOrCreateByPhone({
          firstName: row.firstName,
          phone: row.phone,
          company: getObjectId(companyId),
          source: captureLead.source,
          createdBy: captureLead.company,
        });

        // Conditionally enrich the contact with fields provided via webhook
        // Only set fields if they are provided and not already present on the contact
        try {
          const updates: Record<string, unknown> = {};

          if (row.lastName && !contact.lastName)
            updates.lastName = row.lastName;

          if (
            row.email &&
            (!contact.email || String(contact.email).trim() === '')
          )
            updates.email = row.email.toLowerCase();

          if (Object.keys(updates).length > 0)
            await contactService.updateContact(
              String(contact._id),
              updates as any,
            );
        } catch (_e) {
          // swallow non-critical enrichment errors to avoid breaking lead creation
          // console.log(e);
          void 0; // noop
        }

        // 2️⃣ Resolve assignee based on assignment mode (batch-aware for equal)
        const assignee =
          captureLead.assignmentMode === 'equal' && equalBatchAssignees
            ? equalBatchAssignees[idx]
            : await resolveAssignee(captureLead, companyId);

        // 3️⃣ Find property by source and sourceProjectId
        const property = await findPropertyBySourceAndProjectId(
          getObjectId(captureLead.source) as Types.ObjectId,
          row.projectId,
        );

        // 4️⃣ Create lead
        const lead = await leadService.createLead({
          company: getObjectId(companyId),
          contact: getObjectId(contact._id as Types.ObjectId),
          interestType: LeadInterestType.BUY,
          buyingPreference: captureLead.buyingPreference,
          project: captureLead.project,
          category: captureLead.category,
          configuration: captureLead.configuration,
          preferredState: captureLead.preferredState,
          preferredCity: captureLead.preferredCity,
          preferredLocalities: captureLead.preferredLocalities,
          assignedTo: assignee,
          leadStage: getObjectId(defaultStage._id as Types.ObjectId),
          priority: LeadPriority.MEDIUM,
          source: captureLead.source,
          createdBy: captureLead.company,
          captureLead: getObjectId(
            (captureLead as any)?._id || (captureLead as any)?.id,
          ),
          sourceProjectId: row.projectId,
          sourceProjectName: row.projectName,
          sourceLocality: row.locality,
          sourceUrl: row.url,
          property: property || undefined,
        }, isCheckCustomFields);

        const contactActivityPayload = {
          title: 'Contact Created',
          comment: 'New contact created successfully.',
          type: 'notes' as const,
          contact: lead?.contact,
          company: companyId,
          status: 'completed' as const,
          assignedTo: lead?.assignedTo,
          createdBy: lead?.createdBy,
          createdAt: lead?.createdAt,
        };

        // Send Notification to the user who assigned the lead
        const user = await User.findById(lead.assignedTo).lean();

        const configDetails = await Configuration.findById(
          captureLead.configuration,
        );

        let description = `${row.firstName} added to your leads.`;

        if (configDetails)
          description = `${row.firstName} added to your leads. Requirement: ${configDetails.name}.`;

        const appRedirect = {
          screenType: 'Lead_Details',
          id: lead._id as Types.ObjectId,
        };

        if (user)
          await createNotification(
            {
              title: `New Lead from ${captureLead.platform}`,
              description: description,
              userType: UserType.SELF,
              user: getObjectId(user._id as Types.ObjectId),
              status: NotificationStatus.SEND,
            },
            appRedirect,
          );

        // 4️⃣ Create activity (include submitted message if present)
        await activityService.createActivity({
          title: 'Lead Created from Webhook',
          comment: `New lead captured from ${captureLead.platform}${row.message ? `\n\nMessage: ${row.message}` : ''
            }${row.notes ? `\n\n${row.notes}` : ''
            }`,
          type: 'notes' as const,
          lead: lead._id as Types.ObjectId,
          contact: getObjectId(contact._id as Types.ObjectId),
          company: companyId,
          status: 'completed',
          assignedTo: lead.assignedTo,
          createdBy: lead.createdBy,
          createdAt: new Date(),
        });

        // 5️⃣ Create follow-up task (only if rule is enabled)
        if ((rules as any).create_task_on_lead) {
          const task = await tasksService.createTasks({
            assignedTo: lead.assignedTo[0],
            notes: 'Follow up on new lead',
            activityType: TaskActivityType.CALL,
            activityDate: new Date(),
            callStatus: 'scheduled' as const,
            companyId: getObjectId(companyId),
            leadId: getObjectId(lead._id as string),
            createdBy: lead.createdBy,
          });

          await activityService.createActivity({
            title: 'Schedule - call',
            type: 'notes' as const,
            task: task?.id as Types.ObjectId,
            lead: lead?.id as Types.ObjectId,
            company: companyId,
            status: 'pending',
            assignedTo: lead?.assignedTo,
            createdBy: lead?.createdBy,
            createdAt: task?.createdAt,
            scheduleDateTime: lead?.createdAt,
          });
        }


        // 6️⃣ Update Lead Score
        await updateLeadScoreFromActivity(
          'contact_added',
          getObjectId(lead._id as string),
        );
      }),
    );
  },

  async createWebhookRequest(
    data: Partial<IWebhookRequest>,
  ): Promise<IWebhookRequest> {
    return await WebhookRequest.create(data);
  },

  async updateWebhookRequest(
    id: string,
    updates: Partial<IWebhookRequest>,
  ): Promise<IWebhookRequest | null> {
    return await WebhookRequest.findByIdAndUpdate(id, updates, { new: true });
  },
};
