import fs from 'fs';
import path from 'path';
import logger from '../utils/logger';

/** Default WFM folder names (joined with path.sep for current OS). */
const DEFAULT_CLAIMS_INCOMING = path.join('WFM-QRE', 'INCOMING', 'WFM_MAIN', 'DLR_INC_CLAIMS');
const DEFAULT_CLAIMS_OUTGOING = path.join('WFM-QRE', 'OUTGOING', 'WFM_SAP_MAIN', 'DLR_INC_CLAIMS');
const DEFAULT_FORM16_INCOMING_MAIN = path.join('WFM-QRE', 'INCOMING', 'WFM_MAIN', 'FORM16');
const DEFAULT_FORM16_INCOMING_ARCHIVE = path.join('WFM-QRE', 'INCOMING', 'WFM_ARCHIVE', 'FORM16');
const DEFAULT_FORM16_OUTGOING_MAIN = path.join('WFM-QRE', 'OUTGOING', 'WFM_SAP_MAIN', 'FORM16');
const DEFAULT_FORM16_OUTGOING_ARCHIVE = path.join('WFM-QRE', 'OUTGOING', 'WFM_SAP_ARCHIVE', 'FORM16');

/**
 * WFM File Service
 * Handles generation and storage of CSV files in the WFM folder structure.
 * Dealer claims use DLR_INC_CLAIMS; Form 16 uses unified folders:
 * - INCOMING/WFM_MAIN/FORM16
 * - INCOMING/WFM_ARCHIVE/FORM16
 * - OUTGOING/WFM_SAP_MAIN/FORM16
 * - OUTGOING/WFM_SAP_ARCHIVE/FORM16
 * Paths are cross-platform; set WFM_BASE_PATH (and optional path overrides) in .env for production.
 */
export class WFMFileService {
    private basePath: string;

    // --- INCOMING PATHS (WFM_MAIN / WFM_ARACHIVE) ---
    private incomingGstClaimsPath: string;
    private incomingArchiveGstClaimsPath: string;

    private incomingNonGstClaimsPath: string;
    private incomingArchiveNonGstClaimsPath: string;

    private form16IncomingCreditPath: string;
    private incomingArchiveForm16CreditPath: string;

    private form16IncomingDebitPath: string;
    private incomingArchiveForm16DebitPath: string;

    // --- OUTGOING PATHS (WFM_SAP_MAIN / WFM_SAP_ARCHIVE) ---
    private outgoingGstClaimsPath: string;
    private outgoingNonGstClaimsPath: string;

    /** Form 16 credit responses: OUTGOING/WFM_SAP_MAIN/FORM16 */
    private form16OutgoingCreditPath: string;
    /** Form 16 debit responses: OUTGOING/WFM_SAP_MAIN/FORM16 */
    private form16OutgoingDebitPath: string;

