const Password = require("node-php-password");
const sequelize = require("../config/dbconnection");
const { transporter } = require("../constants/common");
const { webUrl, SEND_EMAIL } = require("../constants/constant");
const { inviteLinkTemplate } = require("../libraries/emailTemplates");
const { MAIL_SENDER } = require("../constants/constant");
const crypto = require("crypto");
const { baseUrl } = require("../constants/constant");
const Client = require('../models/client');
const { empty } = require('../Helpers/Common');
const { log } = require("console");
const moment = require("moment")
const Admin = require("../models/admin")
const ClientChat = require('../models/clientChat')
const Team = require('../models/teams')
const Request = require('../models/request')
const OnboardLink = require('../models/onboardLink')
const sgMail = require('@sendgrid/mail')
const { SENDGRID_API_KEY, BASE_URL, portalName } = require("../constants/constant")
const { sendPartnerMail, activateProfile, activateProfileAccountToggle } = require("../constants/emailTemplates");
const Project= require("../models/Projects");
const OnboardWorkFlowStages=require("../models/onboardWorkFlowStages")
const OnboardWorkFlowDocuments=require("../models/onboardWorkflowDocuments")
const OnboardWorkFlowFields=require("../models/onboardWorkflowFields")
const OnboardWorkFlowQuestionnaires=require("../models/onboardWorkflowQuestionnaires")
const ContactTags = require('../models/contactTag')
const Tickets = require('../models/tickets')
const Onboard=require("../models/onboard")
const axios = require('axios')
const nodemailer = require('nodemailer');
const customerType = require('../models/customerType.js')

const transporterNodeMailer = nodemailer.createTransport({
  host: 'mail.signwisesolutions.com', // Replace with your domain's mail server (WHM SMTP host)
  port: 465, // 465 for secure SMTP (SSL), or 587 for TLS
  secure: true, // Use true for 465, false for other ports
  auth: {
    user: 'noreply@signwisesolutions.com', 
    pass: 'signwise@2024', 
  },
});

const qs = require('qs');
const {
  clientId,
  clientSecret,
  redirectUri,
  authCode,
  companyId,
  accessToken,
  refreshToken,
  getNewAccessToken,
  PRODUCTION,
  AVALARA_ACCOUNT_ID,
  AVALARA_LICENSE_KEY,
  AVALARA_BASE_URL,
  AVALARA_COMPANY_ID,
  locationInventoryCode,
  realmId
} = require('../quickbook/quickbook.js');
function generateToken() {
  return crypto.randomBytes(20).toString("hex");
}










// send invite link to the client email

const clientInvite = async (request, reply) => {
  try {
    const { email } = request.body;
    const [results, metadata] = await sequelize.query(
      `
            SELECT id, email,first_name
            FROM users
            WHERE email = :email AND status = 1
            `,
      {
        replacements: { email },
        type: sequelize.QueryTypes.SELECT,
      }
    );

    if (results) {
      reply.send({ message: "Client already exist" });
    } else {
      const inviteToken = generateToken();

      let inviteLink = webUrl + `/user/signup?token=${inviteToken}`;
      let MsgBody = inviteLinkTemplate();

      MsgBody = MsgBody.replace(/\[SUBJECT\]/g, "Invite Link to Signwise");
      MsgBody = MsgBody.replace(/\[NAME\]/g, `Hi  There ! `);
      MsgBody = MsgBody.replace(
        /\[CONTENT\]/g,
        `Please use the link below to Signup to Signwise: <br/><br/><a href="${inviteLink}">${inviteLink}</a><br/><br/> `
      );
      MsgBody = MsgBody.replace(/\[BASE_URL\]/g, baseUrl);

      // Send reset email
      const mailOptions = {
        from: MAIL_SENDER,
        to: email,
        subject: "Invitation Link",
        html: MsgBody,
      };

      // if(SEND_EMAIL){
        if(true){
      transporterNodeMailer.sendMail(mailOptions, (error, info) => {
        if (error) {
          console.error(error);
          reply.status(500).send({ message: "Error sending email" });
        } else {
          console.log("Email sent1: " + info.response);
          reply.send({ status: 1, message: "Email sent successfully" });
        }
      });
      reply.send({ status: 1, message: "Email sent successfully" });
    }else{
      reply.send({ status: 1, message: "Email not sended" });
    }
    }
  } catch (error) {
    console.log(error);
    reply.status(500).send({ message: "Server error" });
  }
};

// Redirect the invite link to another page
const clientLinkRedirect = (request, reply) => {
  try {
    const { token } = request.query;
    reply.redirect(webUrl + `/auth/register?token=${token}`);
    reply.send({ status: 1, message: "Redirecting to signup page" });
  } catch (error) {
    console.log(error);
    reply.status(500).send({ message: "Server error" });
  }
};

// Add a client into database -- client signup
const clientSignup = async (request, reply) => {
  try {
    const { token } = request.query;
    const { email, password, company_name, first_name, last_name } = request.body;
    const username = email;
    const hashedPassword = Password.hash(password);
    const [results, metadata] = await sequelize.query(
      `
        INSERT INTO users (username, email, company_name ,first_name, last_name, password,user_type)
        VALUES (:username, :email, :company_name ,:first_name, :last_name,  :hashedPassword, 17)
    `,
      {
        replacements: {
          username,
          email,
          company_name,
          first_name,
          last_name,
          hashedPassword,
        },
        type: sequelize.QueryTypes.INSERT,
      }
    );

    if (results) {
      reply.send({ status: 1, message: "Client successfully added" });
    } else {
      reply.send({ status: 0, message: "Failed to add the client" });
    }
  } catch (error) {
    console.log(error);
    reply.status(500).send({ message: "Server error" });
  }
};


// Edit the client information
const clientEdit = async (request, reply) => {
  try {
    const { id } = request.params;
    const { email, phone, password, company_name } = request.body;

    const [results, metadata] = await sequelize.query(
      `
          UPDATE users
          SET
            email = :email,
            phone = :phone,
            company_name = :company_name
          WHERE
            id = :id
        `,
      {
        replacements: {
          id,
          email,
          phone,
          company_name,
        },
        type: sequelize.QueryTypes.UPDATE,
      }
    );

    if (results && metadata.affectedRows > 0) {
      reply.send({ status: 1, message: "Client successfully updated" });
    } else {
      reply.send({ status: 0, message: "Failed to update the client" });
    }
  } catch (error) {
    console.log(error);
    reply.status(500).send({ message: "Server error" });
  }
};

// Delete the client 
const clientDelete = async (request, reply) => {
  try {
    const { id } = request.params;

    const [results, metadata] = await sequelize.query(
      `
          UPDATE users
          SET
            status = 0
          WHERE
            id = :id
        `,
      {
        replacements: {
          id,
        },
        type: sequelize.QueryTypes.UPDATE,
      }
    );

    if (results && metadata.affectedRows > 0) {
      reply.send({ status: 1, message: "Client successfully deleted" });
    } else {
      reply.send({ status: 0, message: "Failed to delete the client" });
    }
  } catch (error) {
    console.log(error);
    reply.status(500).send({ message: "Server error" });
  }
};
const getAllClients = async (req, reply) => {

  let userType = req.body.userType
  let userId = req.body.userId
  let page = req.body?.page
  let search = req.body?.search
  let rowPerpage = req.body?.rowsPerPage
  let isVendor = req.body?.isVendor
  let isOrder = req.body?.order || false
  let listView = req.body?.listView || false
  let orderBy = req.body?.orderBy || 'name'
  let From = req.body?.From || null
  let teamId = req.body?.teamId || null
  let requestClientId = req.body?.requestClientId || null
  let order = (req.body?.order && typeof req.body.order === 'string') 
    ? req.body.order.toLowerCase() 
    : 'asc'
  let FromDesignOrder = req.body?.FromDesignOrder || false
  let fromClient = req.body?.fromClient || false
  // Ensure order is either 'asc' or 'desc'
  if (order !== 'asc' && order !== 'desc') {
    order = 'asc'
  }
  console.log({page,rowPerpage,search})
  let data =[]
  // if(isOrder){
  //   data = await Client.getAllClientsForOrder(true, userType, userId,page,rowPerpage,search,isVendor);
  // }else{
    data = await Client.getAllClients(true, userType, userId,page,rowPerpage,search,isVendor,listView,orderBy,order,From,teamId,requestClientId);
  // }
  console.log('requestClientId++++++++++', requestClientId)
  
  let services = []
  let ranks = []
  let states = []
  let availablities = []

  if (fromClient) {
    services = await Client.getAllServices()
    ranks = await Client.getAllRanks()
    states = await Client.getAllStates()
    availablities = await Client.getAllAvailabilities()
  }
  if (!empty(data)) {
 
    reply.send({
      'status': 1,
      'msg': 'Records found',
      'customer': data.data,
      totalCount:data.totalCount,
      services,
      ranks, states,
      availablities
    })
  }
  else {
    reply.send({
      'status': 0,
      'customer':[],
      'msg': 'No Records found'
    })
  }

}
const getDataForAddClient = async (req, reply) => {
  let userType = req.body.userType
  let userId = req.body.userId
  // let clients = await Client.getAllClients(true, userType, userId);
  // let workflows = await Client.getAllWorkflows();
  let workflows=await Onboard.getAllClientFacingWorkflow()
  let partnerTypes = await Client.getAllPartnerTypes();
  let tags = await ContactTags.getAllContactTags()
  let customerTypes = await customerType.findAllCustomerType()
  reply.send({
    'status': 1,
    'msg': 'Records found',
    'workflows': workflows,
    'partnerTypes': partnerTypes,
    "tags":tags,
    "customerTypes":customerTypes
  })
}


////////////////////////////////quickbook

const findCustomerIdByName = async (customerName) => {
  let name =customerName
  try {
    // Ensure valid access token
    let token = await getNewAccessToken();

    const apiUrl = PRODUCTION
    ? `https://quickbooks.api.intuit.com/v3/company/${realmId}/query`
    : `https://sandbox-quickbooks.api.intuit.com/v3/company/${realmId}/query`;


    const response = await axios.get(
      apiUrl,
      {
        headers: {
          'Authorization': `Bearer ${token}`,
          'Content-Type': 'application/text'
        },
        params: {
          query: `SELECT * FROM Customer WHERE DisplayName = '${name.trim()}'`
        }
      }
    );

    const customers = response?.data?.QueryResponse?.Customer;

    if (customers && customers.length > 0) {
      return customers[0].Id;
    } 
  } catch (err) {
    console.error('Error finding customer by name:', err.response ? err.response.data : err.message);
    
  }
};

const findVendorIdByName = async (name) => {
  try {
    // Ensure valid access token
    let token = await getNewAccessToken();

    const apiUrl = PRODUCTION
      ? `https://quickbooks.api.intuit.com/v3/company/${realmId}/query`
      : `https://sandbox-quickbooks.api.intuit.com/v3/company/${realmId}/query`;

    const response = await axios.get(
      apiUrl,
      {
        headers: {
          'Authorization': `Bearer ${token}`,
          'Content-Type': 'application/text'
        },
        params: {
          query: `SELECT * FROM Vendor WHERE DisplayName = '${name}'`
        }
      }
    );

    const vendors = response.data.QueryResponse.Vendor;
    console.log(vendors, "Vendors retrieved");

    if (vendors && vendors?.length > 0) {
      return vendors[0].Id;
    } else {
      // throw new Error(`Vendor with name ${name} not found`);
      return null;
    }
  } catch (err) {
    console.error('Error finding vendor by name:', err.response ? err.response.data : err.message);
    // throw err;
    return null;
  }
};

