/**
 * Form 16 notification triggers: 26AS added, success/unsuccess, alerts, reminders.
 * Uses Form 16 admin config templates and notificationService.sendToUsers.
 */

import { Op } from 'sequelize';
import { User } from '@models/User';
import { Dealer } from '@models/Dealer';
import { Form16CreditNote } from '@models/Form16CreditNote';
import { Form16aSubmission } from '@models/Form16aSubmission';
import { WorkflowRequest } from '@models/WorkflowRequest';
import { Form1626asQuarterSnapshot } from '@models/Form1626asQuarterSnapshot';
import { getForm16Config } from './form16Config.service';
import logger from '@utils/logger';

/** Get user IDs for dealers (principal email linked to a dealer). */
export async function getDealerUserIds(): Promise<string[]> {
  const dealers = await Dealer.findAll({
    where: { isActive: true },
    attributes: ['dealerPrincipalEmailId'],
    raw: true,
  });
  const emails = [...new Set((dealers.map((d) => (d as any).dealerPrincipalEmailId).filter(Boolean) as string[]).map((e) => e.trim().toLowerCase()))];
  if (emails.length === 0) return [];
  const users = await User.findAll({
    where: userWhereEmailIn(emails),
    attributes: ['userId'],
    raw: true,
  });
  return users.map((u) => (u as any).userId);
}

function userWhereEmailIn(emails: string[]) {
  if (emails.length === 0) return { email: { [Op.eq]: '__no_match__' } } as any;
  return { [Op.or]: emails.map((e) => ({ email: { [Op.iLike]: e } })) };
}

/**
 * Get user IDs who have 26AS management access (same intent as requireForm1626AsAccess).
 * - ADMIN: always included
 * - If twentySixAsViewerEmails is configured (non-empty): include those users (+ ADMIN)
 * - If empty: allow all RE users by default, but NEVER include dealers
 */
export async function getReUserIdsFor26As(): Promise<string[]> {
  const config = await getForm16Config();
  const viewerEmails = config.twentySixAsViewerEmails || [];

  const dealerUserIds = await getDealerUserIds();
  const dealerSet = new Set(dealerUserIds);

  const admins = await User.findAll({
    where: { role: { [Op.iLike]: 'ADMIN' } } as any,
    attributes: ['userId'],
    raw: true,
  });
  const adminIds = admins.map((u) => (u as any).userId).filter(Boolean);

  if (viewerEmails.length > 0) {
    const users = await User.findAll({
      where: userWhereEmailIn(viewerEmails),
      attributes: ['userId'],
      raw: true,
    });
    const ids = users.map((u) => (u as any).userId).filter(Boolean);
    return [...new Set([...adminIds, ...ids])].filter((id) => !dealerSet.has(id));
  }

  // Empty list = allow all RE users with 26AS access by default, but exclude dealers
  const allUsers = await User.findAll({ attributes: ['userId'], raw: true });
  const allIds = allUsers.map((u) => (u as any).userId).filter(Boolean);
  return [...new Set([...adminIds, ...allIds])].filter((id) => !dealerSet.has(id));
}

/**
 * Trigger notifications when 26AS data is uploaded: sent only to RE users (admins / 26AS viewers / submission viewers).
 * Dealers no longer receive this notification.
 */
export async function trigger26AsDataAddedNotification(): Promise<void> {
  try {
    const config = await getForm16Config();
    const n = config.notification26AsDataAdded;
    if (!n?.enabled) {
      logger.info('[Form16Notification] 26AS notification disabled in config, skipping');
      return;
    }
    const { notificationService } = await import('./notification.service');

    // Base RE audience (admins / RE viewers). This helper already tries to exclude dealers,
    // but we defensively re-filter below so that 26AS notifications are never sent to dealers.
    const baseReUserIds = await getReUserIdsFor26As();
    const dealerUserIds = await getDealerUserIds();
    const dealerSet = new Set(dealerUserIds);
    const reUserIds = baseReUserIds.filter((id) => !dealerSet.has(id));

    const title = 'Form 16 – 26AS data updated';
    if (reUserIds.length > 0 && n.templateRe) {
      await notificationService.sendToUsers(reUserIds, {
        title,
        body: n.templateRe,
        type: 'form16_26as_added',
      });
      logger.info(`[Form16Notification] 26AS notification sent to ${reUserIds.length} RE user(s)`);
    }
  } catch (e) {
    logger.error('[Form16Notification] trigger26AsDataAddedNotification failed:', e);
  }
}