    constructor() {
        this.basePath = process.env.WFM_BASE_PATH || 'C:\\WFM';

        // Initialize Incoming Paths from .env or defaults
        this.incomingGstClaimsPath = process.env.WFM_INCOMING_GST_CLAIMS_PATH || DEFAULT_CLAIMS_INCOMING + '_GST';
        this.incomingArchiveGstClaimsPath = process.env.WFM_ARCHIVE_GST_CLAIMS_PATH || path.join('WFM-QRE', 'INCOMING', 'WFM_ARACHIVE', 'DLR_INC_CLAIMS_GST');

        this.incomingNonGstClaimsPath = process.env.WFM_INCOMING_NON_GST_CLAIMS_PATH || DEFAULT_CLAIMS_INCOMING + '_NON_GST';
        this.incomingArchiveNonGstClaimsPath = process.env.WFM_ARCHIVE_NON_GST_CLAIMS_PATH || path.join('WFM-QRE', 'INCOMING', 'WFM_ARACHIVE', 'DLR_INC_CLAIMS_NON_GST');

        // Form16 unified path (credit/debit both use FORM16). Keep legacy vars as fallback.
        const legacyForm16Incoming = process.env.WFM_FORM16_INCOMING_PATH;
        const form16IncomingMain =
            process.env.WFM_FORM16_INCOMING_MAIN_PATH ||
            process.env.WFM_FORM16_CREDIT_INCOMING_PATH ||
            process.env.WFM_FORM16_DEBIT_INCOMING_PATH ||
            legacyForm16Incoming ||
            DEFAULT_FORM16_INCOMING_MAIN;
        const form16IncomingArchive =
            process.env.WFM_FORM16_INCOMING_ARCHIVE_PATH ||
            process.env.WFM_FORM16_CREDIT_ARCHIVE_PATH ||
            process.env.WFM_FORM16_DEBIT_ARCHIVE_PATH ||
            DEFAULT_FORM16_INCOMING_ARCHIVE;
        
        this.form16IncomingCreditPath = form16IncomingMain;
        this.form16IncomingDebitPath = form16IncomingMain;
        this.incomingArchiveForm16CreditPath = form16IncomingArchive;
        this.incomingArchiveForm16DebitPath = form16IncomingArchive;

        // Initialize Outgoing Paths from .env or defaults
        this.outgoingGstClaimsPath = process.env.WFM_OUTGOING_GST_CLAIMS_PATH || DEFAULT_CLAIMS_OUTGOING + '_GST';
        this.outgoingNonGstClaimsPath = process.env.WFM_OUTGOING_NON_GST_CLAIMS_PATH || DEFAULT_CLAIMS_OUTGOING + '_NON_GST';

        // Outgoing unified path (credit/debit both use FORM16). Keep legacy vars as fallback.
        const legacyForm16Outgoing = process.env.WFM_FORM16_OUTGOING_PATH;
        const form16OutgoingMain =
            process.env.WFM_FORM16_OUTGOING_MAIN_PATH ||
            process.env.WFM_FORM16_CREDIT_OUTGOING_PATH ||
            process.env.WFM_FORM16_DEBIT_OUTGOING_PATH ||
            legacyForm16Outgoing ||
            DEFAULT_FORM16_OUTGOING_MAIN;
        this.form16OutgoingCreditPath = form16OutgoingMain;
        this.form16OutgoingDebitPath = form16OutgoingMain;
    }

    /**
     * Ensure the target directory exists
     */
    private ensureDirectoryExists(dirPath: string): void {
        if (!fs.existsSync(dirPath)) {
            fs.mkdirSync(dirPath, { recursive: true });
            logger.info(`[WFMFileService] Created directory: ${dirPath}`);
        }
    }