const addClientToQuickbook = async (type, apiUrl, customerPayload, vendorPayload) => {
  let token = await getNewAccessToken();
  let response;
  let customerId = null;

  try {
    if (type === 'client') {
      customerId = await findCustomerIdByName(customerPayload?.DisplayName);
      if(customerId){
        return customerId;
      }
      response = await axios.post(`${apiUrl}/customer`, customerPayload, {
        headers: {
          Authorization: `Bearer ${token}`,
          'Accept': 'application/json',
          'Content-Type': 'application/json',
        },
      });
    } else if (type === 'vendor') {
      customerId = await findVendorIdByName(vendorPayload?.DisplayName);
      if(customerId){
        return customerId;
      }
      response = await axios.post(`${apiUrl}/vendor`, vendorPayload, {
        headers: {
          Authorization: `Bearer ${token}`,
          'Accept': 'application/json',
          'Content-Type': 'application/json',
        },
      });
    }
    console.log(`${type.charAt(0).toUpperCase() + type.slice(1)} added:`, response.data);
    const partnerQbId =type === 'client' ? response.data.Customer?.Id : response.data.Vendor?.Id;
    return partnerQbId || null;
  } catch (error) {
    if (error.response && error.response.status === 401) {
      console.log('Access token expired. Refreshing...');
      token = await getNewAccessToken();
      return syncWithQuickBooks(type, apiUrl, customerPayload, vendorPayload); // Retry with new token
    } else if (error.response && error.response.status === 403) {
      console.error('Permission denied:', error.response.data);
    } else if (error.code === 'ENOTFOUND') {
      console.error('DNS lookup failed. Check your internet connection.');
    } else {
      return null
      throw error; // Rethrow for any other errors
    }
  }
};
///////////////////////////////
////////////////////////////////Avalara
async function createAvalaraCustomer(customer) {

  const COMPANY_CODE = AVALARA_COMPANY_ID.toString(); // Use the company ID from config

  // 🔐 Auth header
  const authHeader =
    "Basic " +
    Buffer.from(`${AVALARA_ACCOUNT_ID}:${AVALARA_LICENSE_KEY}`).toString("base64");

  // 🧱 Determine if customer is tax-exempt
  const isTaxExempt = customer.taxExemptionReasonId && customer.entityUseCode;
  
  // 🧱 Build Avalara customer payload
  const payload = {
    customerCode: String(customer.customerCode), // unique ID per company
    name: customer.name || "Unnamed Customer",
    companyCode: COMPANY_CODE,
    email: customer.email || "",
    phoneNumber: customer.phone || "",
    line1: customer.address || "",
    city: customer.city || "",
    region: customer.state || "",
    postalCode: customer.zip || "",
    country: customer.country || "US",
    taxExemptionReasonId: customer.taxExemptionReasonId || null, // optional numeric reason
    entityUseCode: customer.entityUseCode || null, // optional like "N"
  };

  // Remove null values for tax exemption to ensure proper handling
  if (!isTaxExempt) {
    delete payload.taxExemptionReasonId;
    delete payload.entityUseCode;
  }

  console.log("📤 Sending payload to Avalara:", JSON.stringify(payload, null, 2));

  try {
    // 📤 Create or update customer in Avalara
    const { data } = await axios.post(
      `${AVALARA_BASE_URL}/api/v2/companies/${COMPANY_CODE}/customers`,
      payload,
      {
        headers: {
          Authorization: authHeader,
          "Content-Type": "application/json",
        },
      }
    );

    console.log("✅ Avalara customer created:", data);
    // console.log(`📋 Tax exemption status: ${isTaxExempt ? 'EXEMPT' : 'TAXABLE'}`);
    // if (isTaxExempt) {
    //   console.log(`   - Reason ID: ${customer.taxExemptionReasonId}`);
    //   console.log(`   - Entity Code: ${customer.entityUseCode}`);
    // }
    return data[0];
  } catch (err) {
    if (err.response) {
      console.log(
        "❌ Avalara error:",
        err.response.status,
        err.response.data
      );
      
      // Handle specific error cases
      if (err.response.status === 404) {
        console.log("❌ Company not found. Please verify the COMPANY_CODE matches your Avalara company ID.");
      } else if (err.response.status === 401) {
        console.log("❌ Authentication failed. Please verify AVALARA_ACCOUNT_ID and AVALARA_LICENSE_KEY.");
      } else if (err.response.status === 400) {
        if (err.response.data?.error?.code === 'DuplicateEntry') {
          console.log("ℹ️ Customer already exists, attempting to update...");
          // Try to update the existing customer
          return await updateAvalaraCustomer(customer, COMPANY_CODE, authHeader);
        } else {
          console.error("❌ Bad request. Please check the customer data payload:", payload);
        }
      }
    } else {
      console.log("❌ Network or config error:", err.message);
    }
    return null
  }
}

// Update existing Avalara customer
async function updateAvalaraCustomer(customer, companyCode, authHeader) {
  // ✅ Force all customers to TAXABLE in update to avoid certificate validation errors
  const payload = {
    customerCode: String(customer.customerCode || "UNKNOWN"),
    name: customer.name || "Unnamed Customer",
    companyCode: companyCode,
    email: customer.email || "",
    phoneNumber: customer.phone || "",
    line1: customer.address || "",
    city: customer.city || "",
    region: customer.state || "",
    postalCode: customer.zip || "",
    country: customer.country || "US",
    taxExemptionReasonId: customer.taxExemptionReasonId || null, // optional numeric reason
    entityUseCode: customer.entityUseCode || null, // optional like "N"
  };

  // 🚫 Never send tax exemption fields unless verified certificate exists
  // (Remove or skip taxExemptionReasonId / entityUseCode entirely)
  // Avalara will treat them as taxable customers by default

  try {
    const { data } = await axios.put(
      `${AVALARA_BASE_URL}/api/v2/companies/${companyCode}/customers/${payload.customerCode}`,
      payload,
      {
        headers: {
          Authorization: authHeader,
          "Content-Type": "application/json",
        },
      }
    );

    console.log("✅ Avalara customer updated:", data);
    return data;
  } catch (err) {
    if (err.response) {
      console.log(
        "❌ Avalara update error:",
        err.response.status,
        JSON.stringify(err.response.data, null, 2)
      );
    } else {
      console.log("❌ Network or config error during update:", err.message);
    }
    return null;
  }
}


// Upsert Avalara customer (create or update)
async function upsertAvalaraCustomer(customer) {
  const COMPANY_CODE = AVALARA_COMPANY_ID.toString();
  const authHeader = "Basic " + Buffer.from(`${AVALARA_ACCOUNT_ID}:${AVALARA_LICENSE_KEY}`).toString("base64");
  
  const customerCode = String(customer.code || customer.id || "UNKNOWN");
  
  try {
    // First try to get the existing customer
    const { data: existingCustomer } = await axios.get(
      `${AVALARA_BASE_URL}/api/v2/companies/${COMPANY_CODE}/customers/${customerCode}`,
      {
        headers: {
          Authorization: authHeader,
          "Content-Type": "application/json",
        },
      }
    );
    
    // Customer exists, update it
    console.log("ℹ️ Customer exists, updating...");
    return await updateAvalaraCustomer(customer, COMPANY_CODE, authHeader);
    
  } catch (err) {
    if (err.response?.status === 404) {
      // Customer doesn't exist, create it
      console.log("ℹ️ Customer doesn't exist, creating...");
      return await createAvalaraCustomer(customer);
    } else {
      // Some other error occurred
     return null
    }
  }
}


///////////////////////////////