/**
 * Dealer-triggered "contact admin" notification for 26AS mismatch / missing data.
 * Sent only to RE users who have 26AS access (26AS viewers).
 */
export async function triggerForm16MismatchContactAdminNotification(params: {
  requestId: string;
  requestNumber?: string;
  dealerUserId: string;
}): Promise<void> {
  try {
    const { notificationService } = await import('./notification.service');
    const reUserIds = await getReUserIdsFor26As();
    if (reUserIds.length === 0) return;

    const title = 'Form 16 – 26AS mismatch reported';
    const body = `Contact administrator: FORM 26AS does not match FORM 16A.\nRequest ID: ${params.requestNumber || params.requestId}.`;
    await notificationService.sendToUsers(reUserIds, {
      title,
      body,
      type: 'form16_26as_mismatch_contact_admin',
      requestId: params.requestId,
      requestNumber: params.requestNumber,
      url: params.requestNumber ? `/request/${params.requestNumber}` : undefined,
    });
    logger.info(`[Form16Notification] Mismatch contact-admin sent to ${reUserIds.length} RE user(s) for requestId=${params.requestId}`);
  } catch (e) {
    logger.error('[Form16Notification] triggerForm16MismatchContactAdminNotification failed:', e);
  }
}

/** Replace [CreditNoteRef] / [Issue] in template. */
function replacePlaceholders(template: string, replacements: Record<string, string>): string {
  let out = template;
  for (const [key, value] of Object.entries(replacements)) {
    out = out.replace(new RegExp(`\\[${key}\\]`, 'gi'), value);
  }
  // Remove unresolved placeholders to avoid leaking raw tokens like [Request ID] in emails.
  out = out.replace(/\[[^\]]+\]/g, '').replace(/\s{2,}/g, ' ').trim();
  return out;
}

function resolveAlertSubmitTemplate(configTemplate: string | undefined): string {
  const fallback =
    'Dear [Name], please submit Form 16A for the pending period. Due: [DueDate].';
  const t = (configTemplate || '').trim();
  if (!t) return fallback;
  // Guard against legacy misconfiguration where reminder template is saved into alert template.
  if (/\[request id\]/i.test(t) || /submission is pending/i.test(t)) return fallback;
  return t;
}

function resolveReminderTemplate(configTemplate: string | undefined): string {
  const fallback =
    'Reminder: Dear [Name], your Form 16A submission is pending for request [Request ID]. Please complete it.';
  const t = (configTemplate || '').trim();
  if (!t) return fallback;
  // Guard against swapped template in config.
  if (/\[duedate\]/i.test(t)) return fallback;
  // Guard against legacy awkward template currently seen in UAT screenshots.
  if (/form 16 submission is pending\.\s*\[name\],\s*\[request id\]\.\s*please review\.?/i.test(t)) return fallback;
  // Enforce required placeholders to keep reminder body meaningful and consistent.
  if (!/\[name\]/i.test(t) || !/\[request id\]/i.test(t)) return fallback;
  return t;
}

function sanitizeRequestRef(value: string | undefined): string {
  const v = (value || '').trim();
  if (!v) return '—';
  // Avoid sending internal UUIDs in user-facing reminder templates.
  const uuidV4Like = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
  if (uuidV4Like.test(v)) return 'pending Form 16 request';
  return v;
}