    /**
     * Generate a CSV file for a credit note/claim and store it in the INCOMING folder
     * @param data The data to be written to the CSV
     * @param fileName The name of the file (e.g., CLAIM_12345.csv)
     */
    async generateIncomingClaimCSV(data: any[], fileName: string, isNonGst: boolean = false): Promise<string> {
        const maxRetries = 3;
        let retryCount = 0;

        while (retryCount <= maxRetries) {
            try {
                const targetPath = isNonGst ? this.incomingNonGstClaimsPath : this.incomingGstClaimsPath;
                const targetDir = path.join(this.basePath, targetPath);
                this.ensureDirectoryExists(targetDir);

                const fileNameWithExt = fileName.endsWith('.csv') ? fileName : `${fileName}.csv`;
                const filePath = path.join(targetDir, fileNameWithExt);

                // Simple CSV generation logic with pipe separator and no quotes
                const headers = Object.keys(data[0] || {}).join('|');
                const rows = data.map(item => Object.values(item).map(val => val === null || val === undefined ? '' : String(val)).join('|')).join('\n');
                const csvContent = `${headers}\n${rows}`;

                fs.writeFileSync(filePath, csvContent);
                logger.info(`[WFMFileService] Generated CSV at: ${filePath}`);

                // Archive copy
                try {
                    const archivePathPrefix = isNonGst ? this.incomingArchiveNonGstClaimsPath : this.incomingArchiveGstClaimsPath;
                    const archiveDir = path.join(this.basePath, archivePathPrefix);
                    this.ensureDirectoryExists(archiveDir);
                    const archivePath = path.join(archiveDir, fileNameWithExt);
                    fs.writeFileSync(archivePath, csvContent);
                    logger.info(`[WFMFileService] Archived CSV copy at: ${archivePath}`);
                } catch (archiveError) {
                    logger.error('[WFMFileService] Error saving archive copy:', archiveError);
                    // Don't throw archive error to avoid failing the main process
                }

                return filePath;
            } catch (error: any) {
                if (error.code === 'EBUSY' && retryCount < maxRetries) {
                    retryCount++;
                    const delay = retryCount * 1000;
                    logger.warn(`[WFMFileService] File busy/locked, retrying in ${delay}ms (Attempt ${retryCount}/${maxRetries}): ${fileName}`);
                    await new Promise(resolve => setTimeout(resolve, delay));
                    continue;
                }

                if (error.code === 'EBUSY') {
                    throw new Error(`File is locked or open in another program (e.g., Excel). Please close '${fileName}' and try again.`);
                }

                logger.error('[WFMFileService] Error generating incoming claim CSV:', error);
                throw error;
            }
        }
        throw new Error(`Failed to generate CSV after ${maxRetries} retries. Please ensure the file '${fileName}' is not open in any other application.`);
    }

    /**
     * Get the absolute path for an outgoing claim file
     */
    getOutgoingPath(fileName: string, isNonGst: boolean = false): string {
        const targetPath = isNonGst ? this.outgoingNonGstClaimsPath : this.outgoingGstClaimsPath;
        return path.join(this.basePath, targetPath, fileName);
    }

    private getOutgoingClaimsDir(isNonGst: boolean = false): string {
        const targetPath = isNonGst ? this.outgoingNonGstClaimsPath : this.outgoingGstClaimsPath;
        return path.join(this.basePath, targetPath);
    }

    /**
     * List all outgoing CSV files in the folder.
     */
    listOutgoingFiles(isNonGst: boolean = false): string[] {
        const outgoingDir = this.getOutgoingClaimsDir(isNonGst);
        if (!fs.existsSync(outgoingDir)) return [];
        
        return fs.readdirSync(outgoingDir)
            .filter(file => file.toLowerCase().endsWith('.csv'))
            .map(file => path.join(outgoingDir, file));
    }

    /**
     * Build outgoing credit note file path for a dealer + request.
     */
    getCreditNoteOutgoingFilePath(dealerCode: string, requestNumber: string, isNonGst: boolean = false): { fileName: string; filePath: string } {
        const dealer = String(dealerCode || '').trim();
        const paddedDealer = dealer.padStart(6, '0');
        const exactCandidates = [
            `CN_${paddedDealer}_${requestNumber}.csv`,
            `CN_${dealer}_${requestNumber}.csv`,
        ];

        for (const candidate of exactCandidates) {
            const candidatePath = this.getOutgoingPath(candidate, isNonGst);
            if (fs.existsSync(candidatePath)) {
                return { fileName: candidate, filePath: candidatePath };
            }
        }

        // Last fallback: pick any CN_*_<requestNumber>.csv from target outgoing folder.
        const outgoingDir = this.getOutgoingClaimsDir(isNonGst);
        if (fs.existsSync(outgoingDir)) {
            const escapedReq = requestNumber.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
            const reqFileRegex = new RegExp(`^CN_.*_${escapedReq}\\.csv$`, 'i');
            const matched = fs.readdirSync(outgoingDir).find((name) => reqFileRegex.test(name));
            if (matched) {
                return { fileName: matched, filePath: path.join(outgoingDir, matched) };
            }
        }

        // Keep deterministic default for callers when no file exists yet.
        const fileName = exactCandidates[0];
        return { fileName, filePath: this.getOutgoingPath(fileName, isNonGst) };
    }

