import { Request, Response } from 'express';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import type { AuthenticatedRequest } from '../types/express';
import * as form16Service from '../services/form16.service';
import {
  simulateCreditNoteFromSap,
  simulateDebitNoteFromSap,
  type SapCreditNoteDealerDetails,
  type SapDebitNoteDealerInfo,
} from '../services/form16SapSimulation.service';
import { extractForm16ADetails } from '../services/form16Ocr.service';
import { canViewForm16Submission, canView26As } from '../services/form16Permission.service';
import { ResponseHandler } from '../utils/responseHandler';
import logger from '../utils/logger';
import { WorkflowRequest } from '@models/WorkflowRequest';
import { Form16aSubmission } from '@models/Form16aSubmission';
import { Dealer } from '@models/Dealer';

/**
 * Form 16 controller: credit notes, OCR extract, and create submission for dealers.
 */

export class Form16Controller {
  // Minimal PII masking for 26AS APIs – mask PAN in all responses
  private maskPan(pan: unknown): string | undefined {
    if (pan == null) return undefined;
    const s = String(pan).trim();
    if (!s) return undefined;
    const last4 = s.slice(-4);
    if (s.length <= 4) return 'XXXX';
    return `XXXXXXX${last4}`;
  }

  private mask26asEntry(entry: any): any {
    if (!entry) return entry;
    const plain = typeof entry.toJSON === 'function' ? entry.toJSON() : entry;
    const masked = { ...plain };
    if (masked.panNumber) {
      masked.panNumber = this.maskPan(masked.panNumber);
    }
    return masked;
  }

  private toSapCsv(sap: {
    trnsUniqNo?: string | null;
    tdsTransId?: string | null;
    sapDocumentNumber?: string | null;
    msgTyp?: string | null;
    message?: string | null;
  }): string {
    const header = ['TRNS_UNIQ_NO', 'TDS_TRNS_ID', 'DOC_NO', 'MSG_TYP', 'MESSAGE'].join('|');
    const row = [
      sap.trnsUniqNo || '',
      sap.tdsTransId || '',
      sap.sapDocumentNumber || '',
      sap.msgTyp || '',
      sap.message || '',
    ]
      .map((v) => String(v).replace(/\r?\n/g, ' ').replace(/\|/g, ' '))
      .join('|');
    return `${header}\n${row}\n`;
  }

  private isStrictTxt26asFile(file: { originalname?: string; mimetype?: string; buffer?: Buffer }): { ok: boolean; reason?: string } {
    const originalName = (file.originalname || '').trim();
    const ext = path.extname(originalName).toLowerCase();
    if (ext !== '.txt') {
      return { ok: false, reason: 'Only .txt files are allowed for 26AS upload.' };
    }

    const mime = String(file.mimetype || '').toLowerCase();
    const allowedMimes = new Set(['text/plain', 'text/csv', 'application/octet-stream']);
    if (!allowedMimes.has(mime)) {
      return { ok: false, reason: 'Invalid MIME type. Only plain text (.txt) is allowed for 26AS upload.' };
    }

    if (!file.buffer || file.buffer.length === 0) {
      return { ok: false, reason: 'Uploaded file is empty. Please upload a valid 26AS .txt file.' };
    }

    const b = file.buffer;
    if (
      (b.length >= 4 && b[0] === 0x25 && b[1] === 0x50 && b[2] === 0x44 && b[3] === 0x46) || // PDF
      (b.length >= 4 && b[0] === 0x50 && b[1] === 0x4b && b[2] === 0x03 && b[3] === 0x04) || // ZIP/DOCX/XLSX
      (b.length >= 4 && b[0] === 0x89 && b[1] === 0x50 && b[2] === 0x4e && b[3] === 0x47) || // PNG
      (b.length >= 3 && b[0] === 0xff && b[1] === 0xd8 && b[2] === 0xff) || // JPEG
      (b.length >= 2 && b[0] === 0x4d && b[1] === 0x5a) // EXE
    ) {
      return { ok: false, reason: 'Binary file signature detected. Only plain text 26AS .txt files are allowed.' };
    }

    let suspiciousControlCount = 0;
    for (let i = 0; i < b.length; i++) {
      const byte = b[i];
      if (byte === 0x00) {
        return { ok: false, reason: 'Invalid text content. Null bytes detected.' };
      }
      const isTabOrLfOrCr = byte === 0x09 || byte === 0x0a || byte === 0x0d;
      const isPrintableAscii = byte >= 0x20 && byte <= 0x7e;
      if (!isTabOrLfOrCr && !isPrintableAscii) suspiciousControlCount++;
    }
    if (suspiciousControlCount / Math.max(b.length, 1) > 0.01) {
      return { ok: false, reason: 'Invalid text content. File appears to contain binary data.' };
    }

    const text = b.toString('utf8');
    if (text.includes('\uFFFD')) {
      return { ok: false, reason: 'Invalid UTF-8 text content. Please upload a plain text .txt file.' };
    }

    const lines = text.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
    if (lines.length === 0) {
      return { ok: false, reason: 'Uploaded file has no usable text rows.' };
    }

    return { ok: true };
  }
  /**
   * GET /api/v1/form16/permissions
   * Returns Form 16 permissions for the current user (API-driven from admin config).
   */
  async getPermissions(req: Request, res: Response): Promise<void> {
    try {
      const user = (req as AuthenticatedRequest).user;
      if (!user?.userId || !user?.email) {
        return ResponseHandler.unauthorized(res, 'Authentication required');
      }
      const role = (user as any).role;
      const [canViewForm16SubmissionData, canView26AS] = await Promise.all([
        canViewForm16Submission(user.email, user.userId, role),
        canView26As(user.email, role),
      ]);
      return ResponseHandler.success(
        res,
        { canViewForm16Submission: canViewForm16SubmissionData, canView26AS },
        'Form 16 permissions'
      );
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      logger.error('[Form16Controller] getPermissions error:', error);
      return ResponseHandler.error(res, 'Failed to get Form 16 permissions', 500, errorMessage);
    }
  }

