import { Request, Response } from 'express';
import type { AuthenticatedRequest } from '../types/express';
import { DealerClaimService } from '../services/dealerClaim.service';
import { ResponseHandler } from '../utils/responseHandler';
import { translateEInvoiceError } from '../utils/einvoiceErrors';
import logger from '../utils/logger';
import { gcsStorageService } from '../services/gcsStorage.service';
import { Document } from '../models/Document';
import { InternalOrder } from '../models/InternalOrder';
import { constants } from '../config/constants';
import { sapIntegrationService } from '../services/sapIntegration.service';
import fs from 'fs';
import path from 'path';
import crypto from 'crypto';
import { DealerClaimDetails } from '../models/DealerClaimDetails';
import { ClaimInvoice } from '../models/ClaimInvoice';
import { ClaimInvoiceItem } from '../models/ClaimInvoiceItem';
import { ClaimCreditNote } from '../models/ClaimCreditNote';
import { ClaimCreditNoteItem } from '../models/ClaimCreditNoteItem';
import { ActivityType } from '../models/ActivityType';
import { Participant } from '../models/Participant';
import { sanitizeObject, sanitizePermissive } from '../utils/sanitizer';
import { buildWfmClaimCsvRow, padDealerCode, WFM_CLAIM_CSV_HEADERS } from '../utils/helpers';
import { costBreakupSchema, closedExpensesSchema, updateEInvoiceSchema, updateIOSchema } from '../validators/dealerClaim.validator';

export class DealerClaimController {
  private dealerClaimService = new DealerClaimService();

  /**
   * Create a new dealer claim request
   * POST /api/v1/dealer-claims
   */
  async createClaimRequest(req: AuthenticatedRequest, res: Response): Promise<void> {
    try {
      const userId = req.user?.userId;
      if (!userId) {
        return ResponseHandler.error(res, 'Unauthorized', 401);
      }

      const {
        activityName,
        activityType,
        dealerCode,
        dealerName,
        dealerEmail,
        dealerPhone,
        dealerAddress,
        activityDate,
        location,
        requestDescription,
        periodStartDate,
        periodEndDate,
        estimatedBudget,
        approvers, // Array of approvers for all 8 steps
      } = req.body;

      // Validation
      const requiredFields = [
        { key: 'activityName', label: 'Activity Name' },
        { key: 'activityType', label: 'Activity Type' },
        { key: 'dealerCode', label: 'Dealer Code' },
        { key: 'dealerName', label: 'Dealer Name' },
        { key: 'location', label: 'Location' },
        { key: 'requestDescription', label: 'Request Description' },
      ];

      const missingFields = requiredFields
        .filter(field => !req.body[field.key])
        .map(field => field.label);

      if (missingFields.length > 0) {
        return ResponseHandler.error(
          res,
          `Required fields are missing or contain invalid content: ${missingFields.join(', ')}`,
          400
        );
      }

      const claimRequest = await this.dealerClaimService.createClaimRequest(userId, {
        activityName,
        activityType,
        dealerCode,
        dealerName,
        dealerEmail,
        dealerPhone,
        dealerAddress,
        activityDate: activityDate ? new Date(activityDate) : undefined,
        location,
        requestDescription,
        periodStartDate: periodStartDate ? new Date(periodStartDate) : undefined,
        periodEndDate: periodEndDate ? new Date(periodEndDate) : undefined,
        estimatedBudget: estimatedBudget ? parseFloat(estimatedBudget) : undefined,
        approvers: approvers || [], // Pass approvers array for all 8 steps
      });

      return ResponseHandler.success(res, {
        request: claimRequest,
        message: 'Claim request created successfully'
      }, 'Claim request created');
    } catch (error: any) {
      // Handle validation and business logic errors
      const isValidationError = error.message && (
        error.message.includes('Approver') ||
        error.message.includes('Valid content is required') ||
        error.message.includes('invalid script') ||
        error.message.includes('empty input detected')
      );

      if (isValidationError) {
        logger.warn('[DealerClaimController] Validation error:', { message: error.message });
        return ResponseHandler.error(res, error.message, 400);
      }

      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      logger.error('[DealerClaimController] Error creating claim request:', error);
      return ResponseHandler.error(res, 'Failed to create claim request', 500, errorMessage);
    }
  }

  /**
   * Get claim details
   * GET /api/v1/dealer-claims/:requestId
   * Accepts either UUID or requestNumber
   */
  async getClaimDetails(req: Request, res: Response): Promise<void> {
    try {
      const identifier = req.params.requestId; // Can be UUID or requestNumber

      // Find workflow to get actual UUID
      const workflow = await this.findWorkflowByIdentifier(identifier);
      if (!workflow) {
        return ResponseHandler.error(res, 'Workflow request not found', 404);
      }

      const requestId = (workflow as any).requestId || (workflow as any).request_id;
      if (!requestId) {
        return ResponseHandler.error(res, 'Invalid workflow request', 400);
      }

      const claimDetails = await this.dealerClaimService.getClaimDetails(requestId);

      return ResponseHandler.success(res, claimDetails, 'Claim details fetched');
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      logger.error('[DealerClaimController] Error getting claim details:', error);
      return ResponseHandler.error(res, 'Failed to fetch claim details', 500, errorMessage);
    }
  }

  /**
   * Helper to find workflow by either requestId (UUID) or requestNumber
   */
  private async findWorkflowByIdentifier(identifier: string): Promise<any> {
    const isUuid = (id: string): boolean => {
      const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
      return uuidRegex.test(id);
    };

    const { WorkflowRequest } = await import('../models/WorkflowRequest');
    if (isUuid(identifier)) {
      return await WorkflowRequest.findByPk(identifier);
    } else {
      return await WorkflowRequest.findOne({ where: { requestNumber: identifier } });
    }
  }