const addClient = async (req, reply) => {
  try {
    let userId = req.body.userId
    let type = req.body.type
    let name = req.body.values.name
    let contact_name = req.body.values.contact_name || null
    let email = req.body.values.email
    let address = req.body.values.address || '';
    let zip_code = req.body.values.zip_code || '';
    let city = req.body.values.city || '';
    let state = req.body.values.state?.id || null;
    let remitToAddress = req.body.values.remitToAddress || '';
    let remitToCity = req.body.values.remitToCity || '';
    let remitToZipCode = req.body.values.remitToZipCode || '';
    let remitToState = req.body.values.remitToState?.id || null;
    let remitToCountry = req.body.values.remitToCountry?.code || null;
    let isSyncWithQuickbooks=req.body.values.isSyncWithQuickbooks
    let quickbookCustomerName =req.body.values.quickbookCustomerName 
    let phone_number=req.body.values.phoneNumber
    let phone_country_code = req.body.values.phone_country_code
    let userRoleId = req?.body?.values?.role?.id || null
    let avalaraTaxType = req.body.values.avalaraTaxType || 1
    const salesRepNames = req.body.values.salesRepNames?.map(rep => rep.id)?.join(',') || null;
    const uploadFiles = req.body.values.uploadFiles || [];
    const additionalEmails = req.body.values.additionalEmails || [];
    let syncWithQuickbooks = 0;
    let partnerQbId = req?.body?.partnerQbId || null;
    let avatarUrl = req.body.values.avatarUrl || null;
    let profile_pic = req.body.values.profile_pic || null;
    
    const customerPayload = {
      // "DisplayName": quickbookCustomerName + locationInventoryCode || '',
      "DisplayName": quickbookCustomerName  || '',
      "CompanyName": name,
      "PrimaryEmailAddr": {
        "Address": email
      },
      BillAddr: {
        Line1: address,
        City: city,
        CountrySubDivisionCode: req.body?.values?.state?.name || '',
        PostalCode: zip_code,
      },
      ShipAddr: {},
    "PrimaryPhone": {
        "FreeFormNumber": phone_number?.toString()
      }
    };

    const vendorPayload = {
      // "DisplayName": quickbookCustomerName + locationInventoryCode || '',
      "DisplayName": quickbookCustomerName  || '',
      "CompanyName": name,
      "PrimaryEmailAddr": {
        "Address": email
      },
      BillAddr: {
        Line1: address,
        City: city,
        CountrySubDivisionCode: req.body?.values?.state?.name || '',
        PostalCode: zip_code,
      },
      ShipAddr: {},
      "PrimaryPhone": {
        "FreeFormNumber": phone_number.toString()
      }
    };

   
    const apiUrl = PRODUCTION
    ? `https://quickbooks.api.intuit.com/v3/company/${companyId}`
    : `https://sandbox-quickbooks.api.intuit.com/v3/company/${companyId}`;


   
      if (isSyncWithQuickbooks && !partnerQbId) {
        try {
        partnerQbId = await addClientToQuickbook(type, apiUrl, customerPayload, vendorPayload) || null;
          syncWithQuickbooks =partnerQbId ? 1 : 0; // Set to 1 if sync is successful
        } catch (error) {
          console.error("QuickBooks sync failed:", error.message);
          
        }
      }else if (partnerQbId) {
        try {
          const token = await getNewAccessToken();
          if (token) {
            const headers = {
              'Authorization': `Bearer ${token}`,
              'Accept': 'application/json',
              'Content-Type': 'application/json',
            };
            if (type === 'client') {
              const fetchUrl = `${apiUrl}/customer/${partnerQbId}`;
              let response = await axios.get(fetchUrl, { headers });
              const updatedCustomerPayload = {
                ...response.data.Customer,
                DisplayName: quickbookCustomerName || '',
                CompanyName: name,
                PrimaryEmailAddr: { Address: email },
                PrimaryPhone: { FreeFormNumber: phone_number?.toString() },
                BillAddr: {
                  Line1: address,
                  City: city,
                  CountrySubDivisionCode: req.body?.values?.state?.name || '',
                  PostalCode: zip_code,
                },
                ShipAddr: null,
                SyncToken: response.data.Customer.SyncToken,
                sparse: false,
                Id: partnerQbId
              };
              const updateUrl = `${apiUrl}/customer?minorversion=73`;
              await axios.post(updateUrl, updatedCustomerPayload, { headers });
            } else if (type === 'vendor') {
              const fetchUrl = `${apiUrl}/vendor/${partnerQbId}`;
              let response = await axios.get(fetchUrl, { headers });
              const updatedVendorPayload = {
                ...response.data.Vendor,
                DisplayName: quickbookCustomerName || '',
                CompanyName: name,
                PrimaryEmailAddr: { Address: email },
                PrimaryPhone: { FreeFormNumber: phone_number?.toString() },
                BillAddr: {
                  Line1: address,
                  City: city,
                  CountrySubDivisionCode: req.body?.values?.state?.name || '',
                  PostalCode: zip_code,
                },
                ShipAddr: null,
                SyncToken: response.data.Vendor.SyncToken,
                sparse: false,
                Id: partnerQbId
              };
              const updateUrl = `${apiUrl}/vendor?minorversion=73`;
              await axios.post(updateUrl, updatedVendorPayload, { headers });
            }
            syncWithQuickbooks = 1;
            console.log('QuickBooks addClient: updated existing record with Id', partnerQbId);
          } else {
            console.warn('⚠️ QuickBooks token refresh failed — skipping QuickBooks update on addClient.');
          }
        } catch (error) {
          console.error('Error updating existing QuickBooks record on addClient:', error.response ? error.response.data : error.message);
        }
      }else if (partnerQbId) {
        syncWithQuickbooks = 1;
      }




    let partnerTypes = req.body.values.partnerType
    const workflowIds = req.body.values.Workflow; // Assuming workflow_ids is part of the request body
    const tagIds = req.body.values.tags; // Assuming workflow_ids is part of the request body
    // let workflows = workflowIds.join(',')
    const workflows = workflowIds?.map(item => item.id)?.join(',');
    const tags = tagIds?.map(item => item.id)?.join(',');
    // const partnerType = partnerTypes.map(item => item.id).join(',');
    // Default to 1 when inviteAs is not selected so save does not fail on null
    const partnerType = partnerTypes?.id || type === 'vendor' ? 1 : 8;
    let names = []
    names.push(name)
    let emails = []
    emails.push(email)
    let phoneNumber=req.body.values.phoneNumber
    let phone=[]
    phone.push(phoneNumber)

    let customerTypeId=req.body.values?.customerType?.id || null
    let customerTypes = [];
    customerTypes.push(customerTypeId)

      let i = 0
   
      while (req.body.values.hasOwnProperty(`name_${i}`)) {
     
        names.push(req.body.values[`name_${i}`]);
        i++;
      }
  
    
      console.log("$$$$$$$$$$$",workflows);

    i = 0
    while (req.body.values.hasOwnProperty(`email_${i}`)) {
      emails.push(req.body.values[`email_${i}`]);
      i++;
    }
    i = 0
    while (req.body.values.hasOwnProperty(`phoneNumber_${i}`)) {
      phone.push(req.body.values[`phoneNumber_${i}`]);
      i++;
    }

    i = 0
    while (req.body.values.hasOwnProperty(`customerType_${i}`)) {
      customerTypes.push(req.body.values[`customerType${i}`]?.id);
      i++;
    }

    let subject =  `${portalName} - Your account has been activated`;
    let email_content = `Thank you for choosing ${portalName}!.\n\nYour username (email address) and password is included for reference below.`;
    if (req.body.values.isCustomMessage) {
      subject = req.body.values.subject;
      email_content = req.body.values.emailcontent;
    }
    // Fallbacks if custom message was toggled but left empty
    if (!subject || subject.trim() === '') {
      subject = `${portalName} - Your account has been activated`;
    }
    if (!email_content || email_content.trim() === '') {
      email_content = `Thank you for choosing ${portalName}!.\n\nYour username (email address) and password is included for reference below.`;
    }
  
    let rank_id = 0;
    let added_by = req.body.userId;
    let timestamp = moment().unix();
    let user_id = 0;
    let status = 1; // default to active
    let emailError = [];
    let dataForDb = [];
    let email_entries = [];
    let email_partner = req.body.values.isSendEmail
    // if (email_partner) {
    //   status = 0;
    // } else {
    //   status = 1;
    // }
    status = 1; // keep client active even when invite email is sent

    let createSuccess = false;
    for (let i = 0; i < names.length; i++) {
      try {
        const partnerExists = await Client.getPartnerByEmail(emails[i]);
        const userExists = await Admin.getUserByEmail(emails[i]); // prevent duplicates across users table

        if (!partnerExists && !userExists) {
          if (email_entries[emails[i]] !== undefined) {
            email_entries[emails[i]]++;
          } else {
            email_entries[emails[i]] = 1;
          }

          if (email_entries[emails[i]] === 1) {
            dataForDb[i] = {
              name: names[i],
              contact_name: type === 'client' ? contact_name : null,
              email: emails[i],
              phone:phone[i],
              isVendor: type === 'vendor',
              workflows: workflows,
              contact_tags:tags,
              partner_type: partnerType,
              subject: subject,
              email_content: email_content,
              added_by: userId,
              customerTypeId:customerTypes[i],
              timestamp: moment().unix(),
              status: status,
              syncWithQuickBook: syncWithQuickbooks,
              quickbookCustomerName: quickbookCustomerName,
              avalaraTaxType: Number(avalaraTaxType),
              partnerQbId: partnerQbId,
              avatarUrl: avatarUrl,
              profile_pic: profile_pic,
              sales_rep_names: salesRepNames
            };

            if (type === 'client' || type !== 'client') {
              dataForDb[i]['address'] = address;
              dataForDb[i]['zip_code'] = zip_code;
              dataForDb[i]['city'] = city;
              dataForDb[i]['state_id'] = state;
              dataForDb[i]['phone_country_code'] = phone_country_code
              dataForDb[i]['avatarUrl'] = avatarUrl;
              dataForDb[i]['thumbnail'] = avatarUrl;
              dataForDb[i]['profile_pic'] = profile_pic;
            }

            if (type === 'vendor') {
              dataForDb[i]['remit_to_address'] = remitToAddress;
              dataForDb[i]['remit_to_city'] = remitToCity;
              dataForDb[i]['remit_to_zip_code'] = remitToZipCode;
              dataForDb[i]['remit_to_state_id'] = remitToState;
              dataForDb[i]['remit_to_country'] = remitToCountry;
            }

          } else {
            emailError[i] = email[i];
          }
        } else {
          emailError[i] = email[i];
        }
      } catch (error) {
        console.error('Error checking partner existence:', error);
        // Handle error as needed
      }
    }

    if (!empty(email_partner)) {

      for (const row of dataForDb) {

        const partnerId = await Client.addClient(row);
        let link={}
        if (partnerId) {
          console.log("$$$$$$$$$$$",workflows);
          link['workflow_ids'] = row['workflows'];
          link['partner_type_id'] = row['partner_type'];
          link['token'] = crypto.randomBytes(20).toString("hex");
          while (await OnboardLink.isInviteLinkExist(link['token'])) {
            link['token'] = crypto.randomBytes(8).toString("hex");
          }
          link['partner_id'] = partnerId.id;
          link['is_public'] = 0;
          link['timestamp'] = moment().unix()
          link['added_by'] = added_by
          link['status'] = 1;
          let linkId = await OnboardLink.addInviteLink(link)


          ///add to user table
          let user={}
          user['first_name'] = row.name
         
          user['company_name'] = row.name
          user['email'] = row.email
         
          user['username'] = user['email'];
          let password=crypto.randomBytes(5).toString("hex")
          let hashedPassword = Password.hash(password);
          user['password'] = hashedPassword;
          user['admin_password']=password
          // user['profile_pic'] = 'avatar.jpg';
          user['profile_pic'] = profile_pic;
          // user['status'] = 2; // keep as reference: pending status
          user['status'] = 1; // mark active even when invite email is sent
          user['user_type'] =userRoleId? userRoleId : 3;
          user['activated_by_admin']=1;
          let userId = await Admin.addUser(user);
          const avalaraData ={
            customerCode: String(userId),
            name: row.name,
            address: row.address,
            city: row.city,
            state: req.body.values.state?.short_name,
            zip: row.zip_code,
            country: 'US',
            email: row.email,
            phone: row.phone,
            taxExemptionReasonId:avalaraTaxType == 1 ? null : 1,  // Non-profit organization
            entityUseCode:avalaraTaxType == 1 ? null : "A"        // Avalara entity code for nonprofit
          }
          const avalaraResponse = await createAvalaraCustomer(avalaraData);
          console.log("$$$$AvalaraResponse,",avalaraResponse);
            console.log("$$$$Userr,",userId);
            if(userId){
              let partner={}
              partner['user_id'] = userId;
              // partner['status'] = 1;
              partner['avalaraCustomerId'] =(avalaraResponse && avalaraResponse.id) ? avalaraResponse.id : null;
              partner['thumbnail'] = row.avatarUrl || avatarUrl;
              let updated = await Client.updateClient(partner, partnerId.id);
            }
         
          if (!empty(linkId)){
            /*
            sgMail.setApiKey(SENDGRID_API_KEY)
            let message = sendPartnerMail();
            message = message.replace('[emailContent]', email_content)
            message = message.replace('[NAME]', row.name)
            message = message.replace('[LOGIN_URL]',`${BASE_URL}/auth/access-invite-link/${link['token']}`)
           
            // const msg = {
            //   to: row.email, // Change to your recipient
            //   from: 'nushara@skyniche.com', // Change to your verified sender
            //   subject: subject,
            //   text: subject,
            //   html: message,
            // }
            // sgMail
            //   .send(msg)
            //   .then(() => {
            //     console.log('Email sent')
            //   })
            //   .catch((error) => {
            //     console.error('Error sending email:', error.message, error.response?.body);
            //   })

              //node mailer

  const mailOptions = {
    // to: "ramdharvish@skyniche.com" , 
    to: row.email , 
    from: '"SignWise Solutions" <noreply@signwisesolutions.com>', //'nushara@skyniche.com', // Change to your verified sender
    subject: subject,
    text: subject,
    html: message,
  }
  try {
    // if(SEND_EMAIL){
    await transporterNodeMailer.sendMail(mailOptions);
    console.log('Email sent 2');
    // }
    
  } catch (error) {
    console.error('##########Err', error);
   
  }
            */

            // New email: account already active, no activation link required (branded template)
            let activatedMessage = activateProfileAccountToggle();
            activatedMessage = activatedMessage.replace(/\[SUBJECT\]/g, subject);
            activatedMessage = activatedMessage.replace('[CONTENT]', `Hello ${row.name || ''},`);
            activatedMessage = activatedMessage.replace(
              '[emailContent]',
              email_content || 'Thank you for creating your account. Your username (email address) and password is included for reference below.'
            );
            activatedMessage = activatedMessage.replace('[NAME]', row.email);
            activatedMessage = activatedMessage.replace('[PASSWORD]', password);

            const mailOptions = {
              to: row.email,
              from: '"SignWise Solutions" <noreply@signwisesolutions.com>',
              subject: subject,
              text: subject,
              html: activatedMessage,
            };

            try {
              await transporterNodeMailer.sendMail(mailOptions);
              console.log('Email sent 2 (account active)');
            } catch (error) {
              console.error('##########Err', error);
            }
          }

          // Save uploaded files
          if (uploadFiles && uploadFiles.length > 0) {
            const filesToSave = uploadFiles.map(file => ({
                client_id: partnerId.id,
                extension: file.extension,
                file_type: file.file_type,
                size: file.size,
                url: file.url,
                timestamp: moment().unix(),
            }));
            await Client.addClientTaxExemptAttachments(filesToSave);
          }

          console.log("###############additionalEmails",additionalEmails);
          // Save additional emails
          if (additionalEmails && additionalEmails.length > 0) {
            const emailsToSave = additionalEmails.map(emailObj => ({
              client_id: partnerId.id,
              contact_name: emailObj.contact_name,
              email: emailObj.email,
              timestamp: moment().unix(),
            }));
            await Client.addClientAdditionalEmails(emailsToSave);
          }
        }

      }
    }
    else {
      console.log("###############elseeeeeeeeeeee",dataForDb);
      for (const row of dataForDb) {
        createSuccess = await Client.addClient(row);
        console.log("###############loged1",createSuccess);
        if(createSuccess){
          console.log("###############loged1");
          let user={}
          user['first_name'] = row.name
         
          user['company_name'] = row.name
          user['email'] = row.email
         
          user['username'] = user['email'];
          let password=crypto.randomBytes(5).toString("hex")
          let hashedPassword = Password.hash(password);
          user['password'] = hashedPassword;
          user['admin_password']=password
          // user['profile_pic'] = 'avatar.jpg';
          user['profile_pic'] = profile_pic;
          user['status'] = 1;
          user['user_type'] =userRoleId ? userRoleId : 3;
          user['activated_by_admin']=1;
        // console.log("$$$$$$$$$$$",await Admin.getUserByEmail(user['email']));
        //   if (empty(await Admin.getUserByEmail(user['email']))) {
       
            let userId = await Admin.addUser(user);
            const avalaraData ={
              customerCode: String(userId),
              name: row.name,
              address: row.address,
              city: row.city,
              state: req.body.values.state?.short_name,
              zip: row.zip_code,
              country: 'US',
              email: row.email,
              phone: row.phone,
              taxExemptionReasonId:avalaraTaxType == 1 ? null : 1,  // Non-profit organization
              entityUseCode:avalaraTaxType == 1 ? null : "A"        // Avalara entity code for nonprofit
            }
            const avalaraResponse = await createAvalaraCustomer(avalaraData);
            console.log("$$$$AvalaraResponse,",avalaraResponse);
            console.log("$$$$Userr,",userId);
            if(userId){
              let partner={}
              partner['user_id'] = userId;
              partner['status'] = 1;
              partner['avalaraCustomerId'] =(avalaraResponse ) ? avalaraResponse.id : null;
              let updated = await Client.updateClient(partner, createSuccess.id);
            
            let subject = portalName + " - Your account has been activated";
            let welcome = "Hello " + user['company_name'] + ","
            let email_content = `Thank you for creating your account. Your username (email address) and password is included for reference below.`;
            sgMail.setApiKey(SENDGRID_API_KEY)
            let message = activateProfile()
            console.log("$$$$$$$$$$$$",user['username']);
            message = message.replace('[emailContent]', email_content)
            message = message.replace('[CONTENT]', welcome)
            message = message.replace('[NAME]', user['username'])
            message = message.replace('[PASSWORD]', password)
            message = message.replace('[LOGIN_URL]', `${BASE_URL}`)

            // const msg = {
            //   to: user.email, // Change to your recipient
            //   from: 'nushara@skyniche.com', // Change to your verified sender
            //   subject: subject,
            //   text: subject,
            //   html: message,
            // }
            // sgMail
            //   .send(msg)
            //   .then(() => {
            //     console.log('Email sent')
            //   })
            //   .catch((error) => {
            //     console.error(error)
            //   })

                          //node mailer
if (email_partner) {
  const mailOptions = {
    to:  row.email , 
    from: '"SignWise Solutions" <noreply@signwisesolutions.com>', //'nushara@skyniche.com', // Change to your verified sender
    subject: subject,
    text: subject,
    html: message,
  }
  try {
    // if(SEND_EMAIL){
    await transporterNodeMailer.sendMail(mailOptions);
    console.log('Email sent 3');
    // }
    
  } catch (error) {
    console.error('##########Err', error);
    }
  }

            let adminEmails = await Admin.getAllAdminEmails();
            let adminSubject = portalName + " - A new user has been registered";
            let adminWelcome = "Hello ! Admin" + ","
            let admin_email_content = `A new user has been registered`;
            let adminMessage = activateProfile()
            adminMessage = adminMessage.replace('[emailContent]', admin_email_content)
            adminMessage = adminMessage.replace('[CONTENT]', adminWelcome)
            adminMessage = adminMessage.replace('[NAME]', user['username'])
            adminMessage = adminMessage.replace('[PASSWORD]', password)
            adminMessage = adminMessage.replace('[LOGIN_URL]', `${BASE_URL}`)
            for (const row of adminEmails) {
              console.log("$$$$$$$$$$$$$$$nush",row);
              // const msg = {
              //   to: row, // Change to your recipient
              //   from: 'nushara@skyniche.com', // Change to your verified sender
              //   subject: adminSubject,
              //   text: adminSubject,
              //   html: adminMessage,
              // }
              // sgMail
              //   .send(msg)
              //   .then(() => {
              //     console.log('Email sent')
              //   })
              //   .catch((error) => {
              //     console.error(error)
              //   })

              const mailOptions = {
                to:  row , 
                from: '"SignWise Solutions" <noreply@signwisesolutions.com>', //'nushara@skyniche.com', // Change to your verified sender
                subject: adminSubject,
                text: adminSubject,
                html: adminMessage,
              }
              try {
                // if(SEND_EMAIL){
                await transporterNodeMailer.sendMail(mailOptions);
                console.log('Email sent 4');
                // }
               
              } catch (error) {
                console.error('##########Err', error);
                
              }
            }

            // Save uploaded files
            if (uploadFiles && uploadFiles.length > 0) {
              const filesToSave = uploadFiles.map(file => ({
                  client_id: createSuccess.id,
                  extension: file.extension,
                  file_type: file.file_type,
                  size: file.size,
                  url: file.url,
                  timestamp: moment().unix(),
              }));
              await Client.addClientTaxExemptAttachments(filesToSave);
            }
            console.log("###############additionalEmails2222",additionalEmails);
            // Save additional emails
            if (additionalEmails && additionalEmails.length > 0) {
              const emailsToSave = additionalEmails.map(emailObj => ({
                client_id: createSuccess.id,
                contact_name: emailObj.contact_name,
                email: emailObj.email,
                timestamp: moment().unix(),
              }));
              await Client.addClientAdditionalEmails(emailsToSave);
            }
         }
        }
      }
    }
    // if (createSuccess) {
    if (!empty(emailError)) {
      console.log("#################Name123",names.length);
      if (names.length > 1) {
        const uniqueEmailIds = [...new Set(emailError)];
        const excludedEmail = uniqueEmailIds.join(', ');
        reply.send({ status: 1, msg: `${type === 'vendor' ? 'Vendor' : 'Client'} added successfully but ignored these duplicate email id(s): ${excludedEmail}` })
      }
      else {
        reply.send({ status: 0, msg: `Failed to add ${type === 'vendor' ? 'Vendor' : 'Client'},Email id already exists` })
      }
    }
    else {
      reply.send({ status: 1, msg: `${type === 'vendor' ? 'Vendor' : 'Client'} added successfully` })
    }

    // }
  }
  catch (error) {
    console.error('Error checking partner existence:', error);
    // Handle error as needed
  }
}

