/* eslint-disable @typescript-eslint/no-explicit-any */
import axios, { AxiosError } from 'axios';
import { Types } from 'mongoose';

import config from '@/shared/config/config';
import { Company } from '@/modules/company/company.model';
import User from '@/modules/user/user.model';
import {
  MyOperatorApiResponse,
  MyOperatorUser,
  CreateMyOperatorUserPayload,
  UpdateMyOperatorUserPayload,
  ListUsersOptions,
  SearchCallLogsOptions,
  MyOperatorCallLog,
  MyOperatorFilter,
  SyncUserOptions,
  SyncUserResult,
  RecordingLinkResponse,
  SaveCredentialsPayload,
  InitiateClickToCallRequest,
  InitiateClickToCallResponse,
  GetCallStatusOptions,
  GetCallStatusResponse,
  MyOperatorOBDApiResponse,
  MyOperatorUserCreateResponse,
} from './myoperator.interface';
import {
  findNextAvailableExtension,
  mapUserToMyOperatorPayload,
  getRecordingLinkExpiry,
} from './myoperator.helper';
import { ICompanyDoc } from '@/modules/company/company.interface';
import { IUserDoc } from '@/modules/user/user.interfaces';
import { sendEmailWithActiveTemplate } from '@/modules/communication/email/email.helper';
import { TemplateName, SOCKET_EVENTS } from '@/shared/constants';
import { emitToUser, getIO } from '@/shared/socket/socket.service';
import { Contact } from '../contacts/contacts.model';
import { Activity } from '@/modules/activity/activity.model';
import { getObjectId } from '@/shared/utils/commonHelper';

class MyOperatorService {
  /**
   * Get company's MyOperator API token
   */
  private async getCompanyToken(
    companyId: string | Types.ObjectId,
  ): Promise<string | null> {
    const company = (await Company.findById(companyId).select(
      'myoperator',
    )) as ICompanyDoc | null;

    if (!company?.myoperator?.apiToken || !company.myoperator.isActive) {
      return null;
    }

    return company.myoperator.apiToken;
  }

  /**
   * Get company's MyOperator credentials
   */
  private async getCompanyCredentials(
    companyId: string | Types.ObjectId,
  ): Promise<{
    apiToken: string;
    secretKey?: string;
    xApiKey?: string;
    companyId?: string;
    publicIvrId?: string;
  } | null> {
    const company = (await Company.findById(companyId).select(
      'myoperator',
    )) as ICompanyDoc | null;

    if (!company?.myoperator?.apiToken || !company.myoperator.isActive) {
      return null;
    }

    return {
      apiToken: company.myoperator.apiToken,
      secretKey: company.myoperator.secretKey,
      xApiKey: company.myoperator.xApiKey,
      companyId: company.myoperator.companyId,
      publicIvrId: company.myoperator.publicIvrId,
    };
  }

  /**
   * Make request to MyOperator API
   */
  private async makeRequest<T = any>(
    companyId: string | Types.ObjectId,
    endpoint: string,
    method: 'GET' | 'POST' | 'PUT' | 'DELETE',
    noHeaders: boolean = true,
    data?: any,
  ): Promise<MyOperatorApiResponse<T>> {
    const credentials = await this.getCompanyCredentials(companyId);
    if (!credentials) {
      throw new Error('MyOperator not configured or inactive for this company');
    }

    const url = `${config.myoperator.apiUrl}${endpoint}`;

    try {
      // Build headers
      const headers: any = {};
      if (!noHeaders) {
        if (credentials.xApiKey) {
          headers['x-api-key'] = credentials.xApiKey;
        }
        if (credentials.secretKey) {
          headers['Authorization'] = `Bearer ${credentials.secretKey}`;
        }
      }

      const requestConfig: any = {
        method,
        url,
        headers,
        timeout: 15000,
      };
      if (method === 'DELETE') {
        console.log('DELETE request sent to MyOperator');
      } else if (method === 'GET') {
        requestConfig.params = { token: credentials.apiToken, ...data };
      } else {
        requestConfig.data = { token: credentials.apiToken, ...data };
      }

      const response = await axios(requestConfig);

      if (response.data?.status === 'error') {
        throw new Error(
          response.data.message || 'MyOperator API returned an error',
        );
      }

      return response.data;
    } catch (error) {
      if (axios.isAxiosError(error)) {
        const axiosError = error as AxiosError<MyOperatorApiResponse>;
        if (axiosError.response?.data?.message) {
          throw new Error(axiosError.response.data.message);
        }
        if (axiosError.code === 'ETIMEDOUT') {
          throw new Error('MyOperator API request timed out');
        }
      }
      throw error;
    }
  }