  /**
   * Submit dealer proposal (Step 1)
   * POST /api/v1/dealer-claims/:requestId/proposal
   * Accepts either UUID or requestNumber
   */
  async submitProposal(req: AuthenticatedRequest, res: Response): Promise<void> {
    try {
      const identifier = req.params.requestId; // Can be UUID or requestNumber
      const userId = req.user?.userId;
      const {
        costBreakup,
        totalEstimatedBudget,
        timelineMode,
        expectedCompletionDate,
        expectedCompletionDays,
        dealerComments,
      } = req.body;

      // Find workflow by identifier (UUID or requestNumber)
      const workflow = await this.findWorkflowByIdentifier(identifier);
      if (!workflow) {
        return ResponseHandler.error(res, 'Workflow request not found', 404);
      }

      // Get actual UUID and requestNumber
      const requestId = (workflow as any).requestId || (workflow as any).request_id;
      const requestNumber = (workflow as any).requestNumber || (workflow as any).request_number;

      if (!requestId) {
        return ResponseHandler.error(res, 'Invalid workflow request', 400);
      }

      // Parse costBreakup - it comes as JSON string from FormData
      let parsedCostBreakup: any[] = [];
      if (costBreakup) {
        if (typeof costBreakup === 'string') {
          try {
            parsedCostBreakup = JSON.parse(costBreakup);
            // Sanitize cost items
            parsedCostBreakup = sanitizeObject(parsedCostBreakup);
          } catch (parseError) {
            logger.error('[DealerClaimController] Failed to parse costBreakup JSON:', parseError);
            return ResponseHandler.error(res, 'Invalid costBreakup format. Expected JSON array.', 400);
          }
        } else if (Array.isArray(costBreakup)) {
          parsedCostBreakup = costBreakup;
        } else {
          logger.warn('[DealerClaimController] costBreakup is not a string or array:', typeof costBreakup);
          parsedCostBreakup = [];
        }
      }


      // Validate costBreakup array using Zod schema
      const costValidation = costBreakupSchema.safeParse(parsedCostBreakup);
      if (!costValidation.success) {
        return ResponseHandler.error(
          res, 
          `Invalid cost breakup data: ${costValidation.error.errors.map((e: any) => e.message).join(', ')}`, 
          400
        );
      }

      // Handle file upload if present
      let proposalDocumentPath: string | undefined;
      let proposalDocumentUrl: string | undefined;

      if (req.file) {
        const file = req.file;
        const fileBuffer = file.buffer || (file.path ? fs.readFileSync(file.path) : Buffer.from(''));

        const uploadResult = await gcsStorageService.uploadFileWithFallback({
          buffer: fileBuffer,
          originalName: file.originalname,
          mimeType: file.mimetype,
          requestNumber: requestNumber || 'UNKNOWN',
          fileType: 'documents'
        });

        proposalDocumentPath = uploadResult.filePath;
        proposalDocumentUrl = uploadResult.storageUrl;

        // Cleanup local file if exists
        if (file.path && fs.existsSync(file.path)) {
          fs.unlinkSync(file.path);
        }
      }

      // Use actual UUID for service call with parsed costBreakup array
      await this.dealerClaimService.submitDealerProposal(requestId, {
        proposalDocumentPath,
        proposalDocumentUrl,
        costBreakup: parsedCostBreakup, // Use parsed array
        totalEstimatedBudget: totalEstimatedBudget ? parseFloat(totalEstimatedBudget) : 0,
        timelineMode: timelineMode || 'date',
        expectedCompletionDate: expectedCompletionDate ? new Date(expectedCompletionDate) : undefined,
        expectedCompletionDays: expectedCompletionDays ? parseInt(expectedCompletionDays) : undefined,
        dealerComments: dealerComments ? sanitizePermissive(dealerComments) : '',
      });

      return ResponseHandler.success(res, { message: 'Proposal submitted successfully' }, 'Proposal submitted');
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      logger.error('[DealerClaimController] Error submitting proposal:', error);
      return ResponseHandler.error(res, 'Failed to submit proposal', 500, errorMessage);
    }
  }

