/**
 * Zoom Sync Service
 * Orchestrates Zoom Phone + Meetings → PostgreSQL sync
 * Cross-references calls/meetings with Autotask contacts and companies
 */

import { getZoomClient } from './zoom-factory';
import { postgresClient } from './postgres-client';

const CALL_WINDOW_DAYS = 90;
const MEETING_WINDOW_DAYS = 90;
const PARTICIPANT_WINDOW_DAYS = 30; // Zoom API limitation

/**
 * Strip all non-digit characters and return last 10 digits.
 * Handles country code variations (e.g. +1 prefix).
 */
function normalizePhone(phone: string | null | undefined): string {
  if (!phone) return '';
  const digits = phone.replace(/\D/g, '');
  return digits.slice(-10);
}

function toIsoDate(d: Date): string {
  return d.toISOString().split('T')[0];
}

export class ZoomSyncService {
  private syncInProgress = false;

  isSyncInProgress(): boolean {
    return this.syncInProgress;
  }

  async sync(): Promise<{
    usersUpserted: number;
    callsUpserted: number;
    callsMatched: number;
    meetingsUpserted: number;
    participantsUpserted: number;
  }> {
    if (this.syncInProgress) {
      throw new Error('Zoom sync already in progress');
    }

    this.syncInProgress = true;
    const startTime = Date.now();
    console.log('[ZOOM-SYNC] Starting sync...');

    try {
      const client = getZoomClient();

      // ─── Step 1: Sync Zoom users ───────────────────────────────────────
      console.log('[ZOOM-SYNC] Fetching Zoom users...');
      const zoomUsers = await client.getUsers();

      // Load set of resource emails from DB (for filtering)
      const resourceEmailsResult = await postgresClient.query(
        `SELECT LOWER(email) as email FROM resources
         WHERE (is_deleted = false OR is_deleted IS NULL) AND email IS NOT NULL`
      );
      const resourceEmails = new Set<string>(
        resourceEmailsResult.rows.map((r: { email: string }) => r.email)
      );

      let usersUpserted = 0;
      const activeZoomUsers = zoomUsers.filter(
        u => u.email && resourceEmails.has(u.email.toLowerCase())
      );

      for (const user of activeZoomUsers) {
        await postgresClient.query(
          `INSERT INTO zoom_users (zoom_id, email, display_name, is_active, synced_at)
           VALUES ($1, $2, $3, true, NOW())
           ON CONFLICT (zoom_id) DO UPDATE SET
             email = EXCLUDED.email,
             display_name = EXCLUDED.display_name,
             is_active = true,
             synced_at = NOW()`,
          [user.id, user.email.toLowerCase(), user.display_name]
        );
        usersUpserted++;
      }
      console.log(`[ZOOM-SYNC] Upserted ${usersUpserted} Zoom users (filtered to resource emails)`);

      // ─── Step 2: Build phone match index from Autotask contacts ───────
      console.log('[ZOOM-SYNC] Building phone index from Autotask contacts...');

      const contactsResult = await postgresClient.query(
        `SELECT id, company_id, phone, mobile_phone, alternate_phone
         FROM contacts
         WHERE (is_deleted = false OR is_deleted IS NULL)`
      );

      // Map: normalized 10-digit phone → { contactId, companyId }
      const phoneIndex = new Map<string, { contactId: number; companyId: number | null }>();
      for (const row of contactsResult.rows) {
        for (const field of ['phone', 'mobile_phone', 'alternate_phone'] as const) {
          const norm = normalizePhone(row[field]);
          if (norm && !phoneIndex.has(norm)) {
            phoneIndex.set(norm, { contactId: row.id, companyId: row.company_id });
          }
        }
      }

      // Also index company phones → companyId (fallback when no contact match)
      const companiesResult = await postgresClient.query(
        `SELECT id, phone FROM companies WHERE phone IS NOT NULL`
      );
      const companyPhoneIndex = new Map<string, number>();
      for (const row of companiesResult.rows) {
        const norm = normalizePhone(row.phone);
        if (norm && !companyPhoneIndex.has(norm)) {
          companyPhoneIndex.set(norm, row.id);
        }
      }

      // Internal phone set — to skip contact matching for internal calls
      const internalNumbers = new Set<string>();

      // ─── Step 3: Sync call logs (90-day window) ────────────────────────
      const callFrom = toIsoDate(new Date(Date.now() - CALL_WINDOW_DAYS * 24 * 60 * 60 * 1000));
      const callTo = toIsoDate(new Date());

      let callsUpserted = 0;
      let callsMatched = 0;

      for (const user of activeZoomUsers) {
        console.log(`[ZOOM-SYNC] Fetching call logs for ${user.email}...`);
        const calls = await client.getUserCallLogs(user.id, callFrom, callTo);

        for (const call of calls) {
          // Determine direction and the other-party number
          const direction = call.direction;
          let otherPartyNumber: string;
          let otherPartyName: string;

          if (direction === 'outbound') {
            otherPartyNumber = call.callee_number;
            otherPartyName = call.callee_name || '';
          } else if (direction === 'inbound') {
            otherPartyNumber = call.caller_number;
            otherPartyName = call.caller_name || '';
          } else {
            // internal — record but skip contact matching
            otherPartyNumber = call.caller_number || call.callee_number || '';
            otherPartyName = call.caller_name || call.callee_name || '';
            internalNumbers.add(normalizePhone(otherPartyNumber));
          }

          const callId = call.id || call.call_id;
          const normOther = normalizePhone(otherPartyNumber);

          // Map call result → status
          const result = (call.result || '').toLowerCase();
          let callStatus = 'completed';
          if (result.includes('missed') || result.includes('no answer')) callStatus = 'missed';
          else if (result.includes('voicemail')) callStatus = 'voicemail';
          else if (result.includes('busy')) callStatus = 'busy';
          else if (result.includes('failed') || result.includes('cancel')) callStatus = 'failed';

          // Cross-reference: contact → company
          let matchedContactId: number | null = null;
          let matchedCompanyId: number | null = null;

          if (direction !== 'internal' && normOther) {
            const contactMatch = phoneIndex.get(normOther);
            if (contactMatch) {
              matchedContactId = contactMatch.contactId;
              matchedCompanyId = contactMatch.companyId;
            } else {
              const companyId = companyPhoneIndex.get(normOther);
              if (companyId) matchedCompanyId = companyId;
            }
          }

          if (matchedContactId || matchedCompanyId) callsMatched++;

          await postgresClient.query(
            `INSERT INTO zoom_calls (
               zoom_call_id, resource_email, direction, call_status,
               other_party_number, other_party_name,
               start_time, duration_seconds,
               matched_contact_id, matched_company_id, synced_at
             ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,NOW())
             ON CONFLICT (zoom_call_id) DO UPDATE SET
               start_time = EXCLUDED.start_time,
               duration_seconds = EXCLUDED.duration_seconds,
               call_status = EXCLUDED.call_status,
               matched_contact_id = EXCLUDED.matched_contact_id,
               matched_company_id = EXCLUDED.matched_company_id,
               synced_at = NOW()`,
            [
              callId,
              user.email.toLowerCase(),
              direction,
              callStatus,
              otherPartyNumber || null,
              otherPartyName || null,
              call.date_time ? new Date(call.date_time) : null,
              call.duration ?? null,
              matchedContactId,
              matchedCompanyId,
            ]
          );
          callsUpserted++;
        }
      }
      console.log(`[ZOOM-SYNC] Calls: ${callsUpserted} upserted, ${callsMatched} matched to contacts/companies`);

      // ─── Step 4: Sync past meetings (90-day window) ───────────────────
      const meetingFrom = toIsoDate(new Date(Date.now() - MEETING_WINDOW_DAYS * 24 * 60 * 60 * 1000));
      const meetingTo = toIsoDate(new Date());

      // Build map of zoom_id → email for host resolution
      const zoomIdToEmail = new Map<string, string>(
        activeZoomUsers.map(u => [u.id, u.email.toLowerCase()])
      );

      let meetingsUpserted = 0;

      // meeting DB id map: zoom_meeting_id → db id (for participant insert)
      const meetingDbIds = new Map<string, number>();

      for (const user of activeZoomUsers) {
        console.log(`[ZOOM-SYNC] Fetching past meetings for ${user.email}...`);
        const meetings = await client.getUserPastMeetings(user.id, meetingFrom, meetingTo);

        for (const meeting of meetings) {
          const meetingId = String(meeting.id);
          const hostEmail = meeting.host_email ?? zoomIdToEmail.get(meeting.host_id) ?? user.email.toLowerCase();
          const endTime = meeting.end_time
            ? new Date(meeting.end_time)
            : meeting.start_time
            ? new Date(new Date(meeting.start_time).getTime() + (meeting.duration ?? 0) * 60000)
            : null;

          const result = await postgresClient.query(
            `INSERT INTO zoom_meetings (
               zoom_meeting_id, zoom_meeting_uuid, host_email,
               topic, start_time, end_time, duration_minutes,
               participant_count, client_participant_count, has_client_attendees,
               synced_at
             ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,0,false,NOW())
             ON CONFLICT (zoom_meeting_id) DO UPDATE SET
               zoom_meeting_uuid = EXCLUDED.zoom_meeting_uuid,
               host_email = EXCLUDED.host_email,
               topic = EXCLUDED.topic,
               start_time = EXCLUDED.start_time,
               end_time = EXCLUDED.end_time,
               duration_minutes = EXCLUDED.duration_minutes,
               participant_count = EXCLUDED.participant_count,
               synced_at = NOW()
             RETURNING id`,
            [
              meetingId,
              meeting.uuid || null,
              hostEmail,
              meeting.topic || null,
              meeting.start_time ? new Date(meeting.start_time) : null,
              endTime,
              meeting.duration ?? null,
              meeting.participants_count ?? 0,
            ]
          );

          if (result.rows[0]) {
            meetingDbIds.set(meetingId, result.rows[0].id);
            meetingsUpserted++;
          }
        }
      }
      console.log(`[ZOOM-SYNC] Meetings upserted: ${meetingsUpserted}`);

      // ─── Step 5: Sync meeting participants (30-day window) ────────────
      const participantCutoff = new Date(Date.now() - PARTICIPANT_WINDOW_DAYS * 24 * 60 * 60 * 1000);

      // Build contact email index
      const contactEmailResult = await postgresClient.query(
        `SELECT id, company_id, LOWER(email_address) as email1,
                LOWER(email_address2) as email2, LOWER(email_address3) as email3
         FROM contacts
         WHERE (is_deleted = false OR is_deleted IS NULL)`
      );
      const contactEmailIndex = new Map<string, { contactId: number; companyId: number | null }>();
      for (const row of contactEmailResult.rows) {
        for (const e of [row.email1, row.email2, row.email3]) {
          if (e && !contactEmailIndex.has(e)) {
            contactEmailIndex.set(e, { contactId: row.id, companyId: row.company_id });
          }
        }
      }

      let participantsUpserted = 0;

      // Process meetings within the 30-day participant window
      const recentMeetingIds = await postgresClient.query(
        `SELECT id, zoom_meeting_id FROM zoom_meetings
         WHERE start_time >= $1`,
        [participantCutoff]
      );

      for (const meetingRow of recentMeetingIds.rows) {
        const dbMeetingId: number = meetingRow.id;
        const zoomMeetingId: string = meetingRow.zoom_meeting_id;

        // Clear existing participants for this meeting (re-sync)
        await postgresClient.query(
          `DELETE FROM zoom_meeting_participants WHERE meeting_id = $1`,
          [dbMeetingId]
        );

        const participants = await client.getMeetingParticipants(zoomMeetingId);

        let clientParticipantCount = 0;
        let hasClientAttendees = false;

        for (const p of participants) {
          const email = (p.user_email || '').toLowerCase();
          const isInternal = email ? resourceEmails.has(email) : false;

          let matchedContactId: number | null = null;
          let matchedCompanyId: number | null = null;

          if (email && !isInternal) {
            const match = contactEmailIndex.get(email);
            if (match) {
              matchedContactId = match.contactId;
              matchedCompanyId = match.companyId;
              clientParticipantCount++;
              hasClientAttendees = true;
            }
          }

          await postgresClient.query(
            `INSERT INTO zoom_meeting_participants (
               meeting_id, participant_email, participant_name,
               duration_seconds, matched_contact_id, matched_company_id,
               is_internal, created_at
             ) VALUES ($1,$2,$3,$4,$5,$6,$7,NOW())`,
            [
              dbMeetingId,
              email || null,
              p.name || null,
              p.duration ?? null,
              matchedContactId,
              matchedCompanyId,
              isInternal,
            ]
          );
          participantsUpserted++;
        }

        // Update meeting with participant stats
        await postgresClient.query(
          `UPDATE zoom_meetings
           SET client_participant_count = $2, has_client_attendees = $3
           WHERE id = $1`,
          [dbMeetingId, clientParticipantCount, hasClientAttendees]
        );
      }
      console.log(`[ZOOM-SYNC] Participants upserted: ${participantsUpserted}`);

      const duration = Date.now() - startTime;
      console.log(`[ZOOM-SYNC] Done in ${duration}ms`);

      return {
        usersUpserted,
        callsUpserted,
        callsMatched,
        meetingsUpserted,
        participantsUpserted,
      };
    } finally {
      this.syncInProgress = false;
    }
  }
}

let _instance: ZoomSyncService | null = null;

export function getZoomSyncService(): ZoomSyncService {
  if (!_instance) {
    _instance = new ZoomSyncService();
  }
  return _instance;
}