const getAllStatesController = async (req, reply) => {
  try {
    const states = await Client.getAllStatesModel();

    if (states && states.length > 0) {
      reply.send({ status: 1, data: states });
    } else {
      reply.send({ status: 0, message: 'No states found' });
    }
  } catch (error) {
    console.error("Error fetching states:", error);
    reply.code(500).send({ status: 0, message: 'Server error while fetching states' });
  }
};

const fetchClientById = async (req, reply) => {
  let { userId, userType, id, hasPermission } = req.body
  let client = await Client.getClientById(id);
  let ranks = await Client.getAllRanks()
  let states = await Client.getAllStates()
  let clientTaxExemptAttachments = await Client.getClientTaxExemptAttachmentsByClientId(id);
  let additionalEmails = await Client.getClientAdditionalEmailsByClientId(id);
  let partnerTypes = await Client.getAllPartnerTypes();
  // let workflows = await Client.getAllWorkflows();
  let workflows=await Onboard.getAllClientFacingWorkflow()
  let contactTags = await ContactTags.getAllContactTags()
  let allRequest = await Client.getRequests("DESC", id, true)
  let allProjects= await Project.getAllProjects(hasPermission,false,userType,userId)
  let bankInfo=await OnboardWorkFlowStages.getBankinfobypartnerid(id)
  let taxInfo=await OnboardWorkFlowStages.getTaxinfobypartnerid(id)
  let portfolio=await OnboardWorkFlowStages.getPortfoliobypartnerid(id)
  let payments = await Client.getAllClientpayments(id)
   let customFieldTemplateId=await Client.getAllFieldTemplatesPartner(id)
   let tickets = await Tickets.getTicketsForClients(id)
  let customerTypes = await customerType.findAllCustomerType()

  //  console.log("$$$$$$$$$$$$",customFieldTemplateId);
   
  //  let field_template_ids = await OnboardWorkFlowFields.getfieldTemplatesbyStageId(customFieldTemplateId.stage_id);
  //  console.log("HIFI",field_template_ids);
  //  let fieldTemplates=[]
  // if (!empty(field_template_ids)) {
  //   fieldTemplates = await OnboardWorkFlowStages.getTemplateFormDataById(field_template_ids, id);
  // }
  let fieldTemplates = [];
  let i=0;
    for (const row of customFieldTemplateId) {
      let field_template_ids = await OnboardWorkFlowFields.getfieldTemplatesbyStageId(row.stage_id);
      console.log("HIFI", field_template_ids);

      if (!empty(field_template_ids)) {
        let templateData = await OnboardWorkFlowStages.getTemplateFormDataById(field_template_ids, id);
        fieldTemplates[i]=templateData;
        i++;
      }
    }
  let fieldTemplatesNew=[]
  let ids = new Set();
  fieldTemplates.forEach(innerArray => {
    let obj = innerArray[0];
    if (!ids.has(obj.id)) {
      fieldTemplatesNew.push(obj);
      ids.add(obj.id);
    }
  });
console.log("$$$$$$$$$$$$$$$$$$BBBBB",fieldTemplatesNew);
  let QuestionTemplateId=await Client.getQuestionnairTemplatesPartner(id)
  
  
  let QuestionTemplates=[]
  for (const row of QuestionTemplateId) { 
    let questionnairIds = await OnboardWorkFlowQuestionnaires.getQuestionairebyStageId(row.stage_id);
    
  if (!empty(questionnairIds)) {
   let  QuestionData = await OnboardWorkFlowStages.get_qns_formData_by_id(questionnairIds, id);
    QuestionTemplates.push(QuestionData);
    
  }
}
let QuestionTemplatesNew=[]
 ids = new Set();
 QuestionTemplates.forEach(innerArray => {
  let obj = innerArray[0];
  if (!ids.has(obj.id)) {
    QuestionTemplatesNew.push(obj);
    ids.add(obj.id);
  }
});
  let signAgrementData=await Client.getAllSignAgrementsOfPartner(id)
  let allDataEmpty=true;
  if(!empty(bankInfo)||!empty(taxInfo)||!empty(portfolio)||!empty(signAgrementData)||!empty(QuestionTemplates)||!empty(fieldTemplates)){
    allDataEmpty=false;
  }

  if (client) {
    reply.send({
      'status': 1,
      'msg': 'Records found',
      'workflows': workflows,
      'partnerTypes': partnerTypes,
      "contactTags":contactTags,
      states,
      clientTaxExemptAttachments,
      additionalEmails,
      tickets,
      payments,
      ranks,
      client,
      allRequest,
      allProjects,
      bankInfo,
      taxInfo,
      portfolio,
      signAgrementData,
      fieldTemplates:fieldTemplatesNew,
      QuestionTemplates:QuestionTemplatesNew,
      allDataEmpty,
      customerTypes
    })
  }
  else {
    reply.send({
      'status': 0,
      'msg': 'Record not  found',

    })
  }

}
const viewClientDetails = async (req, res) => {
  try {
    let userId = req.body
    let clientDetails = await Client.getPartnerByUserId(userId)
    let services = await Request.getServices(null)
    let id=clientDetails.id
    let bankInfo=await OnboardWorkFlowStages.getBankinfobypartnerid(id)
  let taxInfo=await OnboardWorkFlowStages.getTaxinfobypartnerid(id)
  let portfolio=await OnboardWorkFlowStages.getPortfoliobypartnerid(id)
 
   let customFieldTemplateId=await Client.getAllFieldTemplatesPartner(id)
  //  console.log("$$$$$$$$$$$$",customFieldTemplateId);
   
  //  let field_template_ids = await OnboardWorkFlowFields.getfieldTemplatesbyStageId(customFieldTemplateId.stage_id);
  //  console.log("HIFI",field_template_ids);
  //  let fieldTemplates=[]
  // if (!empty(field_template_ids)) {
  //   fieldTemplates = await OnboardWorkFlowStages.getTemplateFormDataById(field_template_ids, id);
  // }
  let fieldTemplates = [];
  let i=0;
    for (const row of customFieldTemplateId) {
      let field_template_ids = await OnboardWorkFlowFields.getfieldTemplatesbyStageId(row.stage_id);
      console.log("HIFI", field_template_ids);

      if (!empty(field_template_ids)) {
        let templateData = await OnboardWorkFlowStages.getTemplateFormDataById(field_template_ids, id);
        fieldTemplates[i]=templateData;
        i++;
      }
    }
  let fieldTemplatesNew=[]
  let ids = new Set();
  fieldTemplates.forEach(innerArray => {
    let obj = innerArray[0];
    if (!ids.has(obj.id)) {
      fieldTemplatesNew.push(obj);
      ids.add(obj.id);
    }
  });
console.log("$$$$$$$$$$$$$$$$$$BBBBB",fieldTemplatesNew);
  let QuestionTemplateId=await Client.getQuestionnairTemplatesPartner(id)
  
  
  let QuestionTemplates=[]
  for (const row of QuestionTemplateId) { 
    let questionnairIds = await OnboardWorkFlowQuestionnaires.getQuestionairebyStageId(row.stage_id);
    
  if (!empty(questionnairIds)) {
   let  QuestionData = await OnboardWorkFlowStages.get_qns_formData_by_id(questionnairIds, id);
    QuestionTemplates.push(QuestionData);
    
  }
}
let QuestionTemplatesNew=[]
 ids = new Set();
 QuestionTemplates.forEach(innerArray => {
  let obj = innerArray[0];
  if (!ids.has(obj.id)) {
    QuestionTemplatesNew.push(obj);
    ids.add(obj.id);
  }
});
  let signAgrementData=await Client.getAllSignAgrementsOfPartner(id)
  let allDataEmpty=true;
  if(!empty(bankInfo)||!empty(taxInfo)||!empty(portfolio)||!empty(signAgrementData)||!empty(QuestionTemplates)||!empty(fieldTemplates)){
    allDataEmpty=false;
  }
    if (!empty(clientDetails)) {
      res.send({
        status: 1,
        clientDetails,
        services,
        bankInfo,
        taxInfo,
        portfolio,
        signAgrementData,
        fieldTemplates:fieldTemplatesNew,
        QuestionTemplates:QuestionTemplatesNew,
        allDataEmpty
        
      })
    } else {
      res.send({
        status: 0
      })
    }
  } catch (error) {
    console.log(error, 'Error in fetching client details in view profile')
  }
}
const updateProfilePic=async(req,res)=>{
  try {
    let clientUserId = req.body.userid
    let clientDetails = await Client.getPartnerByUserId(clientUserId)
    let client={}
    client.profile_pic=req.body.imgURL

    
    // let updated = await Client.updateClient(client, clientDetails.id)
    let updated = await Admin.updateUser( clientUserId,client)
    if(updated){
      res.send({  
        status:1,message:'Profile image updated successfully'
      })
    }else{
      res.send({
        status:0,message:'Profile image updated failed'
      })
    }
  } catch (error) {
    console.log(error,'error in uplaoding image')
  }
}
const updateCoverImage=async(req,res)=>{
  try {
    let clientUserId = req.body.userid
    let clientDetails = await Client.getPartnerByUserId(clientUserId)
    let client={}
    client.thumbnail=req.body.imgURL

    
     let updated = await Client.updateClient(client, clientDetails.id)
    
    if(updated){
      res.send({  
        status:1,message:'Cover Image updated successfully'
      })
    }else{
      res.send({
        status:0,message:'Cover Image updated failed'
      })
    }
  } catch (error) {
    console.log(error,'error in uplaoding image')
  }
}


////////////////////////////////////quickbook

// const addClientVendorToQuickbook = async (data, urlType) => {
//   try {
//     // Check if the customer/vendor already exists
//     let checkResponse;
//     let token = await getNewAccessToken()
    
//     const apiUrl = PRODUCTION
//     ? `https://quickbooks.api.intuit.com/v3/company/${companyId}`
//     : `https://sandbox-quickbooks.api.intuit.com/v3/company/${companyId}`;

    
//     checkResponse = await axios.get(
//       // `https://sandbox-quickbooks.api.intuit.com/v3/company/${companyId}/query?query=select * from ${urlType} where DisplayName='${data.DisplayName}'`,
//       `${apiUrl}/query?query=select * from ${urlType} where DisplayName='${data.DisplayName}'`,
//       {
//         headers: {
//           'Authorization': `Bearer ${token}`,
//           'Accept': 'application/json',
//           'Content-Type': 'application/json',
//         },
//       }
//     );

//     // If a customer/vendor is found, return early or handle accordingly
//     if (checkResponse.data.QueryResponse[urlType] && checkResponse.data.QueryResponse[urlType].length > 0) {
//       console.log(`${urlType} already exists with the name: ${data.DisplayName}`);
//       return;  // Exit if the customer/vendor exists
//     }

//     token = await getNewAccessToken()
//     // Otherwise, create a new customer/vendor
//     let response = await axios.post(
//       // `https://sandbox-quickbooks.api.intuit.com/v3/company/${companyId}/${urlType}`,
//       `${apiUrl}/${urlType}`,
//       data,
//       {
//         headers: {
//           'Authorization': `Bearer ${token}`,
//           'Accept': 'application/json',
//           'Content-Type': 'application/json',
//         },
//       }
//     );

//     // return response.data; 
//     const partnerQbId =urlType === 'customer' ? response.data.Customer?.Id : response.data.Vendor?.Id;
//     return partnerQbId

//   } catch (error) {

//     const apiUrl = PRODUCTION
//     ? `https://quickbooks.api.intuit.com/v3/company/${companyId}`
//     : `https://sandbox-quickbooks.api.intuit.com/v3/company/${companyId}`;

//     if (error.response && error.response.status === 401) {
//       // Refresh the access token and retry
//       const newAccessToken = await getNewAccessToken();
//       let response = await axios.post(
//         // `https://sandbox-quickbooks.api.intuit.com/v3/company/${companyId}/${urlType}`,
//         `${apiUrl}/${urlType}`,
//         data,
//         {
//           headers: {
//             'Authorization': `Bearer ${newAccessToken}`,
//             'Accept': 'application/json',
//             'Content-Type': 'application/json',
//           },
//         }
//       );
//       // return response.data;
//       const partnerQbId =urlType === 'customer' ? response.data.Customer?.Id : response.data.Vendor?.Id;
//       return partnerQbId
//     } else if (error.response && error.response.status === 400 && error.response.data.Fault.Error[0].code === '6240') {
//       console.error('Duplicate Name Exists:', error.response.data.Fault.Error[0].Message);
//     } else {
//       console.error('An error occurred:', error);
//     }
//   }
// };