  /**
   * Submit completion documents (Step 5)
   * POST /api/v1/dealer-claims/:requestId/completion
   * Accepts either UUID or requestNumber
   */
  async submitCompletion(req: AuthenticatedRequest, res: Response): Promise<void> {
    try {
      const identifier = req.params.requestId; // Can be UUID or requestNumber
      const {
        activityCompletionDate,
        numberOfParticipants,
        closedExpenses,
        totalClosedExpenses,
        completionDescription,
      } = req.body;

      // Parse closedExpenses if it's a JSON string
      let parsedClosedExpenses: any[] = [];
      if (closedExpenses) {
        try {
          parsedClosedExpenses = typeof closedExpenses === 'string' ? JSON.parse(closedExpenses) : closedExpenses;
          // Sanitize expenses
          parsedClosedExpenses = sanitizeObject(parsedClosedExpenses);
        } catch (e) {
          logger.warn('[DealerClaimController] Failed to parse closedExpenses JSON:', e);
          parsedClosedExpenses = [];
        }
      }

      // Validate closed expenses using Zod schema
      const expenseValidation = closedExpensesSchema.safeParse(parsedClosedExpenses);
      if (!expenseValidation.success) {
        return ResponseHandler.error(
          res, 
          `Invalid closed expenses: ${expenseValidation.error.errors.map((e: any) => e.message).join(', ')}`, 
          400
        );
      }

      // Get files from multer
      const files = req.files as { [fieldname: string]: Express.Multer.File[] } | undefined;
      const completionDocumentsFiles = files?.completionDocuments || [];
      const activityPhotosFiles = files?.activityPhotos || [];
      const invoicesReceiptsFiles = files?.invoicesReceipts || [];
      const attendanceSheetFile = files?.attendanceSheet?.[0];

      // Find workflow to get actual UUID
      const workflow = await this.findWorkflowByIdentifier(identifier);
      if (!workflow) {
        return ResponseHandler.error(res, 'Workflow request not found', 404);
      }

      const requestId = (workflow as any).requestId || (workflow as any).request_id;
      const requestNumber = (workflow as any).requestNumber || (workflow as any).request_number || 'UNKNOWN';
      if (!requestId) {
        return ResponseHandler.error(res, 'Invalid workflow request', 400);
      }

      const userId = (req as any).user?.userId || (req as any).user?.user_id;
      if (!userId) {
        return ResponseHandler.error(res, 'User not authenticated', 401);
      }

      if (!activityCompletionDate) {
        return ResponseHandler.error(res, 'Activity completion date is required', 400);
      }

      // Upload files to GCS and save to documents table
      const completionDocuments: any[] = [];
      const activityPhotos: any[] = [];

      // Upload and save completion documents to documents table with COMPLETION_DOC category
      for (const file of completionDocumentsFiles) {
        try {
          const fileBuffer = file.buffer || (file.path ? fs.readFileSync(file.path) : Buffer.from(''));
          const checksum = crypto.createHash('sha256').update(fileBuffer).digest('hex');

          const uploadResult = await gcsStorageService.uploadFileWithFallback({
            buffer: fileBuffer,
            originalName: file.originalname,
            mimeType: file.mimetype,
            requestNumber: requestNumber,
            fileType: 'documents'
          });

          const extension = path.extname(file.originalname).replace('.', '').toLowerCase();

          // Save to documents table
          const doc = await Document.create({
            requestId,
            uploadedBy: userId,
            fileName: path.basename(file.filename || file.originalname),
            originalFileName: file.originalname,
            fileType: extension,
            fileExtension: extension,
            fileSize: file.size,
            filePath: uploadResult.filePath,
            storageUrl: uploadResult.storageUrl,
            mimeType: file.mimetype,
            checksum,
            isGoogleDoc: false,
            googleDocUrl: null as any,
            category: constants.DOCUMENT_CATEGORIES.COMPLETION_DOC,
            version: 1,
            parentDocumentId: null as any,
            isDeleted: false,
            downloadCount: 0,
          } as any);

          completionDocuments.push({
            documentId: doc.documentId,
            name: file.originalname,
            url: uploadResult.storageUrl,
            size: file.size,
            type: file.mimetype,
          });

          // Cleanup local file if exists
          if (file.path && fs.existsSync(file.path)) {
            try {
              fs.unlinkSync(file.path);
            } catch (unlinkError) {
              logger.warn(`[DealerClaimController] Failed to delete local file ${file.path}`);
            }
          }
        } catch (error) {
          logger.error(`[DealerClaimController] Error uploading completion document ${file.originalname}:`, error);
        }
      }

      // Upload and save activity photos to documents table with ACTIVITY_PHOTO category
      for (const file of activityPhotosFiles) {
        try {
          const fileBuffer = file.buffer || (file.path ? fs.readFileSync(file.path) : Buffer.from(''));
          const checksum = crypto.createHash('sha256').update(fileBuffer).digest('hex');

          const uploadResult = await gcsStorageService.uploadFileWithFallback({
            buffer: fileBuffer,
            originalName: file.originalname,
            mimeType: file.mimetype,
            requestNumber: requestNumber,
            fileType: 'attachments'
          });

          const extension = path.extname(file.originalname).replace('.', '').toLowerCase();

          // Save to documents table
          const doc = await Document.create({
            requestId,
            uploadedBy: userId,
            fileName: path.basename(file.filename || file.originalname),
            originalFileName: file.originalname,
            fileType: extension,
            fileExtension: extension,
            fileSize: file.size,
            filePath: uploadResult.filePath,
            storageUrl: uploadResult.storageUrl,
            mimeType: file.mimetype,
            checksum,
            isGoogleDoc: false,
            googleDocUrl: null as any,
            category: constants.DOCUMENT_CATEGORIES.ACTIVITY_PHOTO,
            version: 1,
            parentDocumentId: null as any,
            isDeleted: false,
            downloadCount: 0,
          } as any);

          activityPhotos.push({
            documentId: doc.documentId,
            name: file.originalname,
            url: uploadResult.storageUrl,
            size: file.size,
            type: file.mimetype,
          });

          // Cleanup local file if exists
          if (file.path && fs.existsSync(file.path)) {
            try {
              fs.unlinkSync(file.path);
            } catch (unlinkError) {
              logger.warn(`[DealerClaimController] Failed to delete local file ${file.path}`);
            }
          }
        } catch (error) {
          logger.error(`[DealerClaimController] Error uploading activity photo ${file.originalname}:`, error);
        }
      }

      // Upload and save invoices/receipts to documents table with SUPPORTING category
      const invoicesReceipts: any[] = [];
      for (const file of invoicesReceiptsFiles) {
        try {
          const fileBuffer = file.buffer || (file.path ? fs.readFileSync(file.path) : Buffer.from(''));
          const checksum = crypto.createHash('sha256').update(fileBuffer).digest('hex');

          const uploadResult = await gcsStorageService.uploadFileWithFallback({
            buffer: fileBuffer,
            originalName: file.originalname,
            mimeType: file.mimetype,
            requestNumber: requestNumber,
            fileType: 'attachments'
          });

          const extension = path.extname(file.originalname).replace('.', '').toLowerCase();

          // Save to documents table
          const doc = await Document.create({
            requestId,
            uploadedBy: userId,
            fileName: path.basename(file.filename || file.originalname),
            originalFileName: file.originalname,
            fileType: extension,
            fileExtension: extension,
            fileSize: file.size,
            filePath: uploadResult.filePath,
            storageUrl: uploadResult.storageUrl,
            mimeType: file.mimetype,
            checksum,
            isGoogleDoc: false,
            googleDocUrl: null as any,
            category: constants.DOCUMENT_CATEGORIES.SUPPORTING,
            version: 1,
            parentDocumentId: null as any,
            isDeleted: false,
            downloadCount: 0,
          } as any);

          invoicesReceipts.push({
            documentId: doc.documentId,
            name: file.originalname,
            url: uploadResult.storageUrl,
            size: file.size,
            type: file.mimetype,
          });

          // Cleanup local file if exists
          if (file.path && fs.existsSync(file.path)) {
            try {
              fs.unlinkSync(file.path);
            } catch (unlinkError) {
              logger.warn(`[DealerClaimController] Failed to delete local file ${file.path}`);
            }
          }
        } catch (error) {
          logger.error(`[DealerClaimController] Error uploading invoice/receipt ${file.originalname}:`, error);
        }
      }

      // Upload and save attendance sheet to documents table with SUPPORTING category
      let attendanceSheet: any = null;
      if (attendanceSheetFile) {
        try {
          const fileBuffer = attendanceSheetFile.buffer || (attendanceSheetFile.path ? fs.readFileSync(attendanceSheetFile.path) : Buffer.from(''));
          const checksum = crypto.createHash('sha256').update(fileBuffer).digest('hex');

          const uploadResult = await gcsStorageService.uploadFileWithFallback({
            buffer: fileBuffer,
            originalName: attendanceSheetFile.originalname,
            mimeType: attendanceSheetFile.mimetype,
            requestNumber: requestNumber,
            fileType: 'attachments'
          });

          const extension = path.extname(attendanceSheetFile.originalname).replace('.', '').toLowerCase();

          // Save to documents table
          const doc = await Document.create({
            requestId,
            uploadedBy: userId,
            fileName: path.basename(attendanceSheetFile.filename || attendanceSheetFile.originalname),
            originalFileName: attendanceSheetFile.originalname,
            fileType: extension,
            fileExtension: extension,
            fileSize: attendanceSheetFile.size,
            filePath: uploadResult.filePath,
            storageUrl: uploadResult.storageUrl,
            mimeType: attendanceSheetFile.mimetype,
            checksum,
            isGoogleDoc: false,
            googleDocUrl: null as any,
            category: constants.DOCUMENT_CATEGORIES.SUPPORTING,
            version: 1,
            parentDocumentId: null as any,
            isDeleted: false,
            downloadCount: 0,
          } as any);

          attendanceSheet = {
            documentId: doc.documentId,
            name: attendanceSheetFile.originalname,
            url: uploadResult.storageUrl,
            size: attendanceSheetFile.size,
            type: attendanceSheetFile.mimetype,
          };

          // Cleanup local file if exists
          if (attendanceSheetFile.path && fs.existsSync(attendanceSheetFile.path)) {
            try {
              fs.unlinkSync(attendanceSheetFile.path);
            } catch (unlinkError) {
              logger.warn(`[DealerClaimController] Failed to delete local file ${attendanceSheetFile.path}`);
            }
          }
        } catch (error) {
          logger.error(`[DealerClaimController] Error uploading attendance sheet:`, error);
        }
      }

      await this.dealerClaimService.submitCompletionDocuments(requestId, {
        activityCompletionDate: new Date(activityCompletionDate),
        numberOfParticipants: numberOfParticipants ? parseInt(numberOfParticipants) : undefined,
        closedExpenses: parsedClosedExpenses,
        totalClosedExpenses: totalClosedExpenses ? parseFloat(totalClosedExpenses) : 0,
        invoicesReceipts: invoicesReceipts.length > 0 ? invoicesReceipts : undefined,
        attendanceSheet: attendanceSheet || undefined,
        completionDescription: completionDescription ? sanitizePermissive(completionDescription) : undefined,
      });

      return ResponseHandler.success(res, { message: 'Completion documents submitted successfully' }, 'Completion submitted');
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      logger.error('[DealerClaimController] Error submitting completion:', error);
      return ResponseHandler.error(res, 'Failed to submit completion documents', 500, errorMessage);
    }
  }

