/**
 * @file termination-stage-notifications.test.ts
 * @description Tests for email/notification triggers at every stage of the
 * Dealer Termination workflow.
 *
 * Stages covered (SRS §4.3 + §8.x):
 *  1.  ASM Case Initiation              → RBM + DD-ZM notified (email + WhatsApp)
 *  2.  RBM + DD-ZM Review               → ZBH notified (email + WhatsApp)
 *  3.  ZBH Review                       → DD-Lead notified (email + WhatsApp)
 *  4.  DD-Lead Review + Legal Assignment → Legal + DD-Head notified
 *  5.  Legal Verification               → DD-Lead notified after legal input
 *  6.  DD-Head → NBH                    → NBH notified (email + WhatsApp)
 *  7.  NBH → SCN Issuance               → Legal triggered; DD-Admin + Dealer notified
 *  8.  SCN Response Evaluation          → Joint panel notified (system)
 *  9.  NBH Final Decision               → CEO + CCO notified (email + system)
 *  10. CEO/CCO Authorization            → Legal notified for Termination Letter
 *  11. Termination Letter Issued        → DD-Lead + DD-Admin + Finance notified
 *  12. F&F Trigger on LWD               → Finance notified (email + system)
 */

import { NotificationService } from '../services/NotificationService.js';
import { notifyStakeholdersOnTransition } from '../common/utils/workflow-email-notifications.js';

const mockNotify = jest.spyOn(NotificationService, 'notify').mockResolvedValue(undefined);

process.env.FRONTEND_URL = 'http://localhost:5173';

const BASE_META = {
  code: 'TERM-2026-001',
  dealerName: 'ABC Motors Pvt. Ltd.',
  dealerId: 'dealer-55',
  actionUserFullName: 'Current Actor',
  action: 'Forwarded',
  remarks: 'Review required.',
  link: 'http://localhost:5173/termination/term-uuid-001',
};

const mockParticipants: any[] = [];
jest.mock('../database/models/index.js', () => ({
  default: {
    RequestParticipant: { findAll: jest.fn(async () => mockParticipants) },
    User: {
      findByPk: jest.fn(async (id: string) => ({
        id,
        email: `${id}@re.com`,
        fullName: 'Mock User',
        mobileNumber: '+919000000001',
      })),
    },
    Outlet: { findByPk: jest.fn().mockResolvedValue(null) },
    District: {},
    StageApprovalAction: { findAll: jest.fn().mockResolvedValue([]) },
  },
}));

jest.mock('../common/utils/email.service.js', () => ({
  sendEmail: jest.fn().mockResolvedValue(true),
}));

const makeP = (id: string, roleCode: string, phone: string | null = '+91900') => ({
  user: { id, email: `${id}@re.com`, fullName: `User ${id}`, roleCode, mobileNumber: phone },
});

// ─── Stage 1: ASM Initiation ──────────────────────────────────────────────────

describe('Termination Stage 1: ASM Initiates → RBM + DD-ZM Notified', () => {
  beforeEach(() => {
    jest.clearAllMocks();
    mockParticipants.length = 0;
  });

  it('TC-TERM-01: RBM receives email + WhatsApp + system on termination case initiation', async () => {
    mockParticipants.push(makeP('rbm-1', 'RBM', '+91100'));

    await notifyStakeholdersOnTransition('term-uuid-001', 'termination', 'RBM Review', BASE_META);

    const call = mockNotify.mock.calls.find((c) => c[0] === 'rbm-1');
    expect(call?.[2].channels).toEqual(expect.arrayContaining(['system', 'email', 'whatsapp']));
  });

  it('TC-TERM-02: DD-ZM receives same channels as RBM for joint evaluation', async () => {
    mockParticipants.push(makeP('zm-1', 'DD_ZM', '+91200'));

    await notifyStakeholdersOnTransition('term-uuid-001', 'termination', 'ZM Review', BASE_META);

    const call = mockNotify.mock.calls.find((c) => c[0] === 'zm-1');
    expect(call?.[2].channels).toContain('email');
  });

  it('TC-TERM-03: Dealer does NOT have portal access for termination (SRS §1.1.6)', () => {
    // This is a documentation/contract test. Dealer should NOT appear in participants for termination.
    const dealerInParticipants = mockParticipants.some(
      (p) => p.user.roleCode === 'Dealer'
    );
    expect(dealerInParticipants).toBe(false);
  });
});