  /**
   * GET /api/v1/form16/credit-notes
   * Dealer: list credit notes for the authenticated dealer.
   * RE: list all credit notes (all dealers).
   */
  async listCreditNotes(req: Request, res: Response): Promise<void> {
    try {
      const userId = (req as AuthenticatedRequest).user?.userId;
      if (!userId) {
        return ResponseHandler.unauthorized(res, 'Authentication required');
      }

      const financialYear = req.query.financialYear as string | undefined;
      const quarter = req.query.quarter as string | undefined;

      const result = await form16Service.listCreditNotesDealerOrRe(userId, {
        financialYear,
        quarter,
      });

      const payload: { creditNotes: typeof result.rows; total: number; summary?: typeof result.summary } = {
        creditNotes: result.rows,
        total: result.total,
      };
      if ((result as any).summary) payload.summary = (result as any).summary;
      return ResponseHandler.success(res, payload, 'Credit notes fetched');
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      logger.error('[Form16Controller] listCreditNotes error:', error);
      return ResponseHandler.error(res, 'Failed to fetch credit notes', 500, errorMessage);
    }
  }

  /**
   * GET /api/v1/form16/debit-notes
   * RE only. List all debit notes (all dealers).
   */
  async listDebitNotes(req: Request, res: Response): Promise<void> {
    try {
      const financialYear = req.query.financialYear as string | undefined;
      const quarter = req.query.quarter as string | undefined;
      const result = await form16Service.listAllDebitNotesForRe({ financialYear, quarter });
      const payload: { debitNotes: typeof result.rows; total: number; summary?: typeof result.summary } = {
        debitNotes: result.rows,
        total: result.total,
        summary: (result as any).summary,
      };
      return ResponseHandler.success(res, payload, 'Debit notes fetched');
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      logger.error('[Form16Controller] listDebitNotes error:', error);
      return ResponseHandler.error(res, 'Failed to fetch debit notes', 500, errorMessage);
    }
  }

  /**
   * GET /api/v1/form16/dealer/submissions
   * Dealer only. List Form 16 submissions for the authenticated dealer (pending/failed for Pending Submissions page).
   * Query: status (optional) pending | failed | pending,failed; financialYear; quarter.
   */
  async listDealerSubmissions(req: Request, res: Response): Promise<void> {
    try {
      const userId = (req as AuthenticatedRequest).user?.userId;
      if (!userId) {
        return ResponseHandler.unauthorized(res, 'Authentication required');
      }
      const status = req.query.status as string | undefined;
      const financialYear = req.query.financialYear as string | undefined;
      const quarter = req.query.quarter as string | undefined;
      const list = await form16Service.listDealerSubmissions(userId, { status, financialYear, quarter });
      return ResponseHandler.success(res, list, 'Dealer submissions fetched');
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      logger.error('[Form16Controller] listDealerSubmissions error:', error);
      return ResponseHandler.error(res, 'Failed to fetch dealer submissions', 500, errorMessage);
    }
  }

  /**
   * GET /api/v1/form16/dealer/pending-quarters
   * Dealer only. List quarters for which Form 16A has not been completed (no credit note).
   */
  async listDealerPendingQuarters(req: Request, res: Response): Promise<void> {
    try {
      const userId = (req as AuthenticatedRequest).user?.userId;
      if (!userId) {
        return ResponseHandler.unauthorized(res, 'Authentication required');
      }
      const list = await form16Service.listDealerPendingQuarters(userId);
      return ResponseHandler.success(res, list, 'Pending quarters fetched');
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      logger.error('[Form16Controller] listDealerPendingQuarters error:', error);
      return ResponseHandler.error(res, 'Failed to fetch pending quarters', 500, errorMessage);
    }
  }

  /**
   * GET /api/v1/form16/non-submitted-dealers
   * RE only. List dealers with missing Form 16A submissions for the given financial year (optional; defaults to current FY).
   * Query: financialYear (optional) e.g. "2024-25"; omit or "" for default/current FY.
   */
  async listNonSubmittedDealers(req: Request, res: Response): Promise<void> {
    try {
      const financialYear = (req.query.financialYear as string)?.trim() || undefined;
      const result = await form16Service.listNonSubmittedDealers(financialYear);
      return ResponseHandler.success(res, result, 'Non-submitted dealers fetched');
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      logger.error('[Form16Controller] listNonSubmittedDealers error:', error);
      return ResponseHandler.error(res, 'Failed to fetch non-submitted dealers', 500, errorMessage);
    }
  }