  /**
   * Create a user in MyOperator
   */
  async createUser(
    companyId: string | Types.ObjectId,
    payload: CreateMyOperatorUserPayload,
  ): Promise<MyOperatorUserCreateResponse> {
    const response = await this.makeRequest<MyOperatorUserCreateResponse>(
      companyId,
      '/user',
      'POST',
      true,
      payload,
    );

    if (response.status !== 'success') {
      throw new Error(response.message || 'Failed to create user in MyOperator');
    }

    return response as MyOperatorUserCreateResponse;
  }

  /**
   * Update a user in MyOperator
   */
  async updateUser(
    companyId: string | Types.ObjectId,
    payload: UpdateMyOperatorUserPayload,
  ): Promise<boolean> {
    const response = await this.makeRequest(
      companyId,
      '/user',
      'PUT',
      true,
      payload,
    );

    return response.status === 'success';
  }

  async deleteUser(
    companyId: string | Types.ObjectId,
    uuid: string,
  ): Promise<{ success: boolean; uuid: string }> {
    const token = await this.getCompanyToken(companyId);
    if (!token) {
      throw new Error('MyOperator not configured or inactive for this company');
    }
    const endpoint = `/user/token/${encodeURIComponent(token)}/uuid/${encodeURIComponent(uuid)}`;
    const response = await this.makeRequest(
      companyId,
      endpoint,
      'DELETE',
    );
    return {
      success: response.status === 'success',
      uuid: response.uuid ?? uuid,
    };
  }

  /**
   * List users from MyOperator
   */
  async listUsers(
    companyId: string | Types.ObjectId,
    options: ListUsersOptions = {},
  ): Promise<MyOperatorUser[]> {
    const params: any = {};

    if (options.keyword) params.keyword = options.keyword;
    if (options.page) params.page = options.page;
    if (options.pageSize) params['page-size'] = options.pageSize;
    if (options.all) params._all = 1;

    const response = await this.makeRequest<MyOperatorUser[]>(
      companyId,
      '/user',
      'GET',
      params,
    );

    return response.data || [];
  }

  /**
   * Search call logs from MyOperator
   */
  async searchCallLogs(
    companyId: string | Types.ObjectId,
    options: SearchCallLogsOptions = {},
  ): Promise<MyOperatorCallLog[]> {
    const payload: any = {};

    if (options.from) payload.from = options.from;
    if (options.to) payload.to = options.to;
    if (options.logFrom) payload.log_from = options.logFrom;
    if (options.pageSize) payload.page_size = Math.min(options.pageSize, 100);
    if (options.searchKey) payload.search_key = options.searchKey;
    if (options.filters) payload.filters = options.filters;

    const response = await this.makeRequest<MyOperatorCallLog[]>(
      companyId,
      '/search',
      'POST',
      payload,
    );

    return response.data || [];
  }

  /**
   * Get available log filters
   */
  async getLogFilters(
    companyId: string | Types.ObjectId,
  ): Promise<MyOperatorFilter[]> {
    const response = await this.makeRequest<MyOperatorFilter[]>(
      companyId,
      '/filters',
      'GET',
    );

    return response.data || [];
  }

  async getRecordingLink(
    companyId: string | Types.ObjectId,
    filename: string,
  ): Promise<RecordingLinkResponse> {
    const response = await this.makeRequest<{ url: string, status: string, code: number }>(
      companyId,
      '/recordings/link',
      'GET',
      true,
      { file: filename },
    );

    const url = response.url
    if (!url) {
      throw new Error('Recording link not available');
    }

    return {
      link: url,
      expiresAt: getRecordingLinkExpiry(),
    };
  }