  /**
   * Validate/Fetch IO details from SAP
   * GET /api/v1/dealer-claims/:requestId/io/validate?ioNumber=IO1234
   * This endpoint fetches IO details from SAP and returns them, does not store anything
   * Flow: Fetch from SAP -> Return to frontend (no database storage)
   */
  async validateIO(req: AuthenticatedRequest, res: Response): Promise<void> {
    try {
      const { ioNumber } = req.query;

      if (!ioNumber || typeof ioNumber !== 'string') {
        return ResponseHandler.error(res, 'IO number is required', 400);
      }

      // Fetch IO details from SAP (will return mock data until SAP is integrated)
      const ioValidation = await sapIntegrationService.validateIONumber(ioNumber.trim());

      if (!ioValidation.isValid) {
        return ResponseHandler.error(res, ioValidation.error || 'Invalid IO number', 400);
      }

      return ResponseHandler.success(res, {
        ioNumber: ioValidation.ioNumber,
        availableBalance: ioValidation.availableBalance,
        blockedAmount: ioValidation.blockedAmount,
        remainingBalance: ioValidation.remainingBalance,
        currency: ioValidation.currency,
        description: ioValidation.description,
        isValid: true,
      }, 'IO fetched successfully from SAP');
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      logger.error('[DealerClaimController] Error validating IO:', error);
      return ResponseHandler.error(res, 'Failed to fetch IO from SAP', 500, errorMessage);
    }
  }