  /**
   * POST /api/v1/form16/non-submitted-dealers/notify
   * RE only. Send "submit Form 16" notification to one non-submitted dealer and record it (last notified column updates).
   * Body: { dealerCode: string, financialYear?: string }.
   */
  async notifyNonSubmittedDealer(req: Request, res: Response): Promise<void> {
    try {
      const userId = (req as AuthenticatedRequest).user?.userId;
      if (!userId) {
        return ResponseHandler.unauthorized(res, 'Authentication required');
      }
      const body = (req.body || {}) as { dealerCode?: string; dealerId?: string; email?: string; financialYear?: string };
      const financialYear = (body.financialYear || '').trim() || undefined;
      let dealerCode = (body.dealerCode || '').trim();
      const dealerId = (body.dealerId || '').trim();
      const dealerEmail = (body.email || '').trim().toLowerCase();

      // Fallback 1: resolve by Dealer PK (when FE sends id but dealerCode is empty).
      if (!dealerCode && dealerId) {
        const dealer = await Dealer.findByPk(dealerId, { attributes: ['salesCode', 'dlrcode'] });
        dealerCode = String((dealer as any)?.salesCode || (dealer as any)?.dlrcode || '').trim();
      }

      // Fallback 2: resolve from non-submitted list (supports id/email based payloads reliably).
      if (!dealerCode) {
        const list = await form16Service.listNonSubmittedDealers(financialYear);
        const match = list.dealers.find((d) =>
          (dealerId && String(d.id).trim() === dealerId) ||
          (dealerEmail && String(d.email || '').trim().toLowerCase() === dealerEmail)
        );
        dealerCode = String(match?.dealerCode || '').trim();
      }

      if (!dealerCode) {
        return ResponseHandler.error(res, 'dealerCode is required', 400);
      }
      const updated = await form16Service.recordNonSubmittedDealerNotification(dealerCode, financialYear || '', userId);
      if (!updated) {
        return ResponseHandler.error(res, 'Dealer not found in non-submitted list for this financial year', 404);
      }
      return ResponseHandler.success(res, { dealer: updated }, 'Notification sent');
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      logger.error('[Form16Controller] notifyNonSubmittedDealer error:', error);
      return ResponseHandler.error(res, 'Failed to send notification', 500, errorMessage);
    }
  }

  /**
   * GET /api/v1/form16/26as
   * RE only. List 26AS TDS entries with optional filters, pagination, and summary.
   */
  async list26as(req: Request, res: Response): Promise<void> {
    try {
      const financialYear = req.query.financialYear as string | undefined;
      const quarter = req.query.quarter as string | undefined;
      const tanNumber = req.query.tanNumber as string | undefined;
      const search = req.query.search as string | undefined;
      const status = req.query.status as string | undefined;
      const assessmentYear = req.query.assessmentYear as string | undefined;
      const sectionCode = req.query.sectionCode as string | undefined;
      const limit = req.query.limit != null ? parseInt(String(req.query.limit), 10) : undefined;
      const offset = req.query.offset != null ? parseInt(String(req.query.offset), 10) : undefined;
      const result = await form16Service.list26asEntries({
        financialYear,
        quarter,
        tanNumber,
        search,
        status,
        assessmentYear,
        sectionCode,
        limit,
        offset,
      });
      const entries = (result.rows || []).map((row: any) => this.mask26asEntry(row));
      return ResponseHandler.success(res, { entries, total: result.total, summary: result.summary }, '26AS entries fetched');
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      logger.error('[Form16Controller] list26as error:', error);
      return ResponseHandler.error(res, 'Failed to fetch 26AS entries', 500, errorMessage);
    }
  }

  /**
   * GET /api/v1/form16/26as/dashboard
   * RE only. Aggregated Form16A dashboard (collection/submission status + year/zone breakdown).
   */
  async get26asDashboard(req: Request, res: Response): Promise<void> {
    try {
      const dashboard = await form16Service.getForm16DashboardData();
      return ResponseHandler.success(res, dashboard, 'Form16A dashboard fetched');
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      logger.error('[Form16Controller] get26asDashboard error:', error);
      return ResponseHandler.error(res, 'Failed to fetch Form16A dashboard', 500, errorMessage);
    }
  }

  /**
   * POST /api/v1/form16/26as
   * RE only. Create a 26AS TDS entry.
   */
  async create26as(req: Request, res: Response): Promise<void> {
    try {
      const body = ((req.body ?? {}) as Record<string, unknown>);
      const tanNumber = (body.tanNumber as string)?.trim();
      const financialYear = (body.financialYear as string)?.trim();
      const quarter = (body.quarter as string)?.trim();
      if (!tanNumber || !financialYear || !quarter) {
        return ResponseHandler.error(res, 'tanNumber, financialYear and quarter are required', 400);
      }
      const taxDeducted = typeof body.taxDeducted === 'number' ? body.taxDeducted : parseFloat(String(body.taxDeducted ?? 0));
      const entry = await form16Service.create26asEntry({
        panNumber: (body.panNumber as string) || undefined,
        tanNumber,
        deductorName: (body.deductorName as string) || undefined,
        quarter,
        assessmentYear: (body.assessmentYear as string) || undefined,
        financialYear,
        sectionCode: (body.sectionCode as string) || undefined,
        amountPaid: typeof body.amountPaid === 'number' ? body.amountPaid : (body.amountPaid ? parseFloat(String(body.amountPaid)) : undefined),
        taxDeducted: Number.isNaN(taxDeducted) ? 0 : taxDeducted,
        totalTdsDeposited: typeof body.totalTdsDeposited === 'number' ? body.totalTdsDeposited : (body.totalTdsDeposited ? parseFloat(String(body.totalTdsDeposited)) : undefined),
        natureOfPayment: (body.natureOfPayment as string) || undefined,
        transactionDate: (body.transactionDate as string) || undefined,
        dateOfBooking: (body.dateOfBooking as string) || undefined,
        statusOltas: (body.statusOltas as string) || undefined,
        remarks: (body.remarks as string) || undefined,
      });
      const masked = this.mask26asEntry(entry);
      return ResponseHandler.success(res, { entry: masked }, '26AS entry created');
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      logger.error('[Form16Controller] create26as error:', error);
      return ResponseHandler.error(res, 'Failed to create 26AS entry', 500, errorMessage);
    }
  }

