import { Request, Response } from 'express';
import { Types } from 'mongoose';

import * as customerService from '@/modules/customer/customer.service';
import * as rulesService from '@/modules/rules/rules.service';
import { getVisibleAssigneeIdsForUser } from '@/modules/teams/teams.service';
import catchAsync from '@/shared/utils/catchAsync';
import responseCodes from '@/shared/utils/responseCode/responseCode';
import { pick } from '@/shared/utils';
import { UserType } from '@/shared/constants/enum.constant';
import { getObjectId } from '@/shared/utils/commonHelper';
import { ApiError } from '@/shared/utils/errors';
import { defaultStatus } from '@/shared/utils/responseCode/httpStatusAlias';

const { CustomerResponseCodes } = responseCodes;

const CUSTOMER_FINANCIAL_BODY_FIELDS = [
  'totalAmount',
  'bookingAmount',
  'rentAmount',
  'rentAmountPaid',
  'depositAmount',
  'depositAmountPaid',
  'bankAccountId',
] as const;

/**
 * When PATCH /customers/:id includes financial fields, require company rule
 * `edit_customer_details` (same as payment timeline endpoints), except for admin/superadmin.
 */
const enforceFinancialFieldsRule = async (req: Request): Promise<void> => {
  const body = req.body as Record<string, unknown>;
  const hasFinancialUpdate = CUSTOMER_FINANCIAL_BODY_FIELDS.some((field) =>
    Object.prototype.hasOwnProperty.call(body, field),
  );

  if (!hasFinancialUpdate) return;

  const { userType, company } = req.user;
  if (userType === UserType.ADMIN || userType === UserType.SUPERADMIN) return;

  const companyId = company?.id ? company.id.toString() : '';
  if (!companyId) return;

  const rules = (await rulesService.getRules(
    companyId,
    'edit_customer_details',
  )) as Record<string, boolean>;

  if (!rules.edit_customer_details)
    throw new ApiError(
      defaultStatus.FORBIDDEN,
      'Editing customer financial details is disabled by company rules',
      true,
      '',
      CustomerResponseCodes.COMPANY_ERROR,
    );
};

const checkCompanyRuleForCrmUser = async (
  req: Request,
  ruleKey: string,
): Promise<boolean> => {
  const { userType, company } = req.user;
  const companyId = company?.id;

  if (
    userType === UserType.ADMIN ||
    userType === UserType.SUPERADMIN ||
    !companyId
  )
    return true;

  const rules = (await rulesService.getRules(
    companyId,
    ruleKey,
  )) as Record<string, boolean>;

  return rules?.[ruleKey] === true;
};

export const createCustomer = catchAsync(
  async (req: Request, res: Response) => {
    const customer = await customerService.createCustomer(
      req.body,
      req.user.id,
      req.user.company?.id,
    );
    res.success(
      customer,
      CustomerResponseCodes.SUCCESS,
      'Customer Created Successfully',
    );
  },
);

export const updateCustomer = catchAsync(
  async (req: Request, res: Response) => {
    await enforceFinancialFieldsRule(req);
    const { id } = pick(req.params, ['id']);

    const updatedCustomer = await customerService.updateCustomer(id, req.body);
    res.success(
      updatedCustomer,
      CustomerResponseCodes.SUCCESS,
      'Customer Updated Successfully',
    );
  },
);

export const deleteCustomerById = catchAsync(
  async (req: Request, res: Response) => {
    const { id } = pick(req.params, ['id']);

    const customer = await customerService.deleteCustomer(id);
    res.success(
      customer,
      CustomerResponseCodes.SUCCESS,
      'Customer Deleted Successfully',
    );
  },
);

export const getCustomerById = catchAsync(
  async (req: Request, res: Response) => {
    const { id } = pick(req.params, ['id']);

    const { populate, fields } = pick(req.query, ['populate', 'fields']);

    const customer = await customerService.getCustomerById(
      id,
      fields,
      populate,
    );

    const canAccessCustomerDocs = await checkCompanyRuleForCrmUser(
      req,
      'access_customer_docs',
    );

    if (!canAccessCustomerDocs) {
      const sanitized = customer as any;
      if (sanitized?.documents) sanitized.documents = [];
      if (sanitized?.unitBookingOrHold?.documents)
        sanitized.unitBookingOrHold.documents = [];
      if (sanitized?.unitBookingOrHold?.content) sanitized.unitBookingOrHold.content = null;
    }

    res.success(
      customer,
      CustomerResponseCodes.SUCCESS,
      'Customer Fetched Successfully',
    );
  },
);