function getQuarterInfoForDate(d: Date): { financialYear: string; quarter: 'Q1' | 'Q2' | 'Q3' | 'Q4' } {
  const year = d.getFullYear();
  const month = d.getMonth() + 1; // 1..12
  const fyStartYear = month >= 4 ? year : year - 1;
  const fy = `${fyStartYear}-${String((fyStartYear + 1) % 100).padStart(2, '0')}`;
  if (month >= 4 && month <= 6) return { financialYear: fy, quarter: 'Q1' };
  if (month >= 7 && month <= 9) return { financialYear: fy, quarter: 'Q2' };
  if (month >= 10 && month <= 12) return { financialYear: fy, quarter: 'Q3' };
  return { financialYear: fy, quarter: 'Q4' };
}

/** Most recently ended quarter relative to now. */
function getMostRecentEndedQuarter(now: Date): { financialYear: string; quarter: 'Q1' | 'Q2' | 'Q3' | 'Q4' } {
  const current = getQuarterInfoForDate(now);
  const q = current.quarter;
  if (q === 'Q1') {
    const startYear = parseInt(current.financialYear.slice(0, 4), 10) - 1;
    const fy = `${startYear}-${String((startYear + 1) % 100).padStart(2, '0')}`;
    return { financialYear: fy, quarter: 'Q4' };
  }
  if (q === 'Q2') return { financialYear: current.financialYear, quarter: 'Q1' };
  if (q === 'Q3') return { financialYear: current.financialYear, quarter: 'Q2' };
  return { financialYear: current.financialYear, quarter: 'Q3' };
}

function getQuarterEndUtc(financialYear: string, quarter: 'Q1' | 'Q2' | 'Q3' | 'Q4'): Date {
  const startYear = parseInt(financialYear.slice(0, 4), 10);
  const mk = (y: number, m: number, day: number) => new Date(Date.UTC(y, m - 1, day, 0, 0, 0));
  if (quarter === 'Q1') return mk(startYear, 6, 30);
  if (quarter === 'Q2') return mk(startYear, 9, 30);
  if (quarter === 'Q3') return mk(startYear, 12, 31);
  return mk(startYear + 1, 3, 31);
}

function daysSinceUtc(dateUtc: Date, nowUtc: Date): number {
  return Math.floor((nowUtc.getTime() - dateUtc.getTime()) / (24 * 60 * 60 * 1000));
}

/**
 * Scheduled job (RE): remind 26AS viewers to upload 26AS for the most recently ended quarter if it's missing.
 * Fires on quarter end + N days (config: reminder26AsUploadAfterQuarterEndDays). Repeats every 7 days by default.
 */
export async function runForm16Remind26AsUploadJob(): Promise<void> {
  try {
    const config = await getForm16Config();
    if (!config.reminder26AsUploadEnabled) return;

    const { financialYear, quarter } = getMostRecentEndedQuarter(new Date());
    const nowUtc = new Date();
    const quarterEndUtc = getQuarterEndUtc(financialYear, quarter);
    const sinceEndDays = daysSinceUtc(quarterEndUtc, nowUtc);
    const afterDays = Math.max(0, Math.min(365, Number((config as any).reminder26AsUploadAfterQuarterEndDays ?? 0)));
    const everyDays = Math.max(1, Math.min(365, Number((config as any).reminder26AsUploadEveryDays ?? 7)));
    if (sinceEndDays < afterDays) return;
    if (((sinceEndDays - afterDays) % everyDays) !== 0) return;

    const snapshotCount = await Form1626asQuarterSnapshot.count({ where: { financialYear, quarter } });
    if (snapshotCount > 0) return;

    const reUserIds = await getReUserIdsFor26As();
    if (reUserIds.length === 0) return;
    const { notificationService } = await import('./notification.service');
    const body = replacePlaceholders(config.reminder26AsUploadTemplate || '', { FinancialYear: financialYear, Quarter: quarter });
    await notificationService.sendToUsers(reUserIds, {
      title: 'Form 16 – 26AS upload reminder',
      body: body || `Reminder: Please upload 26AS for ${financialYear} ${quarter}.`,
      type: 'form16_26as_upload_reminder',
    });
  } catch (e) {
    logger.error('[Form16Notification] runForm16Remind26AsUploadJob failed:', e);
  }
}