  /**
   * PUT /api/v1/form16/26as/:id
   * RE only. Update a 26AS TDS entry.
   */
  async update26as(req: Request, res: Response): Promise<void> {
    try {
      const id = parseInt((req.params as { id: string }).id, 10);
      if (Number.isNaN(id)) {
        return ResponseHandler.error(res, 'Invalid entry id', 400);
      }
      const body = ((req.body ?? {}) as Record<string, unknown>);
      const updateData: Record<string, unknown> = {};
      if (body.tanNumber !== undefined) updateData.tanNumber = body.tanNumber;
      if (body.panNumber !== undefined) updateData.panNumber = body.panNumber;
      if (body.deductorName !== undefined) updateData.deductorName = body.deductorName;
      if (body.quarter !== undefined) updateData.quarter = body.quarter;
      if (body.assessmentYear !== undefined) updateData.assessmentYear = body.assessmentYear;
      if (body.financialYear !== undefined) updateData.financialYear = body.financialYear;
      if (body.sectionCode !== undefined) updateData.sectionCode = body.sectionCode;
      if (body.amountPaid !== undefined) updateData.amountPaid = body.amountPaid;
      if (body.taxDeducted !== undefined) updateData.taxDeducted = body.taxDeducted;
      if (body.totalTdsDeposited !== undefined) updateData.totalTdsDeposited = body.totalTdsDeposited;
      if (body.natureOfPayment !== undefined) updateData.natureOfPayment = body.natureOfPayment;
      if (body.transactionDate !== undefined) updateData.transactionDate = body.transactionDate;
      if (body.dateOfBooking !== undefined) updateData.dateOfBooking = body.dateOfBooking;
      if (body.statusOltas !== undefined) updateData.statusOltas = body.statusOltas;
      if (body.remarks !== undefined) updateData.remarks = body.remarks;
      const entry = await form16Service.update26asEntry(id, updateData as any);
      if (!entry) {
        return ResponseHandler.error(res, '26AS entry not found', 404);
      }
      const masked = this.mask26asEntry(entry);
      return ResponseHandler.success(res, { entry: masked }, '26AS entry updated');
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      logger.error('[Form16Controller] update26as error:', error);
      return ResponseHandler.error(res, 'Failed to update 26AS entry', 500, errorMessage);
    }
  }

  /**
   * DELETE /api/v1/form16/26as/:id
   * RE only. Delete a 26AS TDS entry.
   */
  async delete26as(req: Request, res: Response): Promise<void> {
    try {
      const id = parseInt((req.params as { id: string }).id, 10);
      if (Number.isNaN(id)) {
        return ResponseHandler.error(res, 'Invalid entry id', 400);
      }
      const deleted = await form16Service.delete26asEntry(id);
      if (!deleted) {
        return ResponseHandler.error(res, '26AS entry not found', 404);
      }
      return ResponseHandler.success(res, {}, '26AS entry deleted');
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      logger.error('[Form16Controller] delete26as error:', error);
      return ResponseHandler.error(res, 'Failed to delete 26AS entry', 500, errorMessage);
    }
  }

  /**
   * GET /api/v1/form16/credit-notes/:id
   * Get a single credit note by id with dealer info and dealer transaction history.
   */
  async getCreditNoteById(req: Request, res: Response): Promise<void> {
    try {
      const id = parseInt((req.params as { id: string }).id, 10);
      if (Number.isNaN(id)) {
        return ResponseHandler.error(res, 'Invalid credit note id', 400);
      }
      const result = await form16Service.getCreditNoteById(id);
      if (!result) {
        return ResponseHandler.error(res, 'Credit note not found', 404);
      }
      return ResponseHandler.success(res, result, 'Credit note fetched');
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      logger.error('[Form16Controller] getCreditNoteById error:', error);
      return ResponseHandler.error(res, 'Failed to fetch credit note', 500, errorMessage);
    }
  }

  /**
   * GET /api/v1/form16/credit-notes/:id/sap-response
   * Returns persisted SAP response fields for the credit note from DB (and optional CSV URL).
   * If not yet available, returns 409 so UI can show "being generated, wait".
   */
  async viewCreditNoteSapResponse(req: Request, res: Response): Promise<void> {
    try {
      const userId = (req as AuthenticatedRequest).user?.userId;
      if (!userId) {
        return ResponseHandler.unauthorized(res, 'Authentication required');
      }
      const id = parseInt((req.params as { id: string }).id, 10);
      if (Number.isNaN(id)) {
        return ResponseHandler.error(res, 'Invalid credit note id', 400);
      }
      let sapResponse = null;
      try {
        sapResponse = await form16Service.getCreditNoteSapResponseForUser(id, userId);
      } catch (e: any) {
        const msg = String(e?.message || '');
        if (msg.toLowerCase().includes('not found')) {
          return ResponseHandler.error(res, 'Credit note not found', 404);
        }
        throw e;
      }
      if (!sapResponse) {
        return ResponseHandler.error(res, 'The credit note is being generated. Please wait.', 409);
      }
      return ResponseHandler.success(
        res,
        {
          sapResponse,
          // Use API-backed CSV URL so View works even when local /uploads file is unavailable in UAT.
          url: `/api/v1/form16/credit-notes/${id}/sap-response/csv`,
        },
        'OK'
      );
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      logger.error('[Form16Controller] viewCreditNoteSapResponse error:', error);
      return ResponseHandler.error(res, 'Failed to fetch credit note SAP response', 500, errorMessage);
    }
  }

  /**
   * GET /api/v1/form16/credit-notes/:id/download
   * Backward-compatible route that now always returns API-backed CSV URL.
   * The CSV itself is generated from persisted DB fields (no /uploads dependency).
   */
  async downloadCreditNote(req: Request, res: Response): Promise<void> {
    try {
      const userId = (req as AuthenticatedRequest).user?.userId;
      if (!userId) {
        return ResponseHandler.unauthorized(res, 'Authentication required');
      }
      const id = parseInt((req.params as { id: string }).id, 10);
      if (Number.isNaN(id)) {
        return ResponseHandler.error(res, 'Invalid credit note id', 400);
      }
      let sapResponse = null;
      try {
        sapResponse = await form16Service.getCreditNoteSapResponseForUser(id, userId);
      } catch (e: any) {
        const msg = String(e?.message || '');
        if (msg.toLowerCase().includes('not found')) {
          return ResponseHandler.error(res, 'Credit note not found', 404);
        }
        throw e;
      }
      if (!sapResponse) {
        return ResponseHandler.error(res, 'The credit note is being generated. Please wait.', 409);
      }
      return ResponseHandler.success(res, { url: `/api/v1/form16/credit-notes/${id}/sap-response/csv` }, 'OK');
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      logger.error('[Form16Controller] downloadCreditNote error:', error);
      return ResponseHandler.error(res, 'Failed to fetch credit note download link', 500, errorMessage);
    }
  }