  /**
   * Sync a CRM user to MyOperator
   */
  async syncUser(options: SyncUserOptions): Promise<SyncUserResult> {
    try {
      const { userId, companyId, extension } = options;

      // Fetch user from database
      const user = (await User.findById(userId).select(
        'firstName lastName email phone myoperator',
      )) as IUserDoc | null;

      if (!user) {
        return {
          success: false,
          error: 'User not found',
        };
      }

      // Validate phone number
      if (!user.phone?.number) {
        return {
          success: false,
          error: 'User does not have a phone number',
        };
      }

      // Check if user is already synced
      if (user.myoperator?.uuid && user.myoperator?.syncStatus === 'synced') {
        // Update existing user
        const payload: UpdateMyOperatorUserPayload = {
          uuid: user.myoperator.uuid,
          name: `${user.firstName} ${user.lastName}`.trim(),
          email: user.email,
        };

        await this.updateUser(companyId, payload);

        return {
          success: true,
          uuid: user.myoperator.uuid,
          extension: user.myoperator.extension,
          message: 'User updated in MyOperator',
        };
      }

      // Determine extension number
      let assignedExtension = extension;

      if (!assignedExtension) {
        // Auto-assign extension
        const existingUsers = await this.listUsers(companyId, { all: true });
        assignedExtension = findNextAvailableExtension(existingUsers);
      }

      // Create user in MyOperator
      const myOperatorPayload = mapUserToMyOperatorPayload(
        user,
        assignedExtension,
      );

      try {
        const createdUser = await this.createUser(companyId, myOperatorPayload);

        // Update user in database
        await User.findByIdAndUpdate(userId, {
          'myoperator.uuid': createdUser.uuid,
          'myoperator.extension': assignedExtension,
          'myoperator.syncedAt': new Date(),
          'myoperator.syncStatus': 'synced',
        });

        return {
          success: true,
          uuid: createdUser.uuid,
          extension: assignedExtension,
          message: 'User synced successfully',
        };
      } catch (createError: any) {
        // Check if error is due to number already being linked
        if (createError.message && createError.message.toLowerCase().includes('already linked')) {
          console.log('User already exists in MyOperator, attempting to sync from MyOperator...');
          
          // Try to find the user in MyOperator by phone number
          const myOperatorUsers = await this.listUsers(companyId, { all: true });
          const phoneNumber = user.phone.number.toString();
          
          // Find matching user in MyOperator
          const matchingUser = myOperatorUsers.find(moUser => {
            const moPhoneNumber = moUser.contact_number.replace(/^\+?91/, '').trim();
            return moPhoneNumber === phoneNumber;
          });

          if (matchingUser) {
            // Update user in database with MyOperator details
            await User.findByIdAndUpdate(userId, {
              'myoperator.uuid': matchingUser.uuid,
              'myoperator.extension': matchingUser.extension,
              'myoperator.syncedAt': new Date(),
              'myoperator.syncStatus': 'synced',
            });

            return {
              success: true,
              uuid: matchingUser.uuid,
              extension: matchingUser.extension,
              message: 'User already exists in MyOperator. Synced details from MyOperator to database.',
            };
          } else {
            return {
              success: false,
              error: 'User exists in MyOperator but could not be found. Please contact support.',
            };
          }
        }
        
        // Re-throw other errors
        throw createError;
      }
    } catch (error: any) {
      console.error('Error syncing user to MyOperator:', error);

      // Update sync status to failed
      await User.findByIdAndUpdate(options.userId, {
        'myoperator.syncStatus': 'failed',
      });

      return {
        success: false,
        error: error.message || 'Failed to sync user',
      };
    }
  }

  /**
   * Check if MyOperator is configured and active for a company
   */
  async isConfigured(
    companyId: string | Types.ObjectId,
  ): Promise<{ configured: boolean; active: boolean }> {
    const company = (await Company.findById(companyId).select(
      'myoperator',
    )) as ICompanyDoc | null;

    return {
      configured: Boolean(company?.myoperator?.apiToken),
      active: Boolean(company?.myoperator?.isActive),
    };
  }