/**
 * Notify the dealer (initiator) after Form 16 submission result: success (credit note) or unsuccessful.
 */
export async function triggerForm16SubmissionResultNotification(
  initiatorUserId: string,
  validationStatus: string | undefined,
  opts: { creditNoteNumber?: string | null; requestId?: string; validationNotes?: string }
): Promise<void> {
  try {
    const config = await getForm16Config();
    const { notificationService } = await import('./notification.service');
    const isSuccess =
      validationStatus === 'success' || validationStatus === 'manually_approved';
    if (isSuccess && config.notificationForm16SuccessCreditNote?.enabled && config.notificationForm16SuccessCreditNote.template) {
      const body = replacePlaceholders(config.notificationForm16SuccessCreditNote.template, {
        CreditNoteRef: opts.creditNoteNumber || '—',
      });
      await notificationService.sendToUsers([initiatorUserId], {
        title: 'Form 16 – Credit note issued',
        body,
        type: 'form16_success_credit_note',
        requestId: opts.requestId,
      });
      logger.info(`[Form16Notification] Success notification sent to initiator ${initiatorUserId}`);
      return;
    }
    if (
      !isSuccess &&
      (validationStatus === 'failed' || validationStatus === 'duplicate' || validationStatus === 'resubmission_needed') &&
      config.notificationForm16Unsuccessful?.enabled &&
      config.notificationForm16Unsuccessful.template
    ) {
      const issue = opts.validationNotes || 'Submission could not be processed. Please check the request and resubmit if needed.';
      const body = replacePlaceholders(config.notificationForm16Unsuccessful.template, { Issue: issue });
      await notificationService.sendToUsers([initiatorUserId], {
        title: 'Form 16 – Submission unsuccessful',
        body,
        type: 'form16_unsuccessful',
        requestId: opts.requestId,
      });
      logger.info(`[Form16Notification] Unsuccessful notification sent to initiator ${initiatorUserId}`);
    }
  } catch (e) {
    logger.error('[Form16Notification] triggerForm16SubmissionResultNotification failed:', e);
  }
}

/**
 * Notify dealer when RE marks submission as resubmission needed or cancels (unsuccessful outcome).
 */
export async function triggerForm16UnsuccessfulByRequestId(requestId: string, issueMessage: string): Promise<void> {
  try {
    const req = await WorkflowRequest.findByPk(requestId, { attributes: ['initiatorId'], raw: true });
    const initiatorId = (req as any)?.initiatorId;
    if (!initiatorId) return;
    await triggerForm16SubmissionResultNotification(initiatorId, 'resubmission_needed', {
      requestId,
      validationNotes: issueMessage,
    });
  } catch (e) {
    logger.error('[Form16Notification] triggerForm16UnsuccessfulByRequestId failed:', e);
  }
}

/**
 * Send "submit Form 16" alert to dealers (e.g. those who haven't submitted for a quarter).
 * Call from a scheduled job using config alertSubmitForm16FrequencyDays/Hours.
 */
export async function triggerForm16AlertSubmit(dealerUserIds: string[], placeholders?: { name?: string; dueDate?: string }): Promise<void> {
  if (dealerUserIds.length === 0) return;
  try {
    const config = await getForm16Config();
    if (!config.alertSubmitForm16Enabled) return;
    const template = resolveAlertSubmitTemplate(config.alertSubmitForm16Template);
    const body = replacePlaceholders(template, {
      Name: placeholders?.name ?? 'Dealer',
      DueDate: placeholders?.dueDate ?? '—',
    });
    const { notificationService } = await import('./notification.service');
    await notificationService.sendToUsers(dealerUserIds, {
      title: 'Form 16 – Submit required',
      body,
      type: 'form16_alert_submit',
    });
    logger.info(`[Form16Notification] Alert submit sent to ${dealerUserIds.length} dealer(s)`);
  } catch (e) {
    logger.error('[Form16Notification] triggerForm16AlertSubmit failed:', e);
  }
}