  /**
   * GET /api/v1/form16/debit-notes/:id/sap-response
   * RE only. Returns a storage URL for the SAP response CSV if available.
   * If not yet available, returns 409 so UI can show "being generated, wait".
   */
  async viewDebitNoteSapResponse(req: Request, res: Response): Promise<void> {
    try {
      const id = parseInt((req.params as { id: string }).id, 10);
      if (Number.isNaN(id)) {
        return ResponseHandler.error(res, 'Invalid debit note id', 400);
      }
      const sapResponse = await form16Service.getDebitNoteSapResponse(id);
      if (!sapResponse) {
        return ResponseHandler.error(res, 'The debit note is being generated. Please wait.', 409);
      }
      return ResponseHandler.success(
        res,
        {
          sapResponse,
          // Use API-backed CSV URL so View works even when local /uploads file is unavailable in UAT.
          url: `/api/v1/form16/debit-notes/${id}/sap-response/csv`,
        },
        'OK'
      );
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      logger.error('[Form16Controller] viewDebitNoteSapResponse error:', error);
      return ResponseHandler.error(res, 'Failed to fetch debit note SAP response', 500, errorMessage);
    }
  }

  /**
   * GET /api/v1/form16/credit-notes/:id/sap-response/csv
   * Stream SAP response CSV generated from persisted DB fields.
   */
  async downloadCreditNoteSapResponseCsv(req: Request, res: Response): Promise<void> {
    try {
      const userId = (req as AuthenticatedRequest).user?.userId;
      if (!userId) {
        return ResponseHandler.unauthorized(res, 'Authentication required');
      }
      const id = parseInt((req.params as { id: string }).id, 10);
      if (Number.isNaN(id)) {
        return ResponseHandler.error(res, 'Invalid credit note id', 400);
      }
      const sapResponse = await form16Service.getCreditNoteSapResponseForUser(id, userId);
      if (!sapResponse) {
        return ResponseHandler.error(res, 'The credit note is being generated. Please wait.', 409);
      }
      const csv = this.toSapCsv(sapResponse);
      const fileName = (sapResponse.fileName || `credit-note-${id}.csv`).replace(/[^a-zA-Z0-9._-]/g, '_');
      res.setHeader('Content-Type', 'text/csv; charset=utf-8');
      res.setHeader('Content-Disposition', `attachment; filename="${fileName}"`);
      res.status(200).send(csv);
      return;
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      logger.error('[Form16Controller] downloadCreditNoteSapResponseCsv error:', error);
      return ResponseHandler.error(res, 'Failed to fetch credit note SAP response CSV', 500, errorMessage);
    }
  }

  /**
   * GET /api/v1/form16/debit-notes/:id/sap-response/csv
   * Stream SAP response CSV generated from persisted DB fields.
   */
  async downloadDebitNoteSapResponseCsv(req: Request, res: Response): Promise<void> {
    try {
      const id = parseInt((req.params as { id: string }).id, 10);
      if (Number.isNaN(id)) {
        return ResponseHandler.error(res, 'Invalid debit note id', 400);
      }
      const sapResponse = await form16Service.getDebitNoteSapResponse(id);
      if (!sapResponse) {
        return ResponseHandler.error(res, 'The debit note is being generated. Please wait.', 409);
      }
      const csv = this.toSapCsv(sapResponse);
      const fileName = (sapResponse.fileName || `debit-note-${id}.csv`).replace(/[^a-zA-Z0-9._-]/g, '_');
      res.setHeader('Content-Type', 'text/csv; charset=utf-8');
      res.setHeader('Content-Disposition', `attachment; filename="${fileName}"`);
      res.status(200).send(csv);
      return;
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      logger.error('[Form16Controller] downloadDebitNoteSapResponseCsv error:', error);
      return ResponseHandler.error(res, 'Failed to fetch debit note SAP response CSV', 500, errorMessage);
    }
  }

  /**
   * GET /api/v1/form16/requests/:requestId/credit-note
   * Get credit note (if any) linked to a Form 16 request. Used on Form 16 details workflow tab.
   */
  async getCreditNoteByRequest(req: Request, res: Response): Promise<void> {
    try {
      const requestId = (req.params as { requestId: string }).requestId;
      if (!requestId) {
        return ResponseHandler.error(res, 'requestId required', 400);
      }
      const note = await form16Service.getCreditNoteByRequestId(requestId);
      return ResponseHandler.success(res, { creditNote: note }, note ? 'Credit note fetched' : 'No credit note for this request');
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      logger.error('[Form16Controller] getCreditNoteByRequest error:', error);
      return ResponseHandler.error(res, 'Failed to fetch credit note', 500, errorMessage);
    }
  }