  /**
   * Update IO details and block amount in SAP
   * PUT /api/v1/dealer-claims/:requestId/io
   * Only stores data when blocking amount > 0
   * Accepts either UUID or requestNumber
   */
  async updateIODetails(req: AuthenticatedRequest, res: Response): Promise<void> {
    const userId = (req as any).user?.userId || (req as any).user?.user_id;
    try {
      const identifier = req.params.requestId; // Can be UUID or requestNumber
      const {
        ioNumber,
        ioRemark,
        availableBalance,
        blockedAmount,
        remainingBalance,
      } = req.body;

      // Find workflow to get actual UUID
      const workflow = await this.findWorkflowByIdentifier(identifier);
      if (!workflow) {
        return ResponseHandler.error(res, 'Workflow request not found', 404);
      }

      const requestId = (workflow as any).requestId || (workflow as any).request_id;
      if (!requestId) {
        return ResponseHandler.error(res, 'Invalid workflow request', 400);
      }

      // Validate request body using Zod schema
      const ioValidation = updateIOSchema.safeParse(req.body);
      if (!ioValidation.success) {
        return ResponseHandler.error(
          res,
          `Invalid IO details: ${ioValidation.error.errors.map((e: any) => e.message).join(', ')}`,
          400
        );
      }

      const blockAmount = blockedAmount ? parseFloat(blockedAmount) : 0;

      // Log received data for debugging
      logger.info('[DealerClaimController] updateIODetails received:', {
        requestId,
        ioNumber,
        availableBalance,
        blockedAmount: blockAmount,
        receivedBlockedAmount: blockedAmount, // Original value from request
        userId,
      });

      // Store in database when blocking amount > 0 OR when ioNumber and ioRemark are provided (for Step 3 approval)
      if (blockAmount > 0) {
        if (availableBalance === undefined) {
          return ResponseHandler.error(res, 'Available balance is required when blocking amount', 400);
        }

        // Don't pass remainingBalance - let the service calculate it from SAP's response
        // This ensures we always use the actual remaining balance from SAP after blocking
        const ioData = {
          ioNumber,
          ioRemark: ioRemark || '',
          availableBalance: parseFloat(availableBalance),
          blockedAmount: blockAmount,
          // remainingBalance will be calculated by the service from SAP's response
        };

        logger.info('[DealerClaimController] Calling updateIODetails service with:', ioData);

        await this.dealerClaimService.updateIODetails(
          requestId,
          ioData,
          userId
        );

        // Fetch and return the updated IO details from database
        const updatedIO = await InternalOrder.findOne({ where: { requestId } });

        if (updatedIO) {
          return ResponseHandler.success(res, {
            message: 'IO blocked successfully in SAP',
            ioDetails: {
              ioNumber: updatedIO.ioNumber,
              ioAvailableBalance: updatedIO.ioAvailableBalance,
              ioBlockedAmount: updatedIO.ioBlockedAmount,
              ioRemainingBalance: updatedIO.ioRemainingBalance,
              ioRemark: updatedIO.ioRemark,
              status: updatedIO.status,
            }
          }, 'IO blocked');
        }

        return ResponseHandler.success(res, { message: 'IO blocked successfully in SAP' }, 'IO blocked');
      } else if (ioNumber && ioRemark !== undefined) {
        // Save IO details (ioNumber, ioRemark) even without blocking amount
        // This is useful when Step 3 is approved but amount hasn't been blocked yet
        // IMPORTANT: Don't pass balance fields to preserve existing values from previous blocking
        await this.dealerClaimService.updateIODetails(
          requestId,
          {
            ioNumber,
            ioRemark: ioRemark || '',
            // Don't pass balance fields - preserve existing values from previous blocking
            // Only pass if explicitly provided and > 0 (for new records)
            ...(availableBalance && parseFloat(availableBalance) > 0 && { availableBalance: parseFloat(availableBalance) }),
            blockedAmount: 0,
            // Don't pass remainingBalance - preserve existing value from previous blocking
          },
          userId
        );

        return ResponseHandler.success(res, { message: 'IO details saved successfully' }, 'IO details saved');
      } else {
        // Just validate IO number without storing
        // This is for validation only (fetch amount scenario)
        return ResponseHandler.success(res, { message: 'IO validated successfully' }, 'IO validated');
      }
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      logger.error('[DealerClaimController] Error updating IO details:', error);
      return ResponseHandler.error(res, 'Failed to update IO details', 500, errorMessage);
    }
  }

  /**
   * Update e-invoice details (Step 7)
   * PUT /api/v1/dealer-claims/:requestId/e-invoice
   * If eInvoiceNumber is not provided, will auto-generate via DMS
   * Accepts either UUID or requestNumber
   */
  async updateEInvoice(req: AuthenticatedRequest, res: Response): Promise<void> {
    try {
      const identifier = req.params.requestId; // Can be UUID or requestNumber
      const {
        eInvoiceNumber,
        eInvoiceDate,
        dmsNumber,
        amount,
        description,
      } = req.body;

      // Validate request body using Zod schema
      const einvoiceValidation = updateEInvoiceSchema.safeParse(req.body);
      if (!einvoiceValidation.success) {
        return ResponseHandler.error(
          res,
          `Invalid e-invoice details: ${einvoiceValidation.error.errors.map((e: any) => e.message).join(', ')}`,
          400
        );
      }

      // Find workflow to get actual UUID
      const workflow = await this.findWorkflowByIdentifier(identifier);
      if (!workflow) {
        return ResponseHandler.error(res, 'Workflow request not found', 404);
      }

      const requestId = (workflow as any).requestId || (workflow as any).request_id;
      if (!requestId) {
        return ResponseHandler.error(res, 'Invalid workflow request', 400);
      }

      // If eInvoiceNumber provided, use manual entry; otherwise auto-generate
      const invoiceData = eInvoiceNumber ? {
        eInvoiceNumber,
        eInvoiceDate: eInvoiceDate ? new Date(eInvoiceDate) : new Date(),
        dmsNumber,
      } : {
        amount: amount ? parseFloat(amount) : undefined,
        description,
      };

      await this.dealerClaimService.updateEInvoiceDetails(requestId, invoiceData);

      return ResponseHandler.success(res, { message: 'E-Invoice details updated successfully' }, 'E-Invoice updated');
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      logger.error('[DealerClaimController] Error updating e-invoice:', error);

      // Translate technical PWC/IRP error codes to user-friendly messages
      const userFacingMessage = translateEInvoiceError(errorMessage);

      return ResponseHandler.error(res, userFacingMessage, 500, errorMessage);
    }
  }