const addClientVendorToQuickbook = async (data, urlType) => {
  try {
    let checkResponse;
    let token = await getNewAccessToken();
    const apiUrl = PRODUCTION
      ? `https://quickbooks.api.intuit.com/v3/company/${companyId}`
      : `https://sandbox-quickbooks.api.intuit.com/v3/company/${companyId}`;

    const query = `select * from ${urlType} where DisplayName='${encodeURIComponent(data.DisplayName)}'`;

    // Send the request to check if the vendor already exists
    checkResponse = await axios.get(
      `${apiUrl}/query?query=${query}`,
      {
        headers: {
          'Authorization': `Bearer ${token}`,
          'Accept': 'application/json',
          'Content-Type': 'application/json',
        },
      }
    );

    // Check if the vendor already exists
    if (checkResponse.data.QueryResponse[urlType] && checkResponse.data.QueryResponse[urlType].length > 0) {
      console.log(`${urlType} already exists with the name: ${data.DisplayName}`);
      return;  // Exit if the customer/vendor exists
    }

    token = await getNewAccessToken();  // Refresh token for the next request

    // Create the new customer/vendor if not found
    let response = await axios.post(
      `${apiUrl}/${urlType}`,
      data,
      {
        headers: {
          'Authorization': `Bearer ${token}`,
          'Accept': 'application/json',
          'Content-Type': 'application/json',
        },
      }
    );

    const partnerQbId = urlType === 'customer' ? response.data.Customer?.Id : response.data.Vendor?.Id;
    return partnerQbId;

  } catch (error) {
    console.error('API Error:', error.response ? error.response.data : error);

    const apiUrl = PRODUCTION
      ? `https://quickbooks.api.intuit.com/v3/company/${companyId}`
      : `https://sandbox-quickbooks.api.intuit.com/v3/company/${companyId}`;

    if (error.response && error.response.status === 401) {
      const newAccessToken = await getNewAccessToken();
      let response = await axios.post(
        `${apiUrl}/${urlType}`,
        data,
        {
          headers: {
            'Authorization': `Bearer ${newAccessToken}`,
            'Accept': 'application/json',
            'Content-Type': 'application/json',
          },
        }
      );
      const partnerQbId = urlType === 'customer' ? response.data.Customer?.Id : response.data.Vendor?.Id;
      return partnerQbId;
    } else if (error.response && error.response.status === 400 && error.response.data.Fault.Error[0].code === '6240') {
      console.error('Duplicate Name Exists:', error.response.data.Fault.Error[0].Message);
    } else {
      console.error('An error occurred:', error);
    }
  }
};

////////////////////////////////////////


const updateClient = async (request, reply) => {
  try{
    let clientId = request.body.clientId
    let client = {};
    let values = request.body.values
    let isNewVendor = request.body.isNewVendor
    console.log('valuesssssssssssss', values)

    let clientData = await Client.getClientById(clientId);

    client.name = values.name
    client.contact_name = clientData.isVendor === 0 ? values.contact_name : null
    client.address = values.address
    client.email = values.email
    client.thumbnail = values.avatarUrl
    client.partner_type = values.partnerType.id,
    client.rank_id = values.rank.id
    client.phone = values.phoneNumber
    client.phone_country_code = values.phone_country_code
    client.zip_code = values.zipcode
    client.fax_number = values.fax
    client.city = values.city
    const contactTagIds = values.contact_tags.map(tag => tag.id);
    client.contact_tags = contactTagIds.join(',');  
    client.state_id = values.state ? values.state.id : 0
    client.quickbookCustomerName = values.quickbookCustomerName;
    client.existingQuickbookCustomerName= values.existingQuickbookCustomerName
    client.customerTypeId =values?.customerType?.id || null
    let userRoleId = values?.role?.id || null
    const salesRepNames = values.salesRepNames?.map(rep => rep.id)?.join(',') || null;
    console.log("salesRepNames",values.salesRepNames);
    client.sales_rep_names = salesRepNames;
    client.avalaraTaxType = Number(values?.avalaraTaxType || 1)
    let uploadFiles = values.uploadFiles || [];
    let additionalEmails = values.additionalEmails || [];
    if (values?.partnerQbId){
      client.partnerQbId = values?.partnerQbId || null;
      client.syncWithQuickBook = 1;
    }

    const avalaraData ={
      customerCode: String(values?.user_id),
      name: client.name,
      address: client.address,
      city: client.city,
      state: values?.state?.short_name || null,
      zip: client.zip_code,
      country: 'US',
      email: client.email,
      phone: client.phone,
      taxExemptionReasonId:client.avalaraTaxType == 1 ? null : 1,  // Non-profit organization
      entityUseCode:client.avalaraTaxType == 1 ? null : "A"        // Avalara entity code for nonprofit
    }

    const avalaraResponse = await upsertAvalaraCustomer(avalaraData);
    console.log("AvalaraResponse,",avalaraResponse);
    if(avalaraResponse){
      client.avalaraCustomerId = avalaraResponse.id
    }

    let updatedUser=null

    if(clientData && clientData.isVendor == 1) {
      client.remit_to_address = values.remitToAddress || '';
      client.remit_to_city = values.remitToCity || '';
      client.remit_to_zip_code = values.remitToZipCode || '';
      client.remit_to_state_id = values.remitToState ? values.remitToState.id : null;
      client.remit_to_country = values.remitToCountry ? values.remitToCountry.code : null;
    }

    const dataForQB = {
      // "DisplayName": client?.quickbookCustomerName + locationInventoryCode || '',
      "DisplayName": client?.quickbookCustomerName  || '',
      "CompanyName": client?.name,
      "PrimaryEmailAddr": {
        "Address": client?.email
      },
      BillAddr: {
        Line1: client?.address,
        City: client?.city,
        CountrySubDivisionCode:  values.state?.name || '',
        PostalCode: client?.zip_code,
      },
      ShipAddr: null, 
      "PrimaryPhone": {
        "FreeFormNumber": client?.phone.toString()
      }
    };

    console.log("dataaaaaaaaaaaaaaaaaaaaaa",clientData);
  
    let urlType = clientData.isVendor === 0 ? 'customer' : 'vendor';

    const apiUrl = PRODUCTION
    ? `https://quickbooks.api.intuit.com/v3/company/${companyId}`
    : `https://sandbox-quickbooks.api.intuit.com/v3/company/${companyId}`;

    if(clientData.syncWithQuickBook !=1 || isNewVendor) {
      if(clientData.isVendor ==1) {
        try{
          // let vendorRes = await  addClientVendorToQuickbook(dataForQB,urlType)
          let vendorRes = await addClientToQuickbook('vendor', apiUrl, dataForQB, dataForQB) || null;
          if(vendorRes){
            client.syncWithQuickBook = 1
            client.partnerQbId = vendorRes
          }
        }catch(error){
          console.log("addClientVendorToQuickbook----vendor",error);
        }
            
      } else if(clientData.isVendor == 0){
        console.log("client-----------------------")
        try{
          // let clientRes = await addClientVendorToQuickbook(dataForQB,urlType)
          let clientRes = await addClientToQuickbook('client', apiUrl, dataForQB, dataForQB) || null;
          if(clientRes){
            client.syncWithQuickBook =clientRes ? 1 : 0
            client.partnerQbId = clientRes
          }
        }catch(error){
          console.log('addClientVendorToQuickbook---client',error)
        }
      }
    }
    try {
      const updateQuickBooksCustomer = async (accessToken) => {
        try {
          // Step 1: Search for the customer by display name
          const url = PRODUCTION
          ? `https://quickbooks.api.intuit.com/v3/company/${companyId}/query?query=select * from Customer where DisplayName='${client.existingQuickbookCustomerName}'`
          : `https://sandbox-quickbooks.api.intuit.com/v3/company/${companyId}/query?query=select * from Customer where DisplayName='${client.existingQuickbookCustomerName}'`;
          let searchResponse = await axios.get(
            url,
            {
              headers: {
                'Authorization': `Bearer ${accessToken}`,
                'Accept': 'application/json',
                'Content-Type': 'application/json',
              },
            }
          );
      
          const customers = searchResponse.data.QueryResponse.Customer;
          if ((customers && customers.length > 0) || values?.partnerQbId) {
            const customerId =values?.partnerQbId ||  customers[0].Id;
      
            // Step 2: Fetch existing customer details
            const Fetchurl = PRODUCTION
            ? `https://quickbooks.api.intuit.com/v3/company/${companyId}/customer/${customerId}`
            : `https://sandbox-quickbooks.api.intuit.com/v3/company/${companyId}/customer/${customerId}`;
            let response = await axios.get(
              Fetchurl,
              {
                headers: {
                  'Authorization': `Bearer ${accessToken}`,
                  'Accept': 'application/json',
                  'Content-Type': 'application/json',
                },
              }
            );
      
            // Step 3: Update customer details, including SyncToken
            const updatedCustomerPayload = {
              ...response.data.Customer,
              // DisplayName: client.quickbookCustomerName + locationInventoryCode || '',
              DisplayName: client.quickbookCustomerName  || '',
              CompanyName: client.name,
              PrimaryEmailAddr: { Address: client.email },
              PrimaryPhone: { FreeFormNumber: client.phone },
              BillAddr: {
                Line1: client.address,
                City: client.city,
                CountrySubDivisionCode: values?.state?.name || '',
                PostalCode: client.zip_code,
              },
              ShipAddr: null, 
              SyncToken: response.data.Customer.SyncToken, // Include SyncToken here
            // };
      
           
              sparse: false,
              Id:customerId
            };
            const Updateurl = PRODUCTION
            ? `https://quickbooks.api.intuit.com/v3/company/${companyId}/customer?minorversion=73`
            : `https://sandbox-quickbooks.api.intuit.com/v3/company/${companyId}/customer?minorversion=73`;
            response = await axios.post(
              Updateurl,
              updatedCustomerPayload,
              {
                headers: {
                  'Authorization': `Bearer ${accessToken}`,
                  'Accept': 'application/json',
                  'Content-Type': 'application/json',
                },
              }
            );
      
            console.log(`Client updated:`, response.data);
            if(!values?.partnerQbId){
              client.partnerQbId = response.data.Customer?.Id
            }
          } else {
            console.log('Customer not found in QuickBooks');
          }
        } catch (error) {
          if (error.response && error.response.status === 401) {
            try {
              const newAccessToken = await getNewAccessToken();
              await updateQuickBooksCustomer(newAccessToken);
            } catch (tokenError) {
              if (tokenError.response && tokenError.response.data.error === 'invalid_grant') {
                const authUrl = generateAuthUrl();
                reply.redirect(authUrl);
              } else {
                throw tokenError;
              }
            }
          } else {
            console.error('Error updating customer:', error.response ? error.response.data : error.message);
            throw error;
          }
        }
      };

      const updateQuickBooksVendor = async (accessToken) => {
        try {
          const searchUrl = PRODUCTION
          ? `https://quickbooks.api.intuit.com/v3/company/${companyId}/query?query=select * from Vendor where DisplayName='${client.existingQuickbookCustomerName}'`
          : `https://sandbox-quickbooks.api.intuit.com/v3/company/${companyId}/query?query=select * from Vendor where DisplayName='${client.existingQuickbookCustomerName}'`;
          // Step 1: Search for the vendor by display name
          let searchResponse = await axios.get(
            searchUrl,
            {
              headers: {
                'Authorization': `Bearer ${accessToken}`,
                'Accept': 'application/json',
                'Content-Type': 'application/json',
              },
            }
          );
      
          const vendors = searchResponse.data.QueryResponse.Vendor;
          console.log("quickbookVendorName------------------", vendors);
          if ((vendors && vendors.length > 0) || values?.partnerQbId) {
            const vendorId =values?.partnerQbId || vendors[0].Id;
      
            const Fetchurl = PRODUCTION
            ? `https://quickbooks.api.intuit.com/v3/company/${companyId}/vendor/${vendorId}`
            : `https://sandbox-quickbooks.api.intuit.com/v3/company/${companyId}/vendor/${vendorId}`;
            // Step 2: Fetch existing vendor details
            let response = await axios.get(
              Fetchurl,
              {
                headers: {
                  'Authorization': `Bearer ${accessToken}`,
                  'Accept': 'application/json',
                  'Content-Type': 'application/json',
                },
              }
            );
      
            // Step 3: Update vendor details, including SyncToken
            const updatedVendorPayload = {
              ...response.data.Vendor,
              // DisplayName: client.quickbookCustomerName +locationInventoryCode,
              DisplayName: client.quickbookCustomerName,
              CompanyName: client.name,
              PrimaryEmailAddr: { Address: client.email },
              PrimaryPhone: { FreeFormNumber: client.phone },
              BillAddr: {
                Line1: client.address,
                City: client.city,
                CountrySubDivisionCode: values?.state?.name || '',
                PostalCode: client.zip_code,
              },
              ShipAddr: null, 
              SyncToken: response.data.Vendor.SyncToken, // Include SyncToken for update
            // };
      
          sparse: false,
              Id:vendorId
            };
            const Updateurl = PRODUCTION
            ? `https://quickbooks.api.intuit.com/v3/company/${companyId}/vendor?minorversion=73`
            : `https://sandbox-quickbooks.api.intuit.com/v3/company/${companyId}/vendor?minorversion=73`;
            response = await axios.post(
              Updateurl,
              updatedVendorPayload,
              {
                headers: {
                  'Authorization': `Bearer ${accessToken}`,
                  'Accept': 'application/json',
                  'Content-Type': 'application/json',
                },
              }
            );
      
            console.log(`Vendor updated:`, response.data);
            if(!values?.partnerQbId){
              client.partnerQbId = response.data.Vendor?.Id
            }
          } else {
            console.log('Vendor not found in QuickBooks');
          }
        } catch (error) {
          if (error.response && error.response.status === 401) {
            try {
              const newAccessToken = await getNewAccessToken();
              await updateQuickBooksVendor(newAccessToken);
            } catch (tokenError) {
              if (tokenError.response && tokenError.response.data.error === 'invalid_grant') {
                const authUrl = generateAuthUrl();
                reply.redirect(authUrl);
              } else {
                throw tokenError;
              }
            }
          } else {
            console.error('Error updating vendor:', error.response ? error.response.data : error.message);
            throw error;
          }
        }
      };
      
      try {
        if (clientData.syncWithQuickBook == 1) {
          const token = await getNewAccessToken();
          if (token) {
            if (clientData.isVendor == 1) {
              await updateQuickBooksVendor(token);
            } else {
              await updateQuickBooksCustomer(token);
            }
          } else {
            console.warn('⚠️ QuickBooks token refresh failed — skipping QuickBooks update.');
          }
        }
      } catch (syncError) {
        console.error('⚠️ QuickBooks sync failed but proceeding with client update:', syncError.message || syncError);
      }
    } catch (error) {
      console.log(error)
    }
    
// return

    if(!empty(values.profile_pic) || !empty(values.name) || request.body?.newPassword){
      let user={}
      if(request.body?.newPassword){
        const hashedNewPassword = Password.hash(request.body.newPassword);
        user.password = hashedNewPassword;
        user.admin_password = request.body.newPassword;
      }
      if(!empty(values.profile_pic)){
        user.profile_pic = values.profile_pic
      }
      if(!empty(values.name)){
        user.first_name = values.name
      }
      if(userRoleId){
      user.user_type =userRoleId
      }
      updatedUser=await Admin.updateUser(clientData.user_id,user)
    }
    let updated = await Client.updateClient(client, clientId)

    // Delete existing uploaded files
    await Client.deleteClientTaxExemptAttachmentsByClientId(clientId);

    // Save uploaded files
    if (uploadFiles && uploadFiles.length > 0) {
      const filesToSave = uploadFiles.map(file => ({
          client_id: clientId,
          extension: file.extension,
          file_type: file.file_type,
          size: file.size,
          url: file.url,
          timestamp: moment().unix(),
      }));
      await Client.addClientTaxExemptAttachments(filesToSave);
    }


    // Handle additional emails: update status or add new ones
    const existingAdditionalEmails = await Client.getClientAdditionalEmailsByClientId(clientId);
    const incomingAdditionalEmails = values.additionalEmails || [];

    const existingEmailMap = new Map(existingAdditionalEmails.map(email => [email.id, email]));
    const incomingEmailIds = new Set();
    const emailsToAddNew = [];

    for (const incomingEmail of incomingAdditionalEmails) {
      if (incomingEmail.id && existingEmailMap.has(incomingEmail.id)) {
        incomingEmailIds.add(incomingEmail.id);
        const existingEmail = existingEmailMap.get(incomingEmail.id);
        // If email address or contact name has changed, update it
        if (existingEmail.email !== incomingEmail.email || existingEmail.contact_name !== incomingEmail.contact_name) {
          await Client.updateClientAdditionalEmail(incomingEmail.id, { email: incomingEmail.email, contact_name: incomingEmail.contact_name, timestamp: moment().unix() });
        }
      } else {
        // This is a new email
        emailsToAddNew.push({
          client_id: clientId,
          contact_name: incomingEmail.contact_name,
          email: incomingEmail.email,
          timestamp: moment().unix(),
          status: 1,
        });
      }
    }

    // Deactivate emails that are in existing but not in incoming
    for (const existingEmail of existingAdditionalEmails) {
      if (!incomingEmailIds.has(existingEmail.id)) {
        await Client.updateClientAdditionalEmail(existingEmail.id, { status: 0 });
      }
    }

    // Add new emails
    if (emailsToAddNew.length > 0) {
      await Client.addClientAdditionalEmails(emailsToAddNew);
    }


    if(updatedUser && userRoleId){
      let staffUserTypeData = {
        user_type : userRoleId,
        parent_user_type : userRoleId
      }
      const updatedClientStaffUsers = await Admin.updateStaffsOfAClient(clientData?.user_id,staffUserTypeData)
      console.log('updatedClientStaffUsers', updatedClientStaffUsers)
    }
    if (updated || updatedUser) {
      let notificationMsg = '';
      let notificationUrl = 'client';
      if(clientData?.isVendor == 1){
        console.log('vendorrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr')
       notificationMsg = `The Vendor "${client.name}" has been updated.`;

    // if(clientData.syncWithQuickBook ==1) {

    //    let token = await getNewAccessToken()
    //    await updateQuickBooksVendor(token);
    // }
      }else{
         notificationMsg = `The client "${client.name}" has been updated.`;
    // if(clientData.syncWithQuickBook ==1) {
    //     let token = await getNewAccessToken()
    //    await updateQuickBooksCustomer(token);
    // }
      }

      // Alert to user of new added project

      const incrementResult_User = await Admin.incrementField(request.body.userId, "noti_count", 1);

      let notificationData_user = {
        user_id: request.body.userId,
        url: notificationUrl,
        timestamp: moment().unix(),
        message: notificationMsg,
        status: 1,
        from_id: request.body.userId,
      };

      const addNotificationResult_user = await Admin.addNotification(notificationData_user);

      // Alert to all admins of new added project

      let adminIds = await Admin.getAllAdminIds();
      let notificationData = {};

      for (const adminId of adminIds) {
        if (adminId != request.body.userId) {
          const incrementResult = await Admin.incrementField(adminId, "noti_count", 1);

          notificationData = {
            user_id: adminId,
            url: notificationUrl,
            timestamp: moment().unix(),
            message: notificationMsg,
            status: 1,
            from_id: request.body.userId,
          };

          const addNotificationResult = await Admin.addNotification(notificationData);
        }
      }
      reply.send({ status: 1, message: notificationMsg })
    }
    else {
      reply.send({ status: 0, message: "No new updates" });
    }
  }catch(error){
    console.log('Error-updating client--',error);
    reply.send({ status: 500, message: "Failed to update the client",err:error.message });
  }
}