  /**
   * POST /api/v1/form16/requests/:requestId/cancel-submission
   * RE only. Cancel the Form 16 submission and mark workflow as rejected.
   */
  async cancelForm16Submission(req: Request, res: Response): Promise<void> {
    try {
      const userId = (req as AuthenticatedRequest).user?.userId;
      if (!userId) return ResponseHandler.unauthorized(res, 'Authentication required');
      const requestId = (req.params as { requestId: string }).requestId;
      if (!requestId) return ResponseHandler.error(res, 'Request ID required', 400);
      const result = await form16Service.cancelForm16Submission(requestId, userId);
      const { triggerForm16UnsuccessfulByRequestId } = await import('../services/form16Notification.service');
      triggerForm16UnsuccessfulByRequestId(requestId, 'Your Form 16 submission was cancelled by RE. No credit note will be issued.').catch((err) =>
        logger.error('[Form16Controller] Cancel notification failed:', err)
      );
      return ResponseHandler.success(res, result, 'Submission cancelled');
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      logger.error('[Form16Controller] cancelForm16Submission error:', error);
      return ResponseHandler.error(res, errorMessage, 404);
    }
  }

  /**
   * POST /api/v1/form16/requests/:requestId/resubmission-needed
   * RE only. Mark Form 16 submission as resubmission needed (e.g. partial OCR).
   */
  async setForm16ResubmissionNeeded(req: Request, res: Response): Promise<void> {
    try {
      const userId = (req as AuthenticatedRequest).user?.userId;
      if (!userId) return ResponseHandler.unauthorized(res, 'Authentication required');
      const requestId = (req.params as { requestId: string }).requestId;
      if (!requestId) return ResponseHandler.error(res, 'Request ID required', 400);
      const result = await form16Service.setForm16ResubmissionNeeded(requestId, userId);
      const { triggerForm16UnsuccessfulByRequestId } = await import('../services/form16Notification.service');
      triggerForm16UnsuccessfulByRequestId(requestId, 'RE has marked this submission as resubmission needed. Please resubmit Form 16.').catch((err) =>
        logger.error('[Form16Controller] Resubmission-needed notification failed:', err)
      );
      return ResponseHandler.success(res, result, 'Marked as resubmission needed');
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      logger.error('[Form16Controller] setForm16ResubmissionNeeded error:', error);
      return ResponseHandler.error(res, errorMessage, 404);
    }
  }

  /**
   * POST /api/v1/form16/sap-simulate/credit-note
   * Form 16 only. Simulate SAP credit note generation (dealer details + amount → JSON response).
   * When real SAP is integrated, replace the simulation service with the actual SAP API call.
   */
  async sapSimulateCreditNote(req: Request, res: Response): Promise<void> {
    try {
      const body = (req.body || {}) as { dealerDetails?: SapCreditNoteDealerDetails; amount?: number };
      const dealerDetails = body.dealerDetails as SapCreditNoteDealerDetails | undefined;
      const amount = typeof body.amount === 'number' ? body.amount : parseFloat(String(body.amount || 0));
      if (!dealerDetails?.dealerCode || amount <= 0) {
        return ResponseHandler.error(res, 'dealerDetails.dealerCode and a positive amount are required', 400);
      }
      const response = simulateCreditNoteFromSap(dealerDetails, amount);
      return ResponseHandler.success(res, response, 'Simulated SAP credit note');
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      logger.error('[Form16Controller] sapSimulateCreditNote error:', error);
      return ResponseHandler.error(res, 'SAP credit note simulation failed', 500, errorMessage);
    }
  }

  /**
   * POST /api/v1/form16/sap-simulate/debit-note
   * Form 16 only. Simulate SAP debit note generation (dealer code, dealer info, credit note number, amount → JSON response).
   * When real SAP is integrated, replace the simulation service with the actual SAP API call.
   */
  async sapSimulateDebitNote(req: Request, res: Response): Promise<void> {
    try {
      const body = (req.body || {}) as {
        dealerCode?: string;
        dealerInfo?: SapDebitNoteDealerInfo;
        creditNoteNumber?: string;
        amount?: number;
      };
      const dealerCode = (body.dealerCode || '').trim();
      const dealerInfo = body.dealerInfo || { dealerCode };
      const creditNoteNumber = (body.creditNoteNumber || '').trim();
      const amount = typeof body.amount === 'number' ? body.amount : parseFloat(String(body.amount || 0));
      if (!dealerCode || !creditNoteNumber || amount <= 0) {
        return ResponseHandler.error(res, 'dealerCode, creditNoteNumber and a positive amount are required', 400);
      }
      const response = simulateDebitNoteFromSap({ dealerCode, dealerInfo, creditNoteNumber, amount });
      return ResponseHandler.success(res, response, 'Simulated SAP debit note');
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      logger.error('[Form16Controller] sapSimulateDebitNote error:', error);
      return ResponseHandler.error(res, 'SAP debit note simulation failed', 500, errorMessage);
    }
  }

  /**
   * POST /api/v1/form16/26as/upload
   * RE only. Upload a single TXT file containing 26AS data (all dealers). Data stored in tds_26as_entries.
   * Only Section 194Q and Booking Status F/O are used for quarter aggregation. Log created first; then aggregation and auto-debit (if 26AS total changed and quarter was settled).
   */
  async upload26as(req: Request, res: Response): Promise<void> {
    try {
      const userId = (req as AuthenticatedRequest).user?.userId;
      const file = (req as any).file;
      if (!file || !file.buffer) {
        return ResponseHandler.error(res, 'No file uploaded. Please upload a .txt file.', 400);
      }
      const ext = path.extname(file.originalname || '').toLowerCase();
      if (ext !== '.txt') {
        return ResponseHandler.error(res, 'Only .txt files are allowed for 26AS upload.', 400);
      }
      const strictTxtValidation = this.isStrictTxt26asFile(file);
      if (!strictTxtValidation.ok) {
        return ResponseHandler.error(res, strictTxtValidation.reason || 'Invalid 26AS text file.', 400);
      }
      if (!userId) {
        return ResponseHandler.error(res, 'Authentication required', 401);
      }
      const log = await form16Service.log26asUpload(userId, {
        fileName: file.originalname || undefined,
        recordsImported: 0,
        errorsCount: 0,
      });
      const result = await form16Service.upload26asFile(file.buffer, (log as any).id);
      await (log as any).update({
        recordsImported: result.imported,
        errorsCount: result.errors.length,
      });
      if (result.imported > 0) {
        const agg = await form16Service.process26asUploadAggregation((log as any).id);
        logger.info(`[Form16] 26AS upload aggregation: ${agg.snapshotsCreated} snapshot(s), ${agg.debitsCreated} debit(s) created.`);
      }
      if (result.imported > 0) {
        const { trigger26AsDataAddedNotification } = await import('../services/form16Notification.service');
        trigger26AsDataAddedNotification().catch((err) =>
          logger.error('[Form16Controller] 26AS notification trigger failed:', err)
        );
      }
      return ResponseHandler.success(
        res,
        { imported: result.imported, errors: result.errors },
        result.imported > 0
          ? `26AS data uploaded: ${result.imported} record(s) imported.`
          : 'No records imported.'
      );
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      logger.error('[Form16Controller] upload26as error:', error);
      return ResponseHandler.error(res, 'Failed to upload 26AS file', 500, errorMessage);
    }
  }