/**
 * Send pending Form 16 reminder to dealers (e.g. those with pending submission).
 * Call from a scheduled job using config reminderFrequencyDays/Hours.
 */
export async function triggerForm16Reminder(dealerUserIds: string[], placeholders?: { name?: string; requestId?: string }): Promise<void> {
  if (dealerUserIds.length === 0) return;
  try {
    const config = await getForm16Config();
    if (!config.reminderNotificationEnabled) return;
    const template = resolveReminderTemplate(config.reminderNotificationTemplate);
    const body = replacePlaceholders(template, {
      Name: placeholders?.name ?? 'Dealer',
      'Request ID': sanitizeRequestRef(placeholders?.requestId),
    });
    const { notificationService } = await import('./notification.service');
    await notificationService.sendToUsers(dealerUserIds, {
      title: 'Form 16 – Pending submission reminder',
      body,
      type: 'form16_reminder',
    });
    logger.info(`[Form16Notification] Reminder sent to ${dealerUserIds.length} dealer(s)`);
  } catch (e) {
    logger.error('[Form16Notification] triggerForm16Reminder failed:', e);
  }
}

/**
 * Scheduled job: send "submit Form 16" alert to all non-submitted dealers (current FY).
 * Call from cron (e.g. daily at 9 AM).
 */
export async function runForm16AlertSubmitJob(): Promise<void> {
  try {
    const config = await getForm16Config();
    if (!config.alertSubmitForm16Enabled) {
      logger.info('[Form16Notification] Alert submit disabled in config, skipping job');
      return;
    }
    const { financialYear, quarter } = getMostRecentEndedQuarter(new Date());
    const nowUtc = new Date();
    const quarterEndUtc = getQuarterEndUtc(financialYear, quarter);
    const sinceEndDays = daysSinceUtc(quarterEndUtc, nowUtc);
    const afterDays = Math.max(0, Math.min(365, Number((config as any).alertSubmitForm16AfterQuarterEndDays ?? 0)));
    const everyDays = Math.max(1, Math.min(365, Number((config as any).alertSubmitForm16EveryDays ?? 7)));
    if (sinceEndDays < afterDays) return;
    if (((sinceEndDays - afterDays) % everyDays) !== 0) return;

    const { getDealerUserIdsMissingQuarter } = await import('./form16.service');
    const dealerUserIds = await getDealerUserIdsMissingQuarter(financialYear, quarter);
    if (dealerUserIds.length === 0) {
      logger.info('[Form16Notification] No non-submitted dealers for alert, skipping');
      return;
    }
    const dueDate = `${financialYear} ${quarter}`;
    await triggerForm16AlertSubmit(dealerUserIds, { name: 'Dealer', dueDate });
    logger.info(`[Form16Notification] Alert submit job completed: notified ${dealerUserIds.length} dealer(s)`, { financialYear, quarter });
  } catch (e) {
    logger.error('[Form16Notification] runForm16AlertSubmitJob failed:', e);
  }
}

/**
 * Scheduled job: send "pending Form 16" reminder to dealers who have open submissions without credit note.
 * Call from cron (e.g. daily at 10 AM).
 */
export async function runForm16ReminderJob(): Promise<void> {
  try {
    const config = await getForm16Config();
    if (!config.reminderNotificationEnabled) {
      logger.info('[Form16Notification] Reminder disabled in config, skipping job');
      return;
    }
    const { getDealersWithPendingForm16Submissions } = await import('./form16.service');
    const pending = await getDealersWithPendingForm16Submissions();
    if (pending.length === 0) {
      logger.info('[Form16Notification] No pending Form 16 submissions for reminder, skipping');
      return;
    }
    for (const { userId, requestNumber } of pending) {
      await triggerForm16Reminder([userId], { name: 'Dealer', requestId: requestNumber });
    }
    logger.info(`[Form16Notification] Reminder job completed: sent ${pending.length} reminder(s)`);
  } catch (e) {
    logger.error('[Form16Notification] runForm16ReminderJob failed:', e);
  }
}