////////////////////////////////////////quickbook
const findCustomerByNameAndMarkInactive = async (customerName) => {
  try {
    let token = await getNewAccessToken(); // Fetch a new access token
    const apiUrl = PRODUCTION
      ? `https://quickbooks.api.intuit.com/v3/company/${companyId}`
      : `https://sandbox-quickbooks.api.intuit.com/v3/company/${companyId}`;

    // Step 1: Find the customer by name
    const findCustomerResponse = await axios.get(
      `${apiUrl}/query`,
      {
        headers: {
          Authorization: `Bearer ${token}`,
          'Content-Type': 'application/json',
          Accept: 'application/json'
        },
        params: {
          query: `SELECT * FROM Customer WHERE DisplayName = '${customerName}'`
        }
      }
    );

    const customers = findCustomerResponse.data.QueryResponse.Customer;
    if (customers && customers.length > 0) {
      const customer = customers[0]; // Assuming you want to process the first match

      console.log('Customer found:', customer);

      // Step 2: Mark the customer as inactive
      const updatePayload = {
        Id: customer.Id,
        SyncToken: customer.SyncToken, // Use the current SyncToken for the customer
        Active: false, // Mark as inactive
        sparse: true // Indicate this is a sparse update
      };

      const updateResponse = await axios.post(
        `${apiUrl}/customer`,
        updatePayload,
        {
          headers: {
            Authorization: `Bearer ${token}`,
            'Content-Type': 'application/json',
            Accept: 'application/json'
          }
        }
      );

      if (updateResponse.status === 200) {
        console.log('Customer has been successfully marked as inactive:', updateResponse.data);
      } else {
        console.error('Failed to mark customer as inactive:', updateResponse.data);
      }

    } else {
      console.log(`Customer with name '${customerName}' not found.`);
    }
  } catch (error) {
    console.error('Error processing customer:', error.response ? error.response.data : error.message);
  }
};

const findVendorByNameAndMarkInactive = async (vendorName) => {
  try {
    let token = await getNewAccessToken(); // Fetch a new access token
    const apiUrl = PRODUCTION
      ? `https://quickbooks.api.intuit.com/v3/company/${companyId}`
      : `https://sandbox-quickbooks.api.intuit.com/v3/company/${companyId}`;

    // Step 1: Find the vendor by name
    const findVendorResponse = await axios.get(
      `${apiUrl}/query`,
      {
        headers: {
          Authorization: `Bearer ${token}`,
          'Content-Type': 'application/json',
          Accept: 'application/json'
        },
        params: {
          query: `SELECT * FROM Vendor WHERE DisplayName = '${vendorName}'`
        }
      }
    );

    const vendors = findVendorResponse.data.QueryResponse.Vendor;
    if (vendors && vendors.length > 0) {
      const vendor = vendors[0]; // Assuming you want to process the first match

      console.log('Vendor found:', vendor);

      // Step 2: Mark the vendor as inactive
      const updatePayload = {
        Id: vendor.Id,
        SyncToken: vendor.SyncToken, // Use the current SyncToken for the vendor
        Active: false, // Mark as inactive
        sparse: true // Indicate this is a sparse update
      };

      const updateResponse = await axios.post(
        `${apiUrl}/vendor`,
        updatePayload,
        {
          headers: {
            Authorization: `Bearer ${token}`,
            'Content-Type': 'application/json',
            Accept: 'application/json'
          }
        }
      );

      if (updateResponse.status === 200) {
        console.log('Vendor has been successfully marked as inactive:', updateResponse.data);
      } else {
        console.error('Failed to mark vendor as inactive:', updateResponse.data);
      }

    } else {
      console.log(`Vendor with name '${vendorName}' not found.`);
    }
  } catch (error) {
    console.error('Error processing vendor:', error.response ? error.response.data : error.message);
  }
};


///////////////////////////////////////

const removeClient = async (req, reply) => {
  try {
    let { id, status } = req.body;
    console.log(id, status, '%%%%%%%%%%%');
    let currentDate = Math.round(moment().valueOf() / 1000);
    if (!empty(id)) {
      let client = await Client.getClientById(id);

      console.log("clientclientclient",client)
      // try {
      //   if(client.isVendor ===1 && client.syncWithQuickBook==1) {

      //     const result = await findVendorByNameAndMarkInactive(client.quickbookCustomerName)


      //   }else if(client.isVendor ===0 && client.syncWithQuickBook==1){

      //     const result = await findCustomerByNameAndMarkInactive(client.quickbookCustomerName)
      //   }
      // } catch (error) {
      //   console.log("error remove client or vendor",error)
      // }
      if (status == 2) {
        let deleted = await Client.deleteClient(id);
        console.log("####################", deleted);
        if (deleted) {
          // // Notification
          if (client["user_id"]) {
            deleted = await Admin.deleteUser(client["user_id"]);}
          console.log("####################", client);
          let notificationMsg = `The Client  "${client.name}" has been deleted.`;
          let notificationUrl = "client";

          let adminIds = await Admin.getAllAdminIds();
          let notificationData = {};

          for (const adminId of adminIds) {

            const incrementResult = await Admin.incrementField(adminId, "noti_count", 1);

            notificationData = {
              user_id: adminId,
              url: notificationUrl,
              timestamp: currentDate,
              message: notificationMsg,
              status: 1,
              from_id: client.added_by,
            };

            const addNotificationResult = await Admin.addNotification(notificationData);

          }
          reply.send({ status: 1, message: `The Client "${client.name}" has been removed successfully!` })
        }
        else {
          reply.send({ status: 0, message: `Unable to remove client!` })
        }
      }
      else if (status == 1) {

        let deleted = await Project.updateclient(id, { status: status })

        if (deleted) {
          // // Notification
          let notificationMsg = `The client "${client.name}" has been made active.`;
          let notificationUrl = "client";

          let adminIds = await Admin.getAllAdminIds();
          let notificationData = {};

          for (const adminId of adminIds) {

            const incrementResult = await Admin.incrementField(adminId, "noti_count", 1);

            notificationData = {
              user_id: adminId,
              url: notificationUrl,
              timestamp: currentDate,
              message: notificationMsg,
              status: 1,
              from_id: client.added_by,
            };

            const addNotificationResult = await Admin.addNotification(notificationData);

          }
          reply.send({ status: 2, message: `The client "${client.name}" has been made active!` })
        }
        else {
          reply.send({ status: 3, message: "Unable to activate the client!" })
        }
      }
      else if (status == 0) {
        let deleted = await Project.updateclient(id, { status: status });

        if (deleted) {
          // Notification
          let notificationMsg = `The client "${client.name}" has been made inactive.`;
          let notificationUrl = "client";

          let adminIds = await Admin.getAllAdminIds();
          let notificationData = {};

          for (const adminId of adminIds) {

            const incrementResult = await Admin.incrementField(adminId, "noti_count", 1);

            notificationData = {
              user_id: adminId,
              url: notificationUrl,
              timestamp: currentDate,
              message: notificationMsg,
              status: 1,
              from_id: client.added_by,
            };

            const addNotificationResult = await Admin.addNotification(notificationData);

          }

          reply.send({ status: 4, message: `The client "${client.name}" has been made Inactive!` })
        }
        else {
          reply.send({ status: 5, message: "Unable to deactivate the client!" })
        }
      }
    }
    else {
      console.log("Error................")
      reply.send({ status: 500, message: "Error!" })
    }
  }
  catch (error) {
    reply.status(500).send({ message: "Server error" + error })
  }
}