  /**
   * GET /api/v1/form16/26as/upload-history
   * RE only. List 26AS upload audit log (who uploaded, when, records imported) for management section.
   */
  async get26asUploadHistory(req: Request, res: Response): Promise<void> {
    try {
      const limit = Math.min(Math.max(parseInt(String(req.query.limit || '50'), 10), 1), 200);
      const offset = Math.max(0, parseInt(String(req.query.offset ?? '0'), 10) || 0);
      const { rows: history, total } = await form16Service.list26asUploadHistory(limit, offset);
      return ResponseHandler.success(res, { history, total }, '26AS upload history fetched');
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      logger.error('[Form16Controller] get26asUploadHistory error:', error);
      return ResponseHandler.error(res, 'Failed to fetch 26AS upload history', 500, errorMessage);
    }
  }

  /**
   * POST /api/v1/form16/extract
   * Exact REform16 pattern: req.file.path from multer disk storage, ocrService.extractForm16ADetails, same response shape.
   */
  async extractOcr(req: Request, res: Response): Promise<void> {
    const file = (req as any).file;
    if (!file) {
      return ResponseHandler.error(res, 'No file uploaded', 400);
    }
    const filePath = file.path as string;
    if (!filePath || !fs.existsSync(filePath)) {
      return ResponseHandler.error(res, 'No file uploaded', 400);
    }
    try {
      logger.info('[Form16Controller] Performing OCR extraction for preview...');
      const ocrResult = await extractForm16ADetails(filePath);
      if (ocrResult.success) {
        logger.info('[Form16Controller] OCR extraction successful:', ocrResult.method || 'gemini');
        return ResponseHandler.success(
          res,
          {
            extractedData: ocrResult.data,
            ocrMethod: ocrResult.method || 'gemini',
            ocrProvider: ocrResult.ocrProvider || 'Google Gemini API',
            filename: file.filename,
            originalName: file.originalname,
            size: file.size,
            url: `/uploads/form16-extract/${file.filename}`,
          },
          'OCR extraction completed successfully'
        );
      }
      logger.error('[Form16Controller] OCR extraction failed:', ocrResult.error);
      return ResponseHandler.error(
        res,
        ocrResult.message || 'Failed to extract data from PDF',
        400,
        ocrResult.error
      );
    } catch (error: any) {
      logger.error('[Form16Controller] extractOcr error:', error);
      return ResponseHandler.error(res, 'OCR extraction failed', 500, error?.message);
    } finally {
      if (filePath && fs.existsSync(filePath)) {
        try {
          fs.unlinkSync(filePath);
        } catch (e) {
          logger.warn('[Form16Controller] Failed to delete temp file:', e);
        }
      }
    }
  }