  /**
   * Test connection to MyOperator
   */
  async testConnection(companyId: string | Types.ObjectId): Promise<{
    success: boolean;
    status: 'connected' | 'disconnected' | 'error';
    message?: string;
  }> {
    try {
      // Try to fetch users to test connection
      await this.listUsers(companyId, { pageSize: 1 });

      // Update connection status
      await Company.findByIdAndUpdate(companyId, {
        'myoperator.connectionStatus': 'connected',
        'myoperator.lastConnectionTest': new Date(),
      });

      return {
        success: true,
        status: 'connected',
        message: 'Successfully connected to MyOperator',
      };
    } catch (error: any) {
      // Update connection status to error
      await Company.findByIdAndUpdate(companyId, {
        'myoperator.connectionStatus': 'error',
        'myoperator.lastConnectionTest': new Date(),
      });

      return {
        success: false,
        status: 'error',
        message: error.message || 'Failed to connect to MyOperator',
      };
    }
  }

  /**
   * Save MyOperator credentials for a company
   */
  async saveCredentials(
    companyId: string | Types.ObjectId,
    payload: SaveCredentialsPayload,
  ): Promise<{
    success: boolean;
    connectionStatus: 'connected' | 'disconnected' | 'error' | 'pending';
    message: string;
  }> {
    const updateData: any = {
      'myoperator.apiToken': payload.apiToken,
      'myoperator.isActive': payload.isActive ?? true,
      'myoperator.lastSyncedAt': new Date(),
      'myoperator.connectionStatus': 'pending',
    };

    if (payload.secretKey) {
      updateData['myoperator.secretKey'] = payload.secretKey;
    }
    if (payload.xApiKey) {
      updateData['myoperator.xApiKey'] = payload.xApiKey;
    }
    if (payload.companyId) {
      updateData['myoperator.companyId'] = payload.companyId;
    }
    if (payload.webhookSecret) {
      updateData['myoperator.webhookSecret'] = payload.webhookSecret;
    }
    if (payload.publicIvrId) {
      updateData['myoperator.publicIvrId'] = payload.publicIvrId;
    }

    await Company.findByIdAndUpdate(companyId, updateData);

    // Test connection after saving
    const connectionTest = await this.testConnection(companyId);

    if (connectionTest.success) {
      await this.syncUsersFromMyOperator(companyId);
    }

    return {
      success: connectionTest.success,
      connectionStatus: connectionTest.status,
      message: connectionTest.message || 'Credentials saved',
    };
  }

  /**
   * Sync users from MyOperator to the database
   */
  private async syncUsersFromMyOperator(
    companyId: string | Types.ObjectId,
  ): Promise<void> {
    try {
      // Fetch all users from MyOperator
      const myOperatorUsers = await this.listUsers(companyId, { all: true });

      if (!myOperatorUsers || myOperatorUsers.length === 0) {
        console.log('No users found in MyOperator');
        return;
      }

      console.log(`Found ${myOperatorUsers.length} users in MyOperator`);

      // Fetch all company users once to avoid N+1 queries
      const companyUsers = await User.find({
        'company.id': companyId,
        isDeleted: false,
      }).select('_id phone');

      // Create a map of phone numbers to user IDs for efficient lookup
      const phoneToUserMap = new Map<string, Types.ObjectId>();
      for (const user of companyUsers) {
        if (user.phone?.number) {
          phoneToUserMap.set(user.phone.number.toString(), user._id);
        }
      }

      // Process each MyOperator user
      for (const myOperatorUser of myOperatorUsers) {
        try {
          const phoneNumber = myOperatorUser.contact_number.replace(/^\+?91/, '').trim();

          if (!phoneNumber || phoneNumber.length < 10) {
            console.log(`Invalid phone number for user: ${myOperatorUser.name}`);
            continue;
          }

          const userId = phoneToUserMap.get(phoneNumber);

          if (userId) {
            // Update existing user with MyOperator details
            await User.findByIdAndUpdate(userId, {
              'myoperator.uuid': myOperatorUser.uuid,
              'myoperator.extension': myOperatorUser.extension,
              'myoperator.syncedAt': new Date(),
              'myoperator.syncStatus': 'synced',
            });

            console.log(
              `Synced user: ${myOperatorUser.name} (${myOperatorUser.uuid})`,
            );
          } else {
            console.log(
              `No matching user found for MyOperator user: ${myOperatorUser.name} (${phoneNumber})`,
            );
          }
        } catch (userError) {
          console.error(
            `Error syncing user ${myOperatorUser.name}:`,
            userError,
          );
        }
      }

      console.log('User sync from MyOperator completed');
    } catch (error) {
      console.error('Error syncing users from MyOperator:', error);
      // Don't throw error - allow credentials to be saved even if sync fails
    }
  }