export const getCustomers = catchAsync(async (req: Request, res: Response) => {
  const filter = pick(req.query, [
    'status',
    'customerType',
    'search',
    'state',
    'company',
    'createdBy',
    'leadId',
    'project',
    'salesBy',
    'salesDate',
  ]);
  const options = pick(req.query, ['sortBy', 'limit', 'page', 'populate']);

  const { userType } = req.user;

  if (userType !== UserType.SUPERADMIN && req.user.company?.id) {
    filter.company = req.user.company.id;
  }

  // Apply see_all_customers rule: org-wide visibility or restrict to team/self based on salesBy
  if (userType !== UserType.SUPERADMIN) {
    const companyId = req.user.company?.id;
    let applySalesByScope = false;
    if (userType !== UserType.ADMIN) {
      if (!companyId) {
        // No company on user: scope to self / team-visible assignees (legacy behavior; avoid getRules(undefined))
        applySalesByScope = true;
      } else {
        const rules = (await rulesService.getRules(
          companyId,
          'see_all_customers',
        )) as { see_all_customers?: boolean };
        applySalesByScope = !rules?.see_all_customers;
      }
    }
    if (applySalesByScope) {
      const teamIds = !companyId
        ? []
        : (req.user.team || []).map((t: Types.ObjectId | string) =>
            getObjectId(t instanceof Types.ObjectId ? t : String(t)),
          );
      const assigneeIds = await getVisibleAssigneeIdsForUser(
        getObjectId(req.user.id),
        teamIds,
      );
      
      if (filter.salesBy) {
        if (
          typeof filter.salesBy === 'string' &&
          filter.salesBy.includes(',')
        ) {
          const requestedIds : string[] = filter.salesBy
            .split(',')
            .map((id: string) => id.trim())
            .filter((id: string) => Types.ObjectId.isValid(id))
          
          const allowedSet = new Set(assigneeIds.map(id => id.toString()));
          const intersected = requestedIds.filter(id => allowedSet.has(id));
          
          if (intersected.length === 0) {
            filter.salesBy = '000000000000000000000000';
          } else if (intersected.length === 1) {
            filter.salesBy = intersected[0];
          } else {
            filter.salesBy = intersected.join(',') as string;
          }
        } else if (Types.ObjectId.isValid(filter.salesBy as string)) {
          const allowedSet = new Set(assigneeIds.map(id => id.toString()));
          if (!allowedSet.has(filter.salesBy)) {
            filter.salesBy = '000000000000000000000000';
          }
        }
      } else {
        filter.salesBy = assigneeIds.join(',') as string;
      }
    }
  }

  const companies = await customerService.queryCustomers(filter, options);

  res.success(
    companies,
    CustomerResponseCodes.SUCCESS,
    'Customer Fetched Successfully',
  );
});

export const updateCustomerDocumentStatus = catchAsync(
  async (req: Request, res: Response) => {
    const canAccessCustomerDocs = await checkCompanyRuleForCrmUser(
      req,
      'access_customer_docs',
    );

    if (!canAccessCustomerDocs)
      throw new ApiError(
        defaultStatus.FORBIDDEN,
        'Access to customer documents is disabled by company rules',
        true,
        '',
        CustomerResponseCodes.COMPANY_ERROR,
      );

    const { id } = pick(req.params, ['id']);
    const { status, documentId , url} = pick(req.body, ['status', 'documentId', 'url']);
    const updatedCustomer = await customerService.updateCustomerDocumentStatus({
      customerId: id,
      documentId,
      status,
      userId: req.user.id,
      url,
    });
    res.success(
      updatedCustomer,
      CustomerResponseCodes.SUCCESS,
      'Customer Updated Successfully',
    );
  },
);

export const cancelCustomerBooking = catchAsync(
  async (req: Request, res: Response) => {
    const { id } = pick(req.params, ['id']);
    const companyId = req.user.company?.id;
    const userId = req.user.id;

    if (!companyId)
      throw new ApiError(
        defaultStatus.BAD_REQUEST,
        'Company not found',
        true,
        '',
        CustomerResponseCodes.COMPANY_ERROR,
      );

    const result = await customerService.cancelCustomerBooking(id, companyId, userId);
    res.success(
      result,
      CustomerResponseCodes.SUCCESS,
      result.message,
    );
  },
);