  /**
   * Download E-Invoice PDF
   * GET /api/v1/dealer-claims/:requestId/e-invoice/pdf
   */
  async downloadInvoicePdf(req: Request, res: Response): Promise<void> {
    try {
      const identifier = req.params.requestId; // Can be UUID or requestNumber

      // Find workflow to get actual UUID
      const workflow = await this.findWorkflowByIdentifier(identifier);
      if (!workflow) {
        return ResponseHandler.error(res, 'Workflow request not found', 404);
      }

      const requestId = (workflow as any).requestId || (workflow as any).request_id;
      if (!requestId) {
        return ResponseHandler.error(res, 'Invalid workflow request', 400);
      }

      // Authorization Check
      const userRole = (req as any).user?.role;
      const userId = (req as any).user?.userId;

      if (userRole !== 'ADMIN' && userRole !== 'MANAGEMENT') {
        const participant = await Participant.findOne({
          where: { requestId, userId, isActive: true }
        });

        if (!participant) {
          return ResponseHandler.error(res, 'Access denied. You are not a participant in this workflow.', 403);
        }
      }

      const { ClaimInvoice } = await import('../models/ClaimInvoice');
      let invoice = await ClaimInvoice.findOne({ where: { requestId } });

      if (!invoice) {
        return ResponseHandler.error(res, 'Invoice record not found', 404);
      }

      // Generate PDF on the fly
      try {
        const { pdfService } = await import('../services/pdf.service');
        const pdfBuffer = await pdfService.generateInvoicePdf(requestId);

        const requestNumber = workflow.requestNumber || 'invoice';
        const fileName = `Invoice_${requestNumber}.pdf`;

        res.setHeader('Content-Type', 'application/pdf');
        res.setHeader('Content-Disposition', `inline; filename="${fileName}"`);
        res.setHeader('Content-Length', pdfBuffer.length);

        // Convert Buffer to stream
        const { Readable } = await import('stream');
        const stream = new Readable();
        stream.push(pdfBuffer);
        stream.push(null);
        stream.pipe(res);
      } catch (pdfError) {
        logger.error(`[DealerClaimController] Failed to generate PDF:`, pdfError);
        return ResponseHandler.error(res, 'Failed to generate invoice PDF', 500);
      }
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      logger.error('[DealerClaimController] Error downloading invoice PDF:', error);
      return ResponseHandler.error(res, 'Failed to download invoice PDF', 500, errorMessage);
    }
  }

  /**
   * Update credit note details (Step 8)
   * PUT /api/v1/dealer-claims/:requestId/credit-note
   * If creditNoteNumber is not provided, will auto-generate via DMS
   * Accepts either UUID or requestNumber
   */
  async updateCreditNote(req: AuthenticatedRequest, res: Response): Promise<void> {
    try {
      const identifier = req.params.requestId; // Can be UUID or requestNumber
      const {
        creditNoteNumber,
        creditNoteDate,
        creditNoteAmount,
        reason,
        description,
      } = req.body;

      // Find workflow to get actual UUID
      const workflow = await this.findWorkflowByIdentifier(identifier);
      if (!workflow) {
        return ResponseHandler.error(res, 'Workflow request not found', 404);
      }

      const requestId = (workflow as any).requestId || (workflow as any).request_id;
      if (!requestId) {
        return ResponseHandler.error(res, 'Invalid workflow request', 400);
      }

      // If creditNoteNumber provided, use manual entry; otherwise auto-generate
      const creditNoteData = creditNoteNumber ? {
        creditNoteNumber,
        creditNoteDate: creditNoteDate ? new Date(creditNoteDate) : new Date(),
        creditNoteAmount: creditNoteAmount ? parseFloat(creditNoteAmount) : undefined,
      } : {
        creditNoteAmount: creditNoteAmount ? parseFloat(creditNoteAmount) : undefined,
        reason,
        description,
      };

      await this.dealerClaimService.updateCreditNoteDetails(requestId, creditNoteData);

      return ResponseHandler.success(res, { message: 'Credit note details updated successfully' }, 'Credit note updated');
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      logger.error('[DealerClaimController] Error updating credit note:', error);
      return ResponseHandler.error(res, 'Failed to update credit note details', 500, errorMessage);
    }
  }

  /**
   * Send credit note to dealer and auto-approve Step 8
   * POST /api/v1/dealer-claims/:requestId/credit-note/send
   * Accepts either UUID or requestNumber
   */
  async sendCreditNoteToDealer(
    req: AuthenticatedRequest,
    res: Response
  ): Promise<void> {
    try {
      const identifier = req.params.requestId; // Can be UUID or requestNumber
      const userId = req.user?.userId;
      if (!userId) {
        return ResponseHandler.error(res, 'Unauthorized', 401);
      }

      // Find workflow to get actual UUID
      const workflow = await this.findWorkflowByIdentifier(identifier);
      if (!workflow) {
        return ResponseHandler.error(res, 'Workflow request not found', 404);
      }

      const requestId = (workflow as any).requestId || (workflow as any).request_id;
      if (!requestId) {
        return ResponseHandler.error(res, 'Invalid workflow request', 400);
      }

      await this.dealerClaimService.sendCreditNoteToDealer(requestId, userId);

      return ResponseHandler.success(res, { message: 'Credit note sent to dealer and Step 8 approved successfully' }, 'Credit note sent');
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      logger.error('[DealerClaimController] Error sending credit note to dealer:', error);
      return ResponseHandler.error(res, 'Failed to send credit note to dealer', 500, errorMessage);
    }
  }