  async initiateClickToCall(
    companyId: string | Types.ObjectId,
    options: InitiateClickToCallRequest,
  ): Promise<InitiateClickToCallResponse> {
    const credentials = await this.getCompanyCredentials(companyId);
    if (!credentials) {
      throw new Error('MyOperator not configured or inactive for this company');
    }

    if (!credentials.companyId) {
      throw new Error('MyOperator company ID not configured');
    }

    if (!credentials.secretKey) {
      throw new Error('MyOperator secret key not configured');
    }

    if (!credentials.publicIvrId) {
      throw new Error('Public IVR ID not configured');
    }

    if (!credentials.xApiKey) {
      throw new Error('MyOperator X-API-Key not configured');
    }

    const contact = await Contact.findById(options.contactId).select('phone');

    if (!contact) {
      throw new Error('Contact not found');
    }

    if (!contact.phone || contact.phone.length === 0) {
      throw new Error('Contact does not have a phone number');
    }

    const primaryPhone =
      contact.phone.find(p => p.isPrimary) || contact.phone[0];

    if (!primaryPhone || !primaryPhone.number) {
      throw new Error('Valid phone number not found');
    }

    const formattedPhone = `+${primaryPhone.countryCode}${primaryPhone.number}`;
    console.log(`📞 Calling contact ${options.contactId} at ${formattedPhone}`);

    // Get the support user's MyOperator UUID
    const supportUser = (await User.findById(options.userId).select(
      'myoperator',
    )) as IUserDoc | null;

    if (!supportUser?.myoperator?.uuid) {
      throw new Error('Support user is not synced with MyOperator');
    }

    const existingCall = await Activity.findOne({
      company: getObjectId(companyId.toString()),
      type: 'call',
      status: 'pending',
      assignedTo: getObjectId(options.userId.toString()),
      myoperatorReferenceId: { $exists: true, $nin: [null, ''] },
    });
    if (existingCall) {
      throw new Error('User already has an ongoing call. Please end the current call before starting another.');
    }

    let referenceId = Math.random().toString(36).substring(2, 8).toUpperCase();

    if (options.leadId) {
      referenceId += `_LEAD_${options.leadId}`;
    }

    if (options.contactId) {
      referenceId += `_CONTACT_${options.contactId}`;
    }

    console.log(`📞 Generated reference_id with metadata: ${referenceId}`)

    const payload = {
      company_id: credentials.companyId,
      secret_token: credentials.secretKey,
      type: '1',
      user_id: supportUser.myoperator.uuid,
      number: formattedPhone,
      public_ivr_id: credentials.publicIvrId,
      reference_id: referenceId,
    };

    try {

      const url = config.myoperator.obdApiUrl;

      const response = await axios.post<MyOperatorOBDApiResponse>(
        url,
        payload,
        {
          headers: {
            'content-type': 'application/json',
            'x-api-key': credentials.xApiKey,
          },
          timeout: 15000,
        },
      );

      const responseData = response.data;

      if (responseData.status === 'error') {
        const errorMsg = responseData.details || responseData.message || 'Failed to initiate call';
        throw new Error(errorMsg);
      }

      await Activity.create({
        company: getObjectId(companyId.toString()),
        contact: options.contactId || undefined,
        lead: options.leadId || undefined,
        assignedTo: options.userId,
        createdBy: options.userId,
        updatedBy: options.userId,
        type: 'call',
        status: 'pending',
        title: 'Outgoing call (MyOperator)',
        comment: 'Call initiated',
        myoperatorReferenceId: responseData.reference_id || referenceId,
      });

      if (getIO()) {
        emitToUser(options.userId.toString(), SOCKET_EVENTS.CALL_START, {
          companyId: companyId.toString(),
          userId: options.userId.toString(),
          referenceId: responseData.reference_id || referenceId,
          ...(options.contactId && { contactId: options.contactId.toString() }),
          ...(options.leadId && { leadId: options.leadId.toString() }),
          uniqueId: responseData.unique_id,
        });
      }

      return {
        success: true,
        message: responseData.details || 'Call initiated successfully',
        uniqueId: responseData.unique_id,
        referenceId: responseData.reference_id,
      };
    } catch (error: any) {
      if (axios.isAxiosError(error)) {
        const axiosError = error as AxiosError<MyOperatorOBDApiResponse>;

        if (axiosError.response?.data) {
          const errorData = axiosError.response.data;

          if (errorData.type === 'INVALID_API_KEY') {
            throw new Error('Invalid MyOperator API key. Please check your credentials.');
          }

          const errorMsg = errorData.details || errorData.message || 'Failed to initiate call';
          throw new Error(errorMsg);
        }

        if (axiosError.code === 'ETIMEDOUT') {
          throw new Error('MyOperator API request timed out');
        }

        throw new Error(axiosError.message || 'Failed to connect to MyOperator API');
      }
      throw error;
    }
  }