  /**
   * POST /api/v1/form16/submissions
   * Create Form 16 submission: workflow_request + form16a_submissions, upload document.
   */
  async createSubmission(req: Request, res: Response): Promise<void> {
    try {
      const userId = (req as AuthenticatedRequest).user?.userId;
      if (!userId) {
        return ResponseHandler.unauthorized(res, 'Authentication required');
      }

      const file = (req as any).file;
      if (!file || !file.buffer) {
        return ResponseHandler.error(res, 'No file uploaded. Please upload Form 16A PDF.', 400);
      }

      const body = req.body as Record<string, string>;
      const dealerCode = (body.dealerCode || '').trim(); // optional: required when user is not mapped as dealer
      const financialYear = (body.financialYear || '').trim();
      const quarter = (body.quarter || '').trim();
      const form16aNumber = (body.form16aNumber || '').trim();
      const tdsAmount = parseFloat(body.tdsAmount);
      const totalAmount = parseFloat(body.totalAmount);
      const tanNumber = (body.tanNumber || '').trim();
      const deductorName = (body.deductorName || '').trim();
      const version = body.version ? parseInt(body.version, 10) : 1;
      let ocrExtractedData: Record<string, unknown> | undefined;
      if (body.ocrExtractedData && typeof body.ocrExtractedData === 'string') {
        try {
          ocrExtractedData = JSON.parse(body.ocrExtractedData) as Record<string, unknown>;
        } catch {
          // ignore invalid JSON
        }
      }

      if (!financialYear || !quarter || !form16aNumber || !tanNumber || !deductorName) {
        return ResponseHandler.error(res, 'Missing required fields: financialYear, quarter, form16aNumber, tanNumber, deductorName', 400);
      }
      if (Number.isNaN(tdsAmount) || Number.isNaN(totalAmount) || tdsAmount < 0 || totalAmount < 0) {
        return ResponseHandler.error(res, 'Valid tdsAmount and totalAmount (numbers >= 0) are required', 400);
      }

      const result = await form16Service.createSubmission(
        userId,
        file.buffer,
        file.originalname || 'form16a.pdf',
        {
          dealerCode: dealerCode || undefined,
          financialYear,
          quarter,
          form16aNumber,
          tdsAmount,
          totalAmount,
          tanNumber,
          deductorName,
          version,
          ocrExtractedData,
        },
        (req as AuthenticatedRequest).user?.email
      );

      const { triggerForm16SubmissionResultNotification } = await import('../services/form16Notification.service');
      triggerForm16SubmissionResultNotification(userId, result.validationStatus, {
        creditNoteNumber: result.creditNoteNumber ?? undefined,
        requestId: result.requestId,
        validationNotes: result.validationNotes,
      }).catch((err) => logger.error('[Form16Controller] Form 16 result notification failed:', err));

      return ResponseHandler.success(res, {
        requestId: result.requestId,
        requestNumber: result.requestNumber,
        submissionId: result.submissionId,
        validationStatus: result.validationStatus ?? undefined,
        creditNoteNumber: result.creditNoteNumber ?? undefined,
        validationNotes: result.validationNotes ?? undefined,
      }, 'Form 16A submission created');
    } catch (error: any) {
      const message = error?.message || 'Unknown error';
      logger.error('[Form16Controller] createSubmission error:', error);
      if (message.includes('Dealer not found') || message.includes('dealerCode is required') || message.includes('Invalid dealerCode')) {
        return ResponseHandler.error(res, message, 403);
      }
      // No validation on certificate number: revised Form 16A can reuse same certificate number for same quarter.
      // Unique constraint only on request_id; duplicate (same quarter + amount + credit note) is enforced in 26AS match.
      const isUniqueError = error?.name === 'SequelizeUniqueConstraintError' || message.includes('duplicate') || message.includes('unique') || message.includes('already been submitted');
      if (isUniqueError) {
        return ResponseHandler.error(res, 'This submission could not be created due to a conflict with an existing record.', 400);
      }
      // Surface Sequelize validation errors (e.g. field length, type) so client gets a clear message
      const isSequelizeValidation = error?.name === 'SequelizeValidationError' && Array.isArray(error?.errors);
      const detail = isSequelizeValidation
        ? error.errors.map((e: any) => e.message || e.type).join('; ')
        : message;
      const statusCode = isSequelizeValidation ? 400 : 500;
      return ResponseHandler.error(res, 'Failed to create Form 16 submission', statusCode, detail);
    }
  }

  /**
   * POST /api/v1/form16/test-notification (dev only)
   * Sends a Form 16 notification (in-app + push + email) to the current user for testing.
   * With Ethereal: check backend logs for "📧 Preview URL" to open the email in browser.
   */
  async testNotification(req: Request, res: Response): Promise<void> {
    try {
      const userId = (req as AuthenticatedRequest).user?.userId;
      if (!userId) {
        return ResponseHandler.unauthorized(res, 'Authentication required');
      }
      const { notificationService } = await import('../services/notification.service');
      await notificationService.sendToUsers([userId], {
        title: 'Form 16 – Test notification',
        body: 'This is a test. If you see this in-app and in your email (Ethereal preview URL in backend logs), Form 16 notifications are working.',
        type: 'form16_success_credit_note',
      });
      return ResponseHandler.success(res, { message: 'Test notification sent. Check in-app bell and backend logs for email preview URL (Ethereal).' }, 'OK');
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      logger.error('[Form16Controller] testNotification error:', error);
      return ResponseHandler.error(res, 'Failed to send test notification', 500, errorMessage);
    }
  }

  /**
   * POST /api/v1/form16/requests/:requestId/contact-admin
   * Dealer UX: when submission fails because 26AS is missing for the quarter, dealer can notify RE admins.
   */
  async contactAdmin(req: Request, res: Response): Promise<void> {
    try {
      const user = (req as AuthenticatedRequest).user;
      const userId = user?.userId;
      if (!userId) {
        return ResponseHandler.unauthorized(res, 'Authentication required');
      }
      const requestId = String(req.params.requestId || '').trim();
      if (!requestId) return ResponseHandler.error(res, 'requestId is required', 400);

      const reqRow = await WorkflowRequest.findByPk(requestId, {
        attributes: ['requestId', 'requestNumber', 'templateType', 'initiatorId'],
        raw: true,
      });
      if (!reqRow || (reqRow as any).templateType !== 'FORM_16') {
        return ResponseHandler.error(res, 'Form 16 request not found', 404);
      }
      if ((reqRow as any).initiatorId !== userId) {
        return ResponseHandler.error(res, 'Forbidden', 403);
      }

      const submission = await Form16aSubmission.findOne({
        where: { requestId },
        order: [['submittedDate', 'DESC']],
        attributes: ['id', 'validationStatus', 'validationNotes'],
      });
      if (!submission) return ResponseHandler.error(res, 'Submission not found', 404);

      const s: any = submission as any;
      const notes = String(s.validationNotes || '').toLowerCase();
      const isMissing26As =
        notes.includes('no 26as') ||
        notes.includes('no 26as record') ||
        notes.includes('ensure 26as has been uploaded');
      if (!isMissing26As) {
        return ResponseHandler.error(res, 'Contact admin is available only when 26AS is missing for the quarter.', 400);
      }

      const { triggerForm16MismatchContactAdminNotification } = await import('../services/form16Notification.service');
      await triggerForm16MismatchContactAdminNotification({
        requestId,
        requestNumber: (reqRow as any).requestNumber,
        dealerUserId: userId,
      });

      return ResponseHandler.success(res, { ok: true }, 'Admin notified');
    } catch (error: any) {
      logger.error('[Form16Controller] contactAdmin error:', error);
      return ResponseHandler.error(res, 'Failed to notify admin', 500, error?.message);
    }
  }
}

export const form16Controller = new Form16Controller();