  /**
   * Test SAP Budget Blocking (for testing/debugging)
   * POST /api/v1/dealer-claims/test/sap-block
   * 
   * This endpoint allows direct testing of SAP budget blocking without creating a full request
   */
  async testSapBudgetBlock(req: AuthenticatedRequest, res: Response): Promise<void> {
    try {
      const userId = req.user?.userId;
      if (!userId) {
        return ResponseHandler.error(res, 'Unauthorized', 401);
      }

      const { ioNumber, amount, requestNumber } = req.body;

      // Validation
      if (!ioNumber || !amount) {
        return ResponseHandler.error(res, 'Missing required fields: ioNumber and amount are required', 400);
      }

      const blockAmount = parseFloat(amount);
      if (isNaN(blockAmount) || blockAmount <= 0) {
        return ResponseHandler.error(res, 'Amount must be a positive number', 400);
      }

      logger.info(`[DealerClaimController] Testing SAP budget block:`, {
        ioNumber,
        amount: blockAmount,
        requestNumber: requestNumber || 'TEST-REQUEST',
        userId
      });

      // First validate IO number
      const ioValidation = await sapIntegrationService.validateIONumber(ioNumber);

      if (!ioValidation.isValid) {
        return ResponseHandler.error(res, `Invalid IO number: ${ioValidation.error || 'IO number not found in SAP'}`, 400);
      }

      logger.info(`[DealerClaimController] IO validation successful:`, {
        ioNumber,
        availableBalance: ioValidation.availableBalance
      });

      // Block budget in SAP
      const testRequestNumber = requestNumber || `TEST-${Date.now()}`;
      const blockResult = await sapIntegrationService.blockBudget(
        ioNumber,
        blockAmount,
        testRequestNumber,
        `Test budget block for ${testRequestNumber}`
      );

      if (!blockResult.success) {
        return ResponseHandler.error(res, `Failed to block budget in SAP: ${blockResult.error}`, 500);
      }

      // Return detailed response
      return ResponseHandler.success(res, {
        message: 'SAP budget block test successful',
        ioNumber,
        requestedAmount: blockAmount,
        availableBalance: ioValidation.availableBalance,
        sapResponse: {
          success: blockResult.success,
          blockedAmount: blockResult.blockedAmount,
          remainingBalance: blockResult.remainingBalance,
          sapDocumentNumber: blockResult.blockId || null,
          error: blockResult.error || null
        },
        calculatedRemainingBalance: ioValidation.availableBalance - blockResult.blockedAmount,
        validation: {
          isValid: ioValidation.isValid,
          availableBalance: ioValidation.availableBalance,
          error: ioValidation.error || null
        }
      }, 'SAP budget block test completed');
    } catch (error: any) {
      logger.error('[DealerClaimController] Error testing SAP budget block:', error);
      return ResponseHandler.error(res, error.message || 'Failed to test SAP budget block', 500);
    }
  }
  /**
   * Download Invoice CSV
   * GET /api/v1/dealer-claims/:requestId/e-invoice/csv
   */
  async downloadInvoiceCsv(req: Request, res: Response): Promise<void> {
    try {
      const identifier = req.params.requestId;

      // Use helper to find workflow
      const workflow = await this.findWorkflowByIdentifier(identifier);
      if (!workflow) {
        return ResponseHandler.error(res, 'Workflow request not found', 404);
      }

      const requestId = (workflow as any).requestId || (workflow as any).request_id;
      const requestNumber = (workflow as any).requestNumber || (workflow as any).request_number;

      if (!requestId) {
        return ResponseHandler.error(res, 'Invalid workflow request', 400);
      }

      // Authorization Check
      const userRole = (req as any).user?.role;
      const userId = (req as any).user?.userId;

      if (userRole !== 'ADMIN' && userRole !== 'MANAGEMENT') {
        const participant = await Participant.findOne({
          where: { requestId, userId, isActive: true }
        });

        if (!participant) {
          return ResponseHandler.error(res, 'Access denied. You are not a participant in this workflow.', 403);
        }
      }

      // Fetch related data
      logger.info(`[DealerClaimController] Preparing CSV for requestId: ${requestId}`);
      const [invoice, items, claimDetails, internalOrder] = await Promise.all([
        ClaimInvoice.findOne({ where: { requestId } }),
        ClaimInvoiceItem.findAll({ where: { requestId }, order: [['slNo', 'ASC']] }),
        DealerClaimDetails.findOne({ where: { requestId } }),
        InternalOrder.findOne({ where: { requestId } })
      ]);

      logger.info(`[DealerClaimController] Found ${items.length} items to export for request ${requestNumber}`);

      let sapRefNo = '';
      let taxationType = 'GST';
      if (claimDetails?.activityType) {
        const activity = await ActivityType.findOne({ where: { title: claimDetails.activityType } });
        sapRefNo = activity?.sapRefNo || '';
        taxationType = activity?.taxationType || (claimDetails.activityType.toLowerCase().includes('non') ? 'Non GST' : 'GST');
      }

      const isNonGst = taxationType === 'Non GST' || taxationType === 'Non-GST';

      // Construct CSV with pipe separator
      const headers = isNonGst
        ? [
            'TRNS_UNIQ_NO',
            'CLAIM_NUMBER',
            'INV_NUMBER',
            'DEALER_CODE',
            'IO_NUMBER',
            'CLAIM_DOC_TYP',
            'CLAIM_TYPE',
            'CLAIM_DATE',
            'CLAIM_AMT'
          ]
        : [...WFM_CLAIM_CSV_HEADERS];

      const rows = items.map(item => {
        if (isNonGst) {
          const d = new Date(invoice?.invoiceDate || invoice?.createdAt || new Date());
          const claimDate = `${d.getFullYear()}${String(d.getMonth() + 1).padStart(2, '0')}${String(d.getDate()).padStart(2, '0')}`;
          return [
            item.transactionCode || '',
            requestNumber,
            invoice?.invoiceNumber || '',
            padDealerCode(claimDetails?.dealerCode || ''),
            internalOrder?.ioNumber || '',
            sapRefNo,
            claimDetails?.activityType || '',
            claimDate,
            item.assAmt
          ].join('|');
        }

        const row = buildWfmClaimCsvRow({
          item: item as any,
          requestNumber,
          invoiceNumber: invoice?.invoiceNumber || '',
          invoiceDate: (invoice?.invoiceDate as Date) || (invoice?.createdAt as Date) || new Date(),
          dealerCode: claimDetails?.dealerCode || '',
          ioNumber: internalOrder?.ioNumber || '',
          claimDocTyp: sapRefNo,
          claimType: claimDetails?.activityType || '',
        });

        return headers.map((key) => String((row as any)[key] ?? '')).join('|');
      });

      const csvContent = [headers.join('|'), ...rows].join('\n');

      res.setHeader('Content-Type', 'text/csv');
      res.setHeader('Content-Disposition', `attachment; filename="Invoice_${requestNumber}.csv"`);

      res.status(200).send(csvContent);
      return;
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      logger.error('[DealerClaimController] Error downloading invoice CSV:', error);
      return ResponseHandler.error(res, 'Failed to download invoice CSV', 500, errorMessage);
    }
  }