const addInternalNotes = async (request, reply) => {
  console.log(request.body, '%%%%%%%%%%%%')
  let clientId = request.body.clientId
  let client = {};

  client.internal_note = request.body.internalNote
  let updated = await Client.updateClient(client, clientId)
  if (updated) {
    let client = await Client.getClientById(clientId);
    let notificationMsg = `Internal note has been added to the profile of ${client.name}.`;
    let notificationUrl = "client";

    // Alert to user of new added project

    const incrementResult_User = await Admin.incrementField(request.body.userId, "noti_count", 1);
    console.log("incrementResult--to--user-----------", incrementResult_User)
    let notificationData_user = {
      user_id: request.body.userId,
      url: notificationUrl,
      timestamp: moment().unix(),
      message: notificationMsg,
      status: 1,
      from_id: request.body.userID,
    };

    const addNotificationResult_user = await Admin.addNotification(notificationData_user);

    // Alert to all admins of new added project

    let adminIds = await Admin.getAllAdminIds();
    let notificationData = {};

    for (const adminId of adminIds) {

      if (adminId != request.body.userId) {
        const incrementResult = await Admin.incrementField(adminId, "noti_count", 1);

        notificationData = {
          user_id: request.body.userId,
          url: notificationUrl,
          timestamp: moment().unix(),
          message: notificationMsg,
          status: 1,
          from_id: request.body.userId,
        };

        const addNotificationResult = await Admin.addNotification(notificationData);

      }
    }
    reply.send({ status: 1, message: `Internal note has been added to the profile of ${client.name}.` })
  }
  else {
    reply.send({ status: 0, message: "Failed to add internal note" });
  }

}
const sendMessage = async (request, reply) => {
  try {
    let userId = request.body.userId;
    let msg = request.body.msg;
    let to = request.body.to;
    let error = "";
    let timestamp = Math.round(moment().valueOf() / 1000);
    let result = false;
    if (msg != "") {
      result = await ClientChat.setPartnerMessage(to, userId, msg, timestamp);
    }
    msg_to = await Admin.getUserById(to);
    msg_from = await Admin.getUserById(userId);
    if (result !== false) {
      return { msg: 1 };
    }
  } catch (error) {
    console.error(error);
    reply.code(500).send({ error: "Internal Server Error" });
  }
};

const viewMessage = async (request, reply) => {
  const userId = request.body.userId;
  const lastUser = request.body.toId;
  let messages, userName, updateMsgResult;
  console.log("userId,lastUser", userId, lastUser);
  if (lastUser) {
    messages = await ClientChat.getLastUserPartnerMsg(userId, lastUser);
    console.log("messages", messages);
  }
  if (messages) {
    userName = await Team.getUserName(lastUser);
    const messageCount = messages.length;
    for (let i = 0; i < messageCount; i++) {
      const chatId = messages[i]["chat_id"];
      try {
        updateMsgResult = await ClientChat.updateMsg(chatId, userId);
        console.log(`Message updated for chat ID ${chatId}:`, updateMsgResult);
      } catch (error) {
        console.error(`Error updating message for chat ID ${chatId}:`, error);
      }
    }
    reply.send({
      status: 1,
      message: messages,
      userName: userName,
      updateMsgResult: updateMsgResult,
    });
  } else {
    reply.send({ status: 1, message: "Failed to fetch messages" });
  }
};
const manageWorkflows = async (request, reply) => {
  let client = {};
  let clientId = request.body.receivedId;
  let currentDate = Math.round(moment().valueOf() / 1000);
  client["workflows"] = request.body.workflowIds;
  let userId = request.body.userId;
  const updated = await Client.updateClient(client,clientId);
  console.log("manageWorkflows update", updated);
  if (updated) {
    client = await Client.getClientById(clientId);
    console.log("manageWorkflows team---", client);
    
    if (client.user_id) {
      // Notification
      let notificationMsg = `The workflows has been updated for the profile ${client.name}.`;
      let notificationUrl = `client/view/${clientId}`;

      // Alert to team member of workflow updation
      const incrementResult_team = await Admin.incrementField(
        client.user_id,
        "noti_count",
        1
      );
      console.log("incrementResult--to--team-----------", incrementResult_team);
      let notificationData_team = {
        user_id: client.user_id,
        url: "onboard/process",
        timestamp: currentDate,
        message: "New onboarding stages has assigned to you",
        status: 1,
        from_id: userId,
      };
      console.log("notificationData--team------------", notificationData_team);
      const addNotificationResult_team = await Admin.addNotification(
        notificationData_team
      );
      console.log(
        "addNotificationResult--team-------",
        addNotificationResult_team
      );

      // Alert to user of workflow updation
      const incrementResult_User = await Admin.incrementField(
        userId,
        "noti_count",
        1
      );
      console.log("incrementResult--to--user-----------", incrementResult_User);
      let notificationData_user = {
        user_id: userId,
        url: notificationUrl,
        timestamp: currentDate,
        message: notificationMsg,
        status: 1,
        from_id: userId,
      };
      console.log("notificationData--user------------", notificationData_user);
      const addNotificationResult_user = await Admin.addNotification(
        notificationData_user
      );
      console.log(
        "addNotificationResult--user-------",
        addNotificationResult_user
      );

      // Alert to all admins of product updation

      let adminIds = await Admin.getAllAdminIds();
      let notificationData = {};
      console.log("adminIds====", adminIds);
      for (const adminId of adminIds) {
        console.log("adminId----------", adminId);
        if (adminId != userId) {
          const incrementResult = await Admin.incrementField(
            adminId,
            "noti_count",
            1
          );
          console.log("incrementResult-------------", incrementResult);
          notificationData = {
            user_id: adminId,
            url: notificationUrl,
            timestamp: currentDate,
            message: notificationMsg,
            status: 1,
            from_id: userId,
          };
          console.log("notificationData------------", notificationData);
          const addNotificationResult = await Admin.addNotification(
            notificationData
          );
          console.log("addNotificationResult-------", addNotificationResult);
        }
      }
      reply.send({
        status: 1,
        message: `Workflows for ${client.name} has been updated successfully!`,
      });
    } else {
      reply.send({ status: 0, message: `Failed to update workflows!` });
    }
  }
  else{
    reply.send({ status: 0, message: `Failed to update workflows!` });
  }
};


//////////////////////////////////////////////////qb
const getAllCustomerDataFromQuickbookSandbox = async (searchTerm = '') => {
  console.log("searchTerm 2",searchTerm);
  try {
   let token = await getNewAccessToken()
    const apiUrl = PRODUCTION
    ? `https://quickbooks.api.intuit.com/v3/company/${companyId}`
    : `https://sandbox-quickbooks.api.intuit.com/v3/company/${companyId}`;

    const queryStr = (!searchTerm || !String(searchTerm).trim())
      ? 'SELECT DisplayName,Id FROM Customer'
      : `SELECT DisplayName,Id FROM Customer WHERE DisplayName LIKE '%${String(searchTerm).trim()}%'`;
    const response = await axios.get(
      `${apiUrl}/query?query=${encodeURIComponent(queryStr)}`,
      {
        headers: {
          'Authorization': `Bearer ${token}`,
          'Accept': 'application/json'
        }
      }
    );

    // console.log('All customer data retrieved:', response.data);
    return response.data?.QueryResponse?.Customer;
  } catch (error) {

    const CatchapiUrl = PRODUCTION
    ? `https://quickbooks.api.intuit.com/v3/company/${companyId}`
    : `https://sandbox-quickbooks.api.intuit.com/v3/company/${companyId}`;
    if (error.response && error.response.status === 401) {
      // Token expired, refresh the token and retry
      const newAccessToken = await getNewAccessToken();
      const response = await axios.get(
        `${CatchapiUrl}/query?query=SELECT DisplayName,Id FROM Customer`,
        {
          headers: {
            'Authorization': `Bearer ${newAccessToken}`,
            'Accept': 'application/json'
          }
        }
      );
      // console.log('All customer data retrieved after token refresh:', response.data);
      return response.data?.QueryResponse?.Customer;
    } else if (error.response && error.response.status === 403) {
      console.error('Permission denied:', error.response.data);
      throw new Error('Permission denied: ' + error.response.data);
    } else if (error.code === 'ENOTFOUND') {
      console.error('DNS lookup failed. Check your internet connection and DNS settings.');
      throw new Error('DNS lookup failed. Check your internet connection and DNS settings.');
    } else {
      console.error('Error retrieving customer data:', error.response ? error.response.data : error.message);
      throw error;
    }
  }
};

const getAllVendorDataFromQuickbookSandbox = async (searchTerm = '') => {
  try {

   let token = await getNewAccessToken()
    const apiUrl = PRODUCTION
    ? `https://quickbooks.api.intuit.com/v3/company/${companyId}`
    : `https://sandbox-quickbooks.api.intuit.com/v3/company/${companyId}`;

    const queryStr = (!searchTerm || !String(searchTerm).trim())
      ? 'SELECT DisplayName,Id FROM Vendor'
      : `SELECT DisplayName,Id FROM Vendor WHERE DisplayName LIKE '%${String(searchTerm).trim()}%'`;
    const response = await axios.get(
      `${apiUrl}/query?query=${encodeURIComponent(queryStr)}`,
      {
        headers: {
          'Authorization': `Bearer ${token}`,
          'Accept': 'application/json'
        }
      }
    );

    // console.log('All vendor data retrieved:', response.data);
    return response.data?.QueryResponse?.Vendor;
  } catch (error) {
    if (error.response && error.response.status === 401) {

      const CatchApiUrl = PRODUCTION
      ? `https://quickbooks.api.intuit.com/v3/company/${companyId}`
      : `https://sandbox-quickbooks.api.intuit.com/v3/company/${companyId}`;
  
      const newAccessToken = await getNewAccessToken();
      const response = await axios.get(
        `${CatchApiUrl}/query?query=SELECT DisplayName,Id FROM Vendor WHERE DisplayName LIKE '%${searchTerm}%'`,
        {
          headers: {
            'Authorization': `Bearer ${newAccessToken}`,
            'Accept': 'application/json'
          }
        }
      );
      console.log('All vendor data retrieved after token refresh:', response.data);
      return response.data?.QueryResponse?.Vendor;
    } else if (error.response && error.response.status === 403) {
      console.error('Permission denied:', error.response.data);
      throw new Error('Permission denied: ' + error.response.data);
    } else if (error.code === 'ENOTFOUND') {
      console.error('DNS lookup failed. Check your internet connection and DNS settings.');
      throw new Error('DNS lookup failed. Check your internet connection and DNS settings.');
    } else {
      console.error('Error retrieving vendor data:', error.response ? error.response.data : error.message);
      throw error;
    }
  }
};

///////////////////////////////////////////////////

const getAllCustomersFromQuickbook = async (req, reply) => {
  try {
    const searchTerm = req?.body?.search ?? req?.query?.search ?? '';
    const allCustomerData = await getAllCustomerDataFromQuickbookSandbox(searchTerm);
    reply.send({ result: Array.isArray(allCustomerData) ? allCustomerData : [], status: 1, message: "success" });
  } catch (error) {
    console.error('Error retrieving all customers:', error.message);
    reply.status(500).send({message:'Failed to retrieve all customer data',status:0});
  }
};



const getAllVendorsFromQuickbook = async (req, reply) => {
  try {
    const searchTerm = req?.body?.search ?? req?.query?.search ?? '';
    const allVendorData = await getAllVendorDataFromQuickbookSandbox(searchTerm);
    reply.send({ result: Array.isArray(allVendorData) ? allVendorData : [], status: 1, message: "success" });
  } catch (error) {
    console.error('Error retrieving all vendors:', error.message);
    reply.status(500).send({ message: 'Failed to retrieve all vendor data', status: 0 });
  }
};


const saveClientVendorOrder = async (req, res) => {
  try {
    const { reorderedItems } = req.body;
    console.log("updatedCardsupdatedCardsupdatedCards",reorderedItems)
    await Client.updateCardOrderClientVendor(reorderedItems);
    res.status(200).send({ status: 1, message: 'Order saved' });
  } catch (error) {
    console.log('saveReq error : ',error)
    res.status(500).send({ status: 500, message: 'Error saving order', error:error });
  }
}


const sendClientEmails = async (emailTasks, constantsAndUtils) => {
  const { portalName, BASE_URL, SENDGRID_API_KEY, sendPartnerMail, activateProfile, transporterNodeMailer, Admin, sgMail } = constantsAndUtils;

  for (const task of emailTasks) {
    if (task.type === 'invite') {
      const { clientData, token } = task; // token from OnboardLink

      sgMail.setApiKey(SENDGRID_API_KEY);
      let message = sendPartnerMail();
      message = message.replace('[emailContent]', clientData.email_content);
      message = message.replace('[NAME]', clientData.name);
      message = message.replace('[LOGIN_URL]', `${BASE_URL}/auth/access-invite-link/${token}`);

      const mailOptions = {
        to: clientData.email,
        from: '"SignWise Solutions" <noreply@signwisesolutions.com>',
        subject: clientData.subject,
        text: clientData.subject,
        html: message,
      };
      try {
        await transporterNodeMailer.sendMail(mailOptions);
        console.log('Invite Email sent to:', clientData.email);
      } catch (mailError) {
        console.error('Error sending invite email:', mailError);
      }
    } else if (task.type === 'welcome') {
      const { clientData, user, password } = task;

      // Send welcome email
      sgMail.setApiKey(SENDGRID_API_KEY);
      let message = activateProfile();
      message = message.replace('[emailContent]', `Thank you for creating your account. Your username (email address) and password is included for reference below.`);
      message = message.replace('[CONTENT]', `Hello ${user['company_name']},`);
      message = message.replace('[NAME]', user['username']);
      message = message.replace('[PASSWORD]', password);
      message = message.replace('[LOGIN_URL]', `${BASE_URL}`);

      const mailOptions = {
        to: clientData.email,
        from: '"SignWise Solutions" <noreply@signwisesolutions.com>',
        subject: `${portalName} - Your account has been activated`,
        text: `${portalName} - Your account has been activated`,
        html: message,
      };
      try {
        await transporterNodeMailer.sendMail(mailOptions);
        console.log('Welcome Email sent to:', clientData.email);
      } catch (mailError) {
        console.error('Error sending welcome email:', mailError);
      }

      // Send admin notification email
      let adminEmails = await Admin.getAllAdminEmails();
      let adminSubject = portalName + " - A new user has been registered";
      let adminWelcome = "Hello ! Admin" + ","
      let admin_email_content = `A new user has been registered`;
      let adminMessage = activateProfile()
      adminMessage = adminMessage.replace('[emailContent]', admin_email_content)
      adminMessage = adminMessage.replace('[CONTENT]', adminWelcome)
      adminMessage = adminMessage.replace('[NAME]', user['username'])
      adminMessage = adminMessage.replace('[PASSWORD]', password)
      adminMessage = adminMessage.replace('[LOGIN_URL]', `${BASE_URL}`)
      for (const adminRow of adminEmails) {
        const adminMailOptions = {
          to: adminRow,
          from: '"SignWise Solutions" <noreply@signwisesolutions.com>',
          subject: adminSubject,
          text: adminSubject,
          html: adminMessage,
        }
        try {
          await transporterNodeMailer.sendMail(adminMailOptions);
          console.log('Admin notification email sent to:', adminRow);
        } catch (adminMailError) {
          console.error('Error sending admin notification email:', adminMailError);
        }
      }
    }
  }
};

