import { Types } from 'mongoose';

import { ApiError } from '@/shared/utils/errors';
import { defaultStatus } from '@/shared/utils/responseCode/httpStatusAlias';
import { Company } from '@/modules/company/company.model';
import {
  CompanyFilter,
  ICompanyBase,
  NewCreatedCompany,
  UpdateCompanyBody,
} from '@/modules/company/company.interface';
import responseCodes from '@/shared/utils/responseCode/responseCode';
import { NewCreatedUser } from '@/modules/user/user.interfaces';
import { createUser } from '@/modules/user/user.service';
import { CommonTypes, UserType } from '@/shared/constants/enum.constant';
import { maskCompaniesData, transformCompanyForResponse } from '@/shared/utils/maskingHelper';

import {
  addOrUpdatePermission,
  getSeedPermissionsForCompanyType,
} from '@/modules/permissions/permissions.service';
import { updateRole } from '@/modules/roles/roles.service';
import User from '../user/user.model';
import { getObjectId } from '@/shared/utils/commonHelper';
import { cloneDefaultLeadStagesForCompany } from '@/modules/master/leadStage/leadStage.service';
import { createDefaultLeadScoreConfig } from '../leadScore/leadScore.service';
import { syncEmailTemplates } from '../communication/email/email.helper';
import { cloneDefaultLeadSourceForCompany } from '../master/constructionStatus/source/source.service';
import { calculatePlanExpiryDate } from '@/shared/utils/plans.utils';
import * as invoiceService from '@/modules/invoice/invoice.service';
import { IInvoiceBase } from '../invoice/invoice.interface';
import { getEntityByIdWithQueryString } from '@/shared/utils/modelPopulateFields';
import { createTeam } from '@/modules/teams/teams.service';
import { Team } from '@/modules/teams/teams.model';

const { CompanyResponseCodes } = responseCodes;

export const createCompany = async (
  data: NewCreatedCompany,
): Promise<ICompanyBase> => {
  try {
    const {
      firstName,
      lastName,
      email,
      phone,
      companyType,
      gstNumber,
      ...companyData
    } = data as NewCreatedCompany & NewCreatedUser;

    const userQuery = User.findOne({ $or: [{ email }, { phone }] }).lean();

    const companyQuery = gstNumber
      ? Company.findOne({ gstNumber }).lean()
      : Promise.resolve(null);

    const [existingUser, existingCompany] = await Promise.all([
      userQuery,
      companyQuery,
    ]);

    if (existingUser)
      throw new ApiError(
        defaultStatus.OK,
        'User with this email or phone already exists',
        true,
        '',
        CompanyResponseCodes.COMPANY_ALREADY_EXIST,
      );

    if (existingCompany)
      throw new ApiError(
        defaultStatus.OK,
        'Company with this GST number already exists',
        true,
        '',
        CompanyResponseCodes.COMPANY_ALREADY_EXIST,
      );

    const company = await Company.create({
      ...companyData,
      companyType,
      gstNumber,
      planExpiryDate: calculatePlanExpiryDate(companyData.planValidity),
    });

    await cloneDefaultLeadStagesForCompany(
      company._id.toString(),
      company.createdBy?.toString(),
    );

    await createDefaultLeadScoreConfig(
      company._id as Types.ObjectId,
      company.createdBy as Types.ObjectId,
    );

    await cloneDefaultLeadSourceForCompany(
      company._id.toString(),
      company.createdBy?.toString(),
    );

    // Create admin role and get permissions in parallel
    const [companyRole, allPermissions] = await Promise.all([
      updateRole({
        name: CommonTypes.ADMIN,
        description: `Role with all permissions for ${CommonTypes.ADMIN}`,
        company: company._id as Types.ObjectId,
      }),
      Promise.resolve(getSeedPermissionsForCompanyType(companyType)),
    ]);

    // Add permissions to role
    await addOrUpdatePermission(
      company._id as Types.ObjectId,
      companyRole._id as Types.ObjectId,
      allPermissions.map((group) => ({
        group: group.group,
        groupAccess: group.groupAccess,
        permissions: group.permissions,
        createdBy: null,
        updatedBy: null,
      })),
      true,
    );

    // Create admin user if email is provided
    if (email) {
      const userData: NewCreatedUser = {
        firstName,
        lastName,
        email,
        phone,
        userType: UserType.ADMIN,
        company: {
          id: company._id as Types.ObjectId,
          role: [companyRole._id as Types.ObjectId],
        },
        createdBy: company.createdBy as Types.ObjectId,
      };

      await syncEmailTemplates(company._id as Types.ObjectId, true);

      const createdAdmin = await createUser(userData);
      const adminId = createdAdmin._id as Types.ObjectId;

      // Create default team with admin as team lead
      await createTeam({
        name: 'default',
        description: 'Default team',
        members: [],
        lead: adminId,
        companyId: company._id as Types.ObjectId,
        createdBy: adminId,
      });

      const defaultTeam = await Team.findOne({
        name: 'default',
        companyId: company._id,
      }).lean();

      if (defaultTeam)
        await User.findByIdAndUpdate(adminId, {
          $addToSet: { team: defaultTeam._id },
        });
    }

    const todaysDate = new Date();

    const invoiceData: IInvoiceBase = {
      issuedDate: todaysDate,
      dueDate: todaysDate,
      company: getObjectId(company._id.toString()),
      planValidity: data.planValidity,
      maxUserCount: data.maxUserCount,
      price: data.price,
      tax: data.tax,
      discount: data.discount,
      finalPrice: data.finalPrice,
      status: 'paid',
    };

    await invoiceService.createInvoice(invoiceData, false);

    return transformCompanyForResponse(company);
  } catch (error) {
    console.log('🚀 ~ createCompany ~ error:', error);
    if (error instanceof ApiError) throw error;

    throw new ApiError(
      defaultStatus.OK,
      'Failed to create company',
      true,
      '',
      CompanyResponseCodes.COMPANY_ERROR,
    );
  }
};