  /**
   * Re-trigger WFM CSV push (Step 7)
   * POST /api/v1/dealer-claims/:requestId/wfm/retrigger
   */
  async retriggerWFMPush(req: Request, res: Response): Promise<void> {
    try {
      const { requestId: identifier } = req.params;

      const workflow = await this.findWorkflowByIdentifier(identifier);
      if (!workflow) {
        return ResponseHandler.error(res, 'Workflow request not found', 404);
      }

      const requestId = (workflow as any).id || (workflow as any).requestId;

      await this.dealerClaimService.pushWFMCSV(requestId);

      return ResponseHandler.success(res, {
        message: 'WFM CSV push re-triggered successfully'
      }, 'WFM push re-triggered');
    } catch (error: any) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      logger.error('[DealerClaimController] Error re-triggering WFM push:', error);
      return ResponseHandler.error(res, 'Failed to re-trigger WFM push', 500, errorMessage);
    }
  }

  /**
   * Fetch parsed WFM credit note CSV from outgoing folder
   * GET /api/v1/dealer-claims/:requestId/credit-note-wfm
   */
  async fetchCreditNoteWfm(req: Request, res: Response): Promise<void> {
    try {
      const identifier = req.params.requestId;

      const workflow = await this.findWorkflowByIdentifier(identifier);
      if (!workflow) {
        return ResponseHandler.error(res, 'Workflow request not found', 404);
      }

      const requestId = (workflow as any).requestId || (workflow as any).request_id;
      const requestNumber = (workflow as any).requestNumber || (workflow as any).request_number;

      const claimDetails = await DealerClaimDetails.findOne({ where: { requestId } });
      if (!claimDetails) {
        return ResponseHandler.error(res, 'Dealer claim details not found', 404);
      }

      let isNonGst = false;
      if (claimDetails.activityType) {
        const activity = await ActivityType.findOne({ where: { title: claimDetails.activityType } });
        const taxationType = activity?.taxationType || (claimDetails.activityType.toLowerCase().includes('non') ? 'Non GST' : 'GST');
        isNonGst = taxationType === 'Non GST' || taxationType === 'Non-GST';
      }

      const { wfmFileService } = await import('../services/wfmFile.service');
      const existingCreditNote = await ClaimCreditNote.findOne({ 
        where: { requestId },
        include: [{ 
          model: ClaimCreditNoteItem, 
          as: 'items',
          attributes: ['transactionNo'],
          order: [['slNo', 'ASC']]
        }]
      }) as any;

      if (existingCreditNote?.sapDocumentNumber || existingCreditNote?.creditNoteNumber) {
        let displayTxn = existingCreditNote.transactionNo || '';
        const items = existingCreditNote.items || [];
        if (items.length > 1) {
          const first = items[0].transactionNo;
          const last = items[items.length - 1].transactionNo;
          if (first && last && first !== last) {
            displayTxn = `${first} - ${last}`;
          }
        }

        const payload = [{
          TRNS_UNIQ_NO: displayTxn,
          CLAIM_NUMBER: requestNumber,
          DOC_NO: existingCreditNote.sapDocumentNumber || existingCreditNote.creditNoteNumber || '',
          MSG_TYP: existingCreditNote.status || '',
          MESSAGE: existingCreditNote.errorMessage || ''
        }];
        return ResponseHandler.success(res, payload, 'Credit note data fetched successfully');
      }

      const { filePath, data: creditNoteData } = await wfmFileService.getCreditNoteDetailsWithPath(
        claimDetails.dealerCode,
        requestNumber,
        isNonGst
      );
      
      if (!creditNoteData.length) {
        return ResponseHandler.success(res, [], 'Credit note data fetched successfully');
      }

      // Process the file using the sync service (handles grouping and transactions)
      const { creditNoteSyncService } = await import('../services/creditNoteSync.service');
      await creditNoteSyncService.processFile(filePath);

      // Return unified row with range if multiple rows exist for this claim
      const claimRows = creditNoteData.filter(row => row.CLAIM_NUMBER === requestNumber);
      if (claimRows.length === 0) {
        return ResponseHandler.success(res, [], 'Credit note data fetched successfully');
      }

      const claimRow = { ...claimRows[0] };
      if (claimRows.length > 1) {
        const first = claimRows[0].TRNS_UNIQ_NO;
        const last = claimRows[claimRows.length - 1].TRNS_UNIQ_NO;
        if (first && last && first !== last) {
          claimRow.TRNS_UNIQ_NO = `${first} - ${last}`;
        }
      }

      return ResponseHandler.success(res, [claimRow], 'Credit note data fetched successfully');
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      logger.error('[DealerClaimController] Error fetching credit note WFM data:', error);
      return ResponseHandler.error(res, 'Failed to fetch credit note CSV data', 500, errorMessage);
    }
  }
}