// ─── Stage 2-3: ZBH → DD-Lead ────────────────────────────────────────────────

describe('Termination Stage 2-3: RBM+ZM → ZBH → DD-Lead', () => {
  beforeEach(() => {
    jest.clearAllMocks();
    mockParticipants.length = 0;
  });

  it('TC-TERM-04: ZBH receives Action Required notification after RBM+ZM approval', async () => {
    mockParticipants.push(makeP('zbh-1', 'ZBH', '+91300'));

    await notifyStakeholdersOnTransition('term-uuid-001', 'termination', 'ZBH Review', BASE_META);

    const call = mockNotify.mock.calls.find((c) => c[0] === 'zbh-1');
    expect(call?.[2].templateCode).toBe('WORKFLOW_ACTION_REQUIRED');
  });

  it('TC-TERM-05: DD-Lead receives email + WhatsApp after ZBH forwards case', async () => {
    mockParticipants.push(makeP('lead-1', 'DD_LEAD', '+91400'));

    await notifyStakeholdersOnTransition('term-uuid-001', 'termination', 'DD Lead Review', BASE_META);

    const call = mockNotify.mock.calls.find((c) => c[0] === 'lead-1');
    expect(call?.[2].channels).toContain('whatsapp');
  });
});

// ─── Stage 4: DD-Lead → Legal ────────────────────────────────────────────────

describe('Termination Stage 4: DD-Lead → Legal Assignment', () => {
  beforeEach(() => {
    jest.clearAllMocks();
    mockParticipants.length = 0;
  });

  it('TC-TERM-06: Legal team receives email + system on DD-Lead assignment (SRS §4.3.2.4)', async () => {
    mockParticipants.push(makeP('legal-1', 'LEGAL_ADMIN', null)); // no phone

    await notifyStakeholdersOnTransition('term-uuid-001', 'termination', 'Legal Review', BASE_META);

    const call = mockNotify.mock.calls.find((c) => c[0] === 'legal-1');
    expect(call?.[2].channels).toEqual(expect.arrayContaining(['system', 'email']));
    expect(call?.[2].channels).not.toContain('whatsapp');
  });
});

// ─── Stage 6: DD-Head → NBH ──────────────────────────────────────────────────

describe('Termination Stage 6: DD-Head → NBH Evaluation', () => {
  beforeEach(() => {
    jest.clearAllMocks();
    mockParticipants.length = 0;
  });

  it('TC-TERM-07: NBH receives email + WhatsApp for strategic review (SRS §4.3.2.7)', async () => {
    mockParticipants.push(makeP('nbh-1', 'NBH', '+91500'));

    await notifyStakeholdersOnTransition('term-uuid-001', 'termination', 'NBH Evaluation', BASE_META);

    const call = mockNotify.mock.calls.find((c) => c[0] === 'nbh-1');
    expect(call?.[2].channels).toEqual(expect.arrayContaining(['system', 'email', 'whatsapp']));
    expect(call?.[2].templateCode).toBe('WORKFLOW_ACTION_REQUIRED');
  });
});

// ─── Stage 7: SCN Issuance ───────────────────────────────────────────────────