    /**
     * Get credit note details from outgoing CSV
     */
    async getCreditNoteDetails(dealerCode: string, requestNumber: string, isNonGst: boolean = false): Promise<any[]> {
        const { data } = await this.getCreditNoteDetailsWithPath(dealerCode, requestNumber, isNonGst);
        return data;
    }

    /**
     * Get credit note details and resolved file path from outgoing CSV.
     */
    async getCreditNoteDetailsWithPath(dealerCode: string, requestNumber: string, isNonGst: boolean = false): Promise<{ fileName: string; filePath: string; data: any[] }> {
        const { fileName, filePath } = this.getCreditNoteOutgoingFilePath(dealerCode, requestNumber, isNonGst);

        try {
            if (!fs.existsSync(filePath)) {
                return { fileName, filePath, data: [] };
            }

            const fileContent = fs.readFileSync(filePath, 'utf-8');
            const lines = fileContent.split('\n').filter(line => line.trim() !== '');
            if (lines.length <= 1) return { fileName, filePath, data: [] }; // Only headers or empty

            const headers = lines[0].split('|');
            const data = lines.slice(1).map(line => {
                const values = line.split('|');
                const row: any = {};
                headers.forEach((header, index) => {
                    row[header.trim()] = values[index]?.trim() || '';
                });
                return row;
            });

            return { fileName, filePath, data };
        } catch (error) {
            logger.error(`[WFMFileService] Error reading credit note CSV: ${fileName}`, error);
            return { fileName, filePath, data: [] };
        }
    }

    /**
     * Delete an outgoing credit note file once it has been persisted.
     */
    deleteCreditNoteOutgoingFileByPath(filePath: string): void {
        try {
            if (fs.existsSync(filePath)) {
                fs.unlinkSync(filePath);
                logger.info(`[WFMFileService] Deleted processed credit note CSV: ${filePath}`);
            }
        } catch (error) {
            logger.error('[WFMFileService] Error deleting processed credit note CSV:', filePath, error);
            throw error;
        }
    }

    /**
     * Generate a CSV file for Form 16 (credit/debit note) and store in INCOMING/WFM_MAIN/FORM16.
     * Format: pipe (|) as column separator, no double quotes around values (SAP/WFM requirement).
     * @param data Array of one or more row objects (keys become header; use UPPER_SNAKE_CASE for column names)
     * @param fileName File name (e.g. CN-F-16-6282-24-25-Q1.csv or DN-F-16-6282-24-25-Q1.csv)
     * @param type 'credit' (default) or 'debit' – logical type only; folder is unified FORM16
     */
    async generateForm16IncomingCSV(data: any[], fileName: string, type: 'credit' | 'debit' = 'credit'): Promise<string> {
        const maxRetries = 3;
        let retryCount = 0;

        while (retryCount <= maxRetries) {
            try {
                const targetPath = type === 'debit' ? this.form16IncomingDebitPath : this.form16IncomingCreditPath;
                const targetDir = path.join(this.basePath, targetPath);
                this.ensureDirectoryExists(targetDir);

                const fileNameWithExt = fileName.endsWith('.csv') ? fileName : `${fileName}.csv`;
                const filePath = path.join(targetDir, fileNameWithExt);

                // Pipe separator, no double quotes (values as plain strings)
                const keys = Object.keys(data[0] || {});
                const headers = keys.join('|');
                const rows = data.map(item =>
                    keys.map(key => {
                        const val = item[key];
                        return val === null || val === undefined ? '' : String(val);
                    }).join('|')
                ).join('\n');
                const csvContent = `${headers}\n${rows}`;

                fs.writeFileSync(filePath, csvContent);
                logger.info(`[WFMFileService] Form 16 CSV generated at: ${filePath}`);

                // Archive copy
                try {
                    const archivePathPrefix = type === 'debit' ? this.incomingArchiveForm16DebitPath : this.incomingArchiveForm16CreditPath;
                    const archiveDir = path.join(this.basePath, archivePathPrefix);
                    this.ensureDirectoryExists(archiveDir);
                    const archivePath = path.join(archiveDir, fileNameWithExt);
                    fs.writeFileSync(archivePath, csvContent);
                    logger.info(`[WFMFileService] Form 16 archived copy at: ${archivePath}`);
                } catch (archiveError) {
                    logger.error('[WFMFileService] Error saving Form 16 archive copy:', archiveError);
                    // Don't throw archive error to avoid failing the main process
                }

                return filePath;
            } catch (error: any) {
                if (error.code === 'EBUSY' && retryCount < maxRetries) {
                    retryCount++;
                    const delay = retryCount * 1000;
                    logger.warn(`[WFMFileService] Form 16 file busy, retrying in ${delay}ms (${retryCount}/${maxRetries}): ${fileName}`);
                    await new Promise(resolve => setTimeout(resolve, delay));
                    continue;
                }
                if (error.code === 'EBUSY') {
                    throw new Error(`Form 16 file is locked. Please close '${fileName}' and try again.`);
                }
                logger.error('[WFMFileService] Error generating Form 16 incoming CSV:', error);
                throw error;
            }
        }
        throw new Error(`Failed to generate Form 16 CSV after ${maxRetries} retries.`);
    }