  async getCallStatus(
    companyId: string | Types.ObjectId,
    options: GetCallStatusOptions,
  ): Promise<GetCallStatusResponse> {
    const filter: Record<string, unknown> = {
      company: companyId,
      type: 'call',
      status: 'pending',
      assignedTo: options.userId,
      myoperatorReferenceId: { $exists: true, $nin: [null, ''] },
    };

    const activity = await Activity.findOne(filter)
      .sort({ createdAt: -1 })
      .lean();

    if (!activity) {
      return { ongoing: false };
    }

    const a = activity as {
      assignedTo?: Types.ObjectId;
      contact?: Types.ObjectId;
      lead?: Types.ObjectId;
      myoperatorReferenceId?: string;
      createdAt: Date;
    };
    return {
      ongoing: true,
      activeCall: {
        userId: a.assignedTo ? String(a.assignedTo) : '',
        contactId: a.contact ? String(a.contact) : undefined,
        leadId: a.lead ? String(a.lead) : undefined,
        referenceId: a.myoperatorReferenceId || '',
        startedAt: a.createdAt.toISOString(),
      },
    };
  }

  /**
   * Deactivate MyOperator integration for a company
   */
  async deactivate(companyId: string | Types.ObjectId): Promise<boolean> {
    await Company.findByIdAndUpdate(companyId, {
      'myoperator.isActive': false,
    });

    return true;
  }

  /**
   * Submit feature request for MyOperator
   */
  async submitFeatureRequest(
    companyId: string | Types.ObjectId,
    payload: {
      firstName: string;
      lastName: string;
      email: string;
      phone: {
        dialCode: number;
        number: number;
      };
      companyName: string;
    },
  ): Promise<{ status: string; message: string }> {
    // Check if company already has MyOperator configured
    const company = (await Company.findById(companyId).select(
      'myoperator',
    )) as ICompanyDoc | null;

    if (!company) {
      throw new Error('Company not found');
    }

    if (company.myoperator?.isActive) {
      throw new Error('MyOperator is already configured for your company');
    }

    // Check if company already has a pending request
    if (company.myoperator?.connectionStatus === 'requested') {
      throw new Error(
        'You already have a pending request. Please wait for admin to process it.',
      );
    }

    // Update company status to 'requested'
    await Company.findByIdAndUpdate(companyId, {
      'myoperator.connectionStatus': 'requested',
    });

    // Send email to admin
    await this.sendRequestEmailToAdmin(companyId, payload);

    return {
      status: 'requested',
      message: 'Request submitted successfully',
    };
  }

  /**
   * Send request email to admin
   */
  private async sendRequestEmailToAdmin(
    companyId: string | Types.ObjectId,
    payload: {
      firstName: string;
      lastName: string;
      email: string;
      phone: {
        dialCode: number;
        number: number;
      };
      companyName: string;
    },
  ): Promise<void> {
    try {
      await sendEmailWithActiveTemplate({
        to: config.adminEmail,
        companyId: companyId,
        scenario: TemplateName.MyOperatorRequestAdmin,
        templateParams: {
          companyName: payload.companyName,
          contactPerson: `${payload.firstName} ${payload.lastName}`,
          email: payload.email,
          phone: `+${payload.phone.dialCode} ${payload.phone.number}`,
          companyId: companyId.toString(),
          requestedAt: new Date().toLocaleString(),
        },
      });
    } catch (error) {
      console.error('Error sending admin email:', error);
    }
  }
}

export const myOperatorService = new MyOperatorService();