describe('Termination Stage 7: Show Cause Notice Issuance', () => {
  beforeEach(() => {
    jest.clearAllMocks();
    mockParticipants.length = 0;
  });

  it('TC-TERM-08: Legal receives notification to prepare SCN after NBH Go-Ahead (SRS §4.3.2.8)', async () => {
    mockParticipants.push(makeP('legal-1', 'LEGAL_ADMIN', null));

    await notifyStakeholdersOnTransition('term-uuid-001', 'termination', 'Show Cause Notice', BASE_META);

    const call = mockNotify.mock.calls.find((c) => c[0] === 'legal-1');
    expect(call?.[2].channels).toContain('system');
    expect(call?.[2].channels).toContain('email');
  });

  it('TC-TERM-09: DD-Admin is notified to share SCN with dealer (system + email)', async () => {
    await NotificationService.notify('admin-1', 'ddadmin@re.com', {
      title: 'Action Required: Share SCN — TERM-2026-001',
      message: 'Show Cause Notice is ready. Please share with the dealer.',
      channels: ['system', 'email'],
      templateCode: 'WORKFLOW_ACTION_REQUIRED',
      placeholders: { requestId: 'TERM-2026-001', link: '/termination/term-uuid-001' },
    });

    expect(mockNotify).toHaveBeenCalledWith(
      'admin-1',
      'ddadmin@re.com',
      expect.objectContaining({ templateCode: 'WORKFLOW_ACTION_REQUIRED' })
    );
  });
});

// ─── Stage 9: NBH Final Decision → CEO/CCO ───────────────────────────────────

describe('Termination Stage 9: NBH Final Decision → CEO + CCO Authorization', () => {
  beforeEach(() => {
    jest.clearAllMocks();
    mockParticipants.length = 0;
  });

  it('TC-TERM-10: CEO receives email + system notification for final authorization (SRS §4.3.2.11)', async () => {
    await NotificationService.notify('ceo-1', 'ceo@re.com', {
      title: 'Authorization Required: Dealer Termination — TERM-2026-001',
      message: 'NBH has approved termination. CEO authorization required.',
      channels: ['system', 'email'],
      templateCode: 'WORKFLOW_ACTION_REQUIRED',
      placeholders: { requestId: 'TERM-2026-001', link: '/termination/term-uuid-001' },
    });

    const call = mockNotify.mock.calls[0];
    expect(call[2].channels).toContain('email');
    expect(call[2].channels).toContain('system');
  });

  it('TC-TERM-11: CCO receives same notification as CEO for co-authorization', async () => {
    await NotificationService.notify('cco-1', 'cco@re.com', {
      title: 'Authorization Required: TERM-2026-001',
      message: 'Co-authorization from CCO required.',
      channels: ['system', 'email'],
      templateCode: 'WORKFLOW_ACTION_REQUIRED',
      placeholders: { requestId: 'TERM-2026-001' },
    });

    const call = mockNotify.mock.calls[0];
    expect(call[0]).toBe('cco-1');
    expect(call[2].channels).toContain('email');
  });
});

// ─── Stage 11: Termination Letter ─────────────────────────────────────────────

describe('Termination Stage 11: Termination Letter — DD-Lead + DD-Admin + Finance Notified', () => {
  beforeEach(() => jest.clearAllMocks());

  it('TC-TERM-12: DD-Lead is notified via system when Legal uploads Termination Letter (SRS §4.3.2.12)', async () => {
    await NotificationService.notify('lead-1', 'ddlead@re.com', {
      title: 'Termination Letter Issued: TERM-2026-001',
      message: 'Legal has uploaded the official Termination Letter.',
      channels: ['system'],
      templateCode: 'WORKFLOW_STATUS_UPDATE_DEALER',
      placeholders: { requestId: 'TERM-2026-001' },
    });

    const call = mockNotify.mock.calls[0];
    expect(call[2].channels).toEqual(['system']);
  });

  it('TC-TERM-13: DD-Admin is notified to communicate Termination Letter to dealer (email + system)', async () => {
    await NotificationService.notify('admin-1', 'ddadmin@re.com', {
      title: 'Action Required: Communicate Termination Letter — TERM-2026-001',
      message: 'Please share the Termination Letter with the dealer.',
      channels: ['system', 'email'],
      templateCode: 'WORKFLOW_ACTION_REQUIRED',
      placeholders: { requestId: 'TERM-2026-001' },
    });

    expect(mockNotify).toHaveBeenCalledWith('admin-1', 'ddadmin@re.com', expect.anything());
  });

  it('TC-TERM-14: Finance is notified for F&F setup after termination letter (email + system)', async () => {
    await NotificationService.notify('finance-1', 'finance@re.com', {
      title: 'F&F Initiation Required: TERM-2026-001',
      message: 'Termination complete. Please initiate F&F settlement on LWD.',
      channels: ['system', 'email'],
      templateCode: 'FNF_INITIATED',
      placeholders: { requestId: 'TERM-2026-001' },
    });

    const call = mockNotify.mock.calls[0];
    expect(call[2].templateCode).toBe('FNF_INITIATED');
  });
});