    /**
     * Get the absolute path for a Form 16 outgoing (response) file.
     * Both credit and debit use WFM/WFM-QRE/OUTGOING/WFM_SAP_MAIN/FORM16.
     */
    getForm16OutgoingPath(fileName: string, type: 'credit' | 'debit' = 'credit'): string {
        const targetPath = type === 'debit' ? this.form16OutgoingDebitPath : this.form16OutgoingCreditPath;
        return path.join(this.basePath, targetPath, fileName);
    }

    /**
     * Read a Form 16 outgoing (SAP) response CSV and return rows as objects keyed by header.
     * Uses getForm16OutgoingPath(fileName, type) to resolve the file path.
     */
    async readForm16OutgoingResponse(fileName: string, type: 'credit' | 'debit' = 'credit'): Promise<any[]> {
        const filePath = this.getForm16OutgoingPath(fileName, type);
        return this.readForm16OutgoingResponseByPath(filePath);
    }

    /**
     * Read a Form 16 outgoing (SAP) response CSV from an absolute path.
     * Expected columns (both credit and debit): DMS_UNIQ_NO, CLAIM_NUMBER, DOC_NO, MSG_TYP, MESSAGE.
     * Delimiter: pipe (|).
     */
    async readForm16OutgoingResponseByPath(filePath: string): Promise<any[]> {
        try {
            if (!fs.existsSync(filePath)) {
                return [];
            }
            const fileContent = fs.readFileSync(filePath, 'utf-8');
            const lines = fileContent.split(/\r?\n/).filter(line => line.trim() !== '');
            if (lines.length <= 1) return [];

            // SAP/WFM responses are expected to use pipe ('|'), but some environments may export
            // with comma/semicolon or with spaces in header names. Normalize both delimiter + headers.
            const headerLine = lines[0] || '';
            const delimiter = headerLine.includes('|') ? '|' : headerLine.includes(';') ? ';' : ',';
            const normalizeHeaderKey = (h: string) =>
                h
                    .trim()
                    .toUpperCase()
                    .replace(/\s+/g, '_') // 'DOC NO' -> 'DOC_NO'
                    .replace(/[^A-Z0-9_]/g, '_'); // keep only safe chars for matching

            const headers = headerLine.split(delimiter).map(normalizeHeaderKey);
            const data = lines.slice(1).map(line => {
                const values = line.split(delimiter);
                const row: any = {};
                headers.forEach((header, index) => {
                    row[header] = values[index]?.trim() || '';
                });
                return row;
            });
            return data;
        } catch (error) {
            logger.error('[WFMFileService] Error reading Form 16 response CSV:', filePath, error);
            return [];
        }
    }
}

export const wfmFileService = new WFMFileService();