const uploadClients = async (request, reply) => {
  try {
    const { data = [], userId } = request.body;
    const successClients = [];
    const failedClients = [];
    const emailTasks = []; // To store email sending tasks

    
    const norm = (v) => (typeof v === "string" ? v.trim() : v);
    const toNumberOr = (v, fallback = 0) => {
      const n = Number(v);
      return Number.isFinite(n) ? n : fallback;
    };

    let inserted = 0;

    for (let i = 0; i < data.length; i++) {
      try {
        const row = data[i] || {};
        const name = norm(row.name);
        const email = norm(row.email);
        const phoneNumber = norm(row.phoneNumber);
        const address = norm(row.address);
        const city = norm(row.city);
        const zip_code = norm(row.zip_code);
        const stateName = norm(row.state);
        const partnerTypeName = norm(row.inviteAs);
        const customerTypeName = norm(row.paymentTerms);
        const roleName = norm(row.role);
        const avalaraTaxType = norm(row.avalaraTaxType) || "1"; // Default to Taxable Client
        const type = norm(row.type) || 'client'; // Default type to 'client'

        // These fields are not read from Excel directly in mass upload,
        // but derived or set with default values as per addClient controller logic.
        const isSyncWithQuickbooks = false; // Default to false for mass upload
        const quickbookCustomerName = name; // Default to client's name from Excel
        const isSendEmail = false; // Default to false for mass upload
        const isCustomMessage = false; // Default to false for mass upload
        const subject = 'New request added by ' + portalName; // Default subject
        const emailcontent = `Thank you for choosing ${portalName}!\n\nWe'd like to remind you to activate your profile and complete the signup process!\n\nWe look forward to serving you and providing an exceptional experience that exceeds your expectations, creating lasting moments of satisfaction and delight.`; // Default content
        const tags = norm(row.tags); // Re-added: Extract tags from the Excel row
        const workflows = norm(row.workflows); // Re-added: Extract workflows from the Excel row
        const salesRepNames = norm(row.salesRepNames);

        if (!name || !email || !phoneNumber) {
          failedClients.push({ row: i + 1, reason: "Name, Email, and Phone Number are required" });
          continue;
        }

        const partnerExists = await Client.getPartnerByEmail(email);
        if (partnerExists) {
          failedClients.push({ row: i + 1, reason: `Client with email ${email} already exists` });
          continue;
        }

        // Look up State ID
        let stateId = null;
        let stateShortName = null;
        if (stateName) {
          const state = await Client.getStateByName(stateName);
          if (state) {
            stateId = state.id;
            stateShortName = state.short_name;
          } else {
            failedClients.push({ row: i + 1, reason: `State "${stateName}" not found` });
            continue;
          }
        }

        // Look up Partner Type ID
        let partnerTypeId = null;
        if (partnerTypeName) {
          const partnerType = await Client.getPartnerTypeByName(partnerTypeName);
          if (partnerType) {
            partnerTypeId = partnerType.id;
          } else {
            failedClients.push({ row: i + 1, reason: `Partner Type "${partnerTypeName}" not found` });
            continue;
          }
        }

        // Look up Customer Type ID
        let customerTypeId = null;
        if (customerTypeName) {
          const customerType = await Client.getCustomerTypeByName(customerTypeName);
          if (customerType) {
            customerTypeId = customerType.id;
          } else {
            failedClients.push({ row: i + 1, reason: `Customer Type "${customerTypeName}" not found` });
            continue;
          }
        }

        // Look up User Role ID
        let userRoleId = null;
        if (roleName) {
          const userRole = await Admin.getUserRoleByName(roleName);
          if (userRole) {
            userRoleId = userRole;
          } else {
            failedClients.push({ row: i + 1, reason: `User Role "${roleName}" not found` });
            continue;
          }
        }

        // Look up Tag IDs (comma-separated in Excel)
        let parsedTagIds = null;
        if (tags) {
          const tagNames = tags.split(',').map(tag => norm(tag)).filter(Boolean);
          const foundTags = await Promise.all(tagNames.map(tagName => ContactTags.getTagByName(tagName))); // Using ContactTags model
          const validTagIds = foundTags.filter(tag => tag).map(tag => tag.id);
          if (validTagIds.length !== tagNames.length) {
            const missingTags = tagNames.filter(tagName => !foundTags.some(tag => tag && tag.name === tagName));
            failedClients.push({ row: i + 1, reason: `Tags not found: ${missingTags.join(', ')}` });
            continue;
          }
          parsedTagIds = validTagIds.join(',');
        }

        // Look up Workflow IDs (comma-separated in Excel)
        let parsedWorkflowIds = null;
        if (workflows) {
          const workflowNames = workflows.split(',').map(wf => norm(wf)).filter(Boolean);
          const foundWorkflows = await Promise.all(workflowNames.map(workflowName => Onboard.getWorkflowByName(workflowName))); // Using Onboard model
          const validWorkflowIds = foundWorkflows.filter(wf => wf).map(wf => wf.id);
          if (validWorkflowIds.length !== workflowNames.length) {
            const missingWorkflows = workflowNames.filter(workflowName => !foundWorkflows.some(wf => wf && wf.name === workflowName));
            failedClients.push({ row: i + 1, reason: `Workflows not found: ${missingWorkflows.join(', ')}` });
            continue;
          }
          parsedWorkflowIds = validWorkflowIds.join(',');
        }

        // Look up Sales Rep IDs (comma-separated in Excel)
        let parsedSalesRepIds = null;
        if (salesRepNames) {
          const salesRepNamesArray = salesRepNames.split(',').map(name => norm(name)).filter(Boolean);
          const foundSalesRepsWithNames = await Promise.all(salesRepNamesArray.map(async name => ({ name, id: await Client.getSalesRepByName(name) })));

          const validSalesRepIds = foundSalesRepsWithNames.filter(item => item.id).map(item => item.id);
          if (validSalesRepIds.length !== salesRepNamesArray.length) {
            const missingSalesReps = foundSalesRepsWithNames.filter(item => !item.id).map(item => item.name);
            failedClients.push({ row: i + 1, reason: `Sales Reps not found: ${missingSalesReps.join(', ')}` });
            continue;
          }
          parsedSalesRepIds = validSalesRepIds.join(',');
        }


        // QuickBooks Sync
        let syncWithQuickbook = 0;
        let partnerQbId = null;
        const apiUrl = PRODUCTION
          ? `https://quickbooks.api.intuit.com/v3/company/${companyId}`
          : `https://sandbox-quickbooks.api.intuit.com/v3/company/${companyId}`;

        if (isSyncWithQuickbooks) {
          try {
            const qbPayload = {
              "DisplayName": quickbookCustomerName || name,
              "CompanyName": name,
              "PrimaryEmailAddr": { "Address": email },
              "PrimaryPhone": { "FreeFormNumber": phoneNumber?.toString() }
            };
            partnerQbId = await addClientToQuickbook(type, apiUrl, qbPayload, qbPayload) || null;
            syncWithQuickbook = 1;
          } catch (error) {
            console.error("QuickBooks sync failed for client:", name, error.message);
            failedClients.push({ row: i + 1, reason: `QuickBooks sync failed: ${error.message}` });
            continue;
          }
        }

        const clientData = {
          name,
          email,
          phone: phoneNumber,
          phone_country_code: '', 
          isVendor: type === 'vendor' ? 1 : 0,
          workflows: parsedWorkflowIds,
          contact_tags: parsedTagIds,
          partner_type: partnerTypeId,
          subject: isCustomMessage ? subject : 'New request added by ' + portalName,
          email_content: isCustomMessage ? emailcontent : `Thank you for choosing ${portalName}!\n\nWe'd like to remind you to activate your profile and complete the signup process!\n\nWe look forward to serving you and providing an exceptional experience that exceeds your expectations, creating lasting moments of satisfaction and delight.`,
          added_by: userId,
          customerTypeId: customerTypeId,
          timestamp: moment().unix(),
          status: isSendEmail ? 0 : 1, // 0 for invite, 1 for active
          syncWithQuickbook: syncWithQuickbook,
          quickbookCustomerName: quickbookCustomerName || name,
          avalaraTaxType: Number(avalaraTaxType),
          partnerQbId: partnerQbId,
          sales_rep_names: parsedSalesRepIds,
          address: address,
          zip_code: zip_code,
          city: city,
          state_id: stateId,
          added_by: userId,
        };

        const result = await Client.addClient(clientData);
        if (result && result.id) {
          inserted++;
          successClients.push(name);

          // If email is to be sent (invite)
          if (isSendEmail) {
            let link = {};
            link['workflow_ids'] = parsedWorkflowIds;
            link['partner_type_id'] = partnerTypeId;
            link['token'] = crypto.randomBytes(20).toString("hex");
            while (await OnboardLink.isInviteLinkExist(link['token'])) {
              link['token'] = crypto.randomBytes(8).toString("hex");
            }
            link['partner_id'] = result.id;
            link['is_public'] = 0;
            link['timestamp'] = moment().unix();
            link['added_by'] = userId;
            link['status'] = 1;
            let linkId = await OnboardLink.addInviteLink(link);

            if (linkId) {
              emailTasks.push({
                type: 'invite',
                clientData: clientData,
                token: link['token'],
              });
            }
          } else { // No email invite, create user directly and send activation email
            let user = {};
            user['first_name'] = clientData.name;
            user['company_name'] = clientData.name;
            user['email'] = clientData.email;
            user['username'] = user['email'];
            let password = crypto.randomBytes(5).toString("hex");
            let hashedPassword = Password.hash(password);
            user['password'] = hashedPassword;
            user['admin_password'] = password;
            user['profile_pic'] = 'avatar.jpg';
            user['status'] = 1;
            user['user_type'] = userRoleId ? userRoleId : 3;
            user['activated_by_admin'] = 1;

            let newUserId = await Admin.addUser(user);

            if (newUserId) {
              let partner = {};
              partner['user_id'] = newUserId;
              partner['status'] = 1;
              await Client.updateClient(partner, result.id);

              // Avalara Customer Creation
              const avalaraData = {
                customerCode: String(newUserId),
                name: clientData.name,
                address: clientData.address,
                city: clientData.city,
                state: stateShortName,
                zip: clientData.zip_code,
                country: 'US',
                email: clientData.email,
                phone: clientData.phone,
                taxExemptionReasonId: avalaraTaxType == "1" ? null : 1,
                entityUseCode: avalaraTaxType == "1" ? null : "A"
              };
              const avalaraResponse = await createAvalaraCustomer(avalaraData);
              if (avalaraResponse && avalaraResponse.id) {
                await Client.updateClient({ avalaraCustomerId: avalaraResponse.id }, result.id);
              }

              emailTasks.push({
                type: 'welcome',
                clientData: clientData,
                user: user,
                password: password,
              });
            }
          }
        } else {
          failedClients.push({ row: i + 1, reason: `Failed to add client ${name} to database` });
        }
      } catch (rowErr) {
        console.error("[ROW ERROR]", rowErr?.message || rowErr);
        failedClients.push({ row: i + 1, reason: `Error processing row: ${rowErr?.message || "unknown"}` });
        continue;
      }
    }

    reply.send({
      status: inserted > 0 ? 1 : 0,
      message:
        inserted > 0
          ? `${inserted} clients uploaded successfully, ${failedClients.length} failed`
          : "Failed to upload clients.",
      successClients,
      failedClients,
    });

    if (emailTasks.length > 0) {
      const constantsAndUtils = {
        portalName,
        BASE_URL,
        SENDGRID_API_KEY,
        sendPartnerMail,
        activateProfile,
        transporterNodeMailer,
        Admin,
        sgMail,
        // Add other necessary constants/utils here
      };
      sendClientEmails(emailTasks, constantsAndUtils);
    }

  } catch (error) {
    console.error("###############Error in uploadClients:", error);
    reply.send({
      status: 0,
      message: "Failed to upload clients",
      error: error.message,
      failedClients: [],
    });
  }
};

const getAllPartnerQb = async (req, reply) => {
  try {
    const isVendor = req.body.isVendor;
    const allPartnerQb = await Client.getAllPartnerQbIds(isVendor);
    reply.send({ result: Array.isArray(allPartnerQb) ? allPartnerQb : [], status: 1, message: "success" });
  } catch (error) {
    console.error('Error retrieving all partner Qb:', error.message);
    reply.status(500).send({ message: 'Failed to retrieve all partner Qb', status: 0 });
  }
};
module.exports = {
  clientInvite,
  clientDelete,
  clientEdit,
  clientSignup,
  clientLinkRedirect,
  getAllClients,
  getDataForAddClient,
  addClient,
  fetchClientById,
  updateClient,
  removeClient,
  addInternalNotes,
  sendMessage,
  viewMessage,
  viewClientDetails,
  manageWorkflows,
  updateProfilePic,
  updateCoverImage,
  getAllCustomersFromQuickbook,
  getAllVendorsFromQuickbook,
  saveClientVendorOrder,
  getAllStatesController,
  uploadClients,
  getAllPartnerQb,
};