export const getCompanyById = async (
  id: string,
  fields?: string,
  populate?: string,
) => {
  const company = await getEntityByIdWithQueryString({
    model: Company,
    entityId: id,
    fields,
    populate,
    responseCode: CompanyResponseCodes.USER_NOT_FOUND,
  });
  return transformCompanyForResponse(company);
};

export const updateCompany = async (
  id: string,
  updateData: UpdateCompanyBody,
): Promise<boolean | null> => {
  let company: boolean | null;

  try {
    company = await Company.findByIdAndUpdate(id, updateData, { new: true });
  } catch (_error) {
    throw new ApiError(
      defaultStatus.OK,
      'Failed to update company',
      true,
      '',
      CompanyResponseCodes.COMPANY_ERROR,
    );
  }

  if (!company)
    throw new ApiError(
      defaultStatus.OK,
      'Company not found',
      true,
      '',
      CompanyResponseCodes.COMPANY_NOT_FOUND,
    );

  return true;
};

export const deleteCompany = async (id: string): Promise<boolean | null> => {
  try {
    let company: boolean | null;
    company = await Company.findByIdAndDelete(id);

    if (!company)
      throw new ApiError(
        defaultStatus.OK,
        'Company not found',
        true,
        '',
        CompanyResponseCodes.COMPANY_NOT_FOUND,
      );

    await User.deleteMany({ 'company.id': id });

    return true;
  } catch (_error) {
    throw new ApiError(
      defaultStatus.OK,
      'Failed to delete company',
      true,
      '',
      CompanyResponseCodes.COMPANY_ERROR,
    );
  }
};

export const queryCompanies = async (
  filter: CompanyFilter = {},
  options = {},
) => {
  const { status, companyType, search, state, ...restFilter } = filter;

  const updatedFilter = {
    ...restFilter,
    ...(status !== 'all' && { status }),
    ...(companyType === 'all' ||
    companyType === undefined ||
    companyType === null
      ? { companyType: { $ne: 'superAdmin' } }
      : { companyType }),
    ...(search && {
      $or: [{ name: { $regex: search, $options: 'i' } }],
    }),
    ...(state !== 'all' && state && { state: getObjectId(state as string) }),
  };

  const pipeline = [
    {
      $match: updatedFilter,
    },
    {
      $lookup: {
        from: 'users',
        let: { companyId: '$_id' },
        pipeline: [
          {
            $match: {
              $expr: {
                $and: [
                  { $eq: ['$company.id', '$$companyId'] },
                  { $eq: ['$userType', 'admin'] },
                ],
              },
            },
          },
          {
            $project: {
              firstName: 1,
              lastName: 1,
              email: 1,
              isEmailVerified: 1,
              phone: 1,
              _id: 1,
            },
          },
        ],
        as: 'admin',
      },
    },
    {
      $lookup: {
        from: 'cities',
        localField: 'city',
        foreignField: '_id',
        as: 'city',
      },
    },
    {
      $unwind: {
        path: '$city',
        preserveNullAndEmptyArrays: true,
      },
    },
    {
      $lookup: {
        from: 'states',
        localField: 'state',
        foreignField: '_id',
        as: 'state',
      },
    },
    {
      $unwind: {
        path: '$state',
        preserveNullAndEmptyArrays: true,
      },
    },
    {
      $lookup: {
        from: 'users',
        let: { companyId: '$_id' },
        pipeline: [
          { $match: { $expr: { $eq: ['$company.id', '$$companyId'] } } },
          { $count: 'count' },
        ],
        as: 'userCountArr',
      },
    },
    {
      $addFields: {
        userCount: {
          $ifNull: [{ $arrayElemAt: ['$userCountArr.count', 0] }, 0],
        },
      },
    },
    {
      $project: {
        __v: 0,
        userCountArr: 0,
      },
    },
  ];

  const result = await Company.paginate(
    {},
    { ...options, aggregation: pipeline },
  );

  if (result.docs && Array.isArray(result.docs)) {
    result.docs = maskCompaniesData(result.docs);
  }

  return result;
};