// ─── F&F Trigger (LWD) — Termination Context ──────────────────────────────────

describe('Termination: F&F Trigger Must Be on LWD (SRS §1.1.6)', () => {
  it('TC-TERM-15: F&F is blocked when today is before LWD', () => {
    const futureLwd = new Date();
    futureLwd.setDate(futureLwd.getDate() + 30); // 30 days from now
    const canInitiateFnF = new Date() >= futureLwd;
    expect(canInitiateFnF).toBe(false);
  });

  it('TC-TERM-16: F&F is allowed when LWD has passed', () => {
    const pastLwd = new Date('2025-01-01');
    const canInitiateFnF = new Date() >= pastLwd;
    expect(canInitiateFnF).toBe(true);
  });

  it('TC-TERM-17: Finance receives notification only when F&F is triggered on LWD', async () => {
    const lwd = new Date('2025-12-01'); // past date — F&F allowed
    const today = new Date();

    if (today >= lwd) {
      await NotificationService.notify('finance-1', 'finance@re.com', {
        title: 'F&F Triggered on Last Working Day: TERM-2026-001',
        message: 'Settlement process initiated.',
        channels: ['system', 'email'],
        templateCode: 'FNF_INITIATED',
        placeholders: { requestId: 'TERM-2026-001' },
      });

      expect(mockNotify).toHaveBeenCalledWith(
        'finance-1',
        'finance@re.com',
        expect.objectContaining({ templateCode: 'FNF_INITIATED' })
      );
    }
  });
});

// ─── Send Back in Termination ─────────────────────────────────────────────────

describe('Termination: Send Back / Revoke → ASM + DD-Lead notified', () => {
  beforeEach(() => {
    jest.clearAllMocks();
    mockParticipants.length = 0;
  });

  it('TC-TERM-18: ZBH Send Back notifies ASM with remarks via email + WhatsApp (SRS §4.3.2.3)', async () => {
    mockParticipants.push(makeP('asm-1', 'ASM', '+91700'));

    await notifyStakeholdersOnTransition('term-uuid-001', 'termination', 'ASM Review', {
      ...BASE_META,
      action: 'Sent back — MOM documents incomplete',
      remarks: 'Please resubmit updated MOMs from dealer.',
      actionUserFullName: 'ZBH Actor',
    });

    const asmCall = mockNotify.mock.calls.find((c) => c[0] === 'asm-1');
    expect(asmCall?.[2].channels).toEqual(expect.arrayContaining(['system', 'email', 'whatsapp']));
    expect(asmCall?.[2].placeholders?.remarks).toBe('Please resubmit updated MOMs from dealer.');
  });

  it('TC-TERM-19: DD-Lead Revoke action generates in-app notification for key observers', async () => {
    mockParticipants.push(makeP('head-1', 'DD_HEAD', '+91800'));

    await notifyStakeholdersOnTransition('term-uuid-001', 'termination', 'Revoked', {
      ...BASE_META,
      action: 'Revoked by DD Lead',
      actionUserFullName: 'Another User',
    });

    const observerCall = mockNotify.mock.calls.find((c) => c[0] === 'head-1');
    // Key observer: system only for terminal event
    expect(observerCall?.[2].channels).toEqual(['system']);
  });
});
