import axios, { AxiosError } from 'axios';
import https from 'https';
import { XMLParser } from 'fast-xml-parser';
import logger from '../utils/logger';

/**
 * SAP Integration Service
 * Handles integration with SAP for IO validation and budget blocking
 * Integrates with SAP OData service via Zscaler URL
 */
export class SAPIntegrationService {
  private sapBaseUrl: string;
  private sapUsername?: string;
  private sapPassword?: string;
  private sapTimeout: number;
  private sapServiceName: string; // OData service name for IO validation (e.g., ZFI_BUDGET_CHECK_API_SRV)
  private sapBlockServiceName: string; // OData service name for budget blocking (e.g., ZFI_BUDGET_BLOCK_API_SRV)
  private sapRequester: string; // Requester identifier for budget blocking

  constructor() {
    this.sapBaseUrl = process.env.SAP_BASE_URL || '';
    this.sapUsername = process.env.SAP_USERNAME;
    this.sapPassword = process.env.SAP_PASSWORD;
    this.sapTimeout = parseInt(process.env.SAP_TIMEOUT_MS || '30000', 10); // Default 30 seconds
    // Service name can be configured per environment, defaults to budget check service
    this.sapServiceName = process.env.SAP_SERVICE_NAME || 'ZFI_BUDGET_CHECK_API_SRV';
    // Budget blocking service name (different from validation service)
    this.sapBlockServiceName = process.env.SAP_BLOCK_SERVICE_NAME || 'ZFI_BUDGET_BLOCK_API_SRV';
    // Requester identifier for budget blocking API
    this.sapRequester = process.env.SAP_REQUESTER || 'REFMS';
  }

  /**
   * Check if SAP integration is configured
   */
  private isConfigured(): boolean {
    if (process.env.SAP_BYPASS === 'true') {
      logger.info('[SAP] SAP integration explicitly bypassed via SAP_BYPASS env variable');
      return false;
    }

    return !!this.sapBaseUrl && !!this.sapUsername && !!this.sapPassword;
  }

  /**
   * Get CSRF token from SAP service (required for POST/PUT/DELETE operations)
   * SAP OData services require CSRF tokens for state-changing operations
   * 
   * Process:
   * 1. Make GET request to service root with header "x-csrf-token: fetch"
   * 2. Include query params: $format=json&sap-client=200
   * 3. Extract CSRF token from response headers
   * 4. Extract cookies from response (SAP session cookies)
   * 5. Return both token and cookies for use in POST requests
   * 
   * @returns Object with csrfToken and cookies, or null if failed
   */
  private async getCsrfToken(serviceName: string): Promise<{ csrfToken: string; cookies: string } | null> {
    try {
      // Build service root URL with required query parameters
      const serviceRootUrl = `/sap/opu/odata/sap/${serviceName}/`;
      const queryParams = new URLSearchParams({
        '$format': 'json'
      });
      const fullUrl = `${this.sapBaseUrl}${serviceRootUrl}?${queryParams.toString()}`;

      logger.debug(`[SAP] Fetching CSRF token from service: ${serviceName}`);
      logger.debug(`[SAP] CSRF token request URL: ${fullUrl}`);

      // Use standalone axios request with Basic Auth in header
      // We need to capture cookies from this response to use in POST request
      const response = await axios.get(fullUrl, {
        auth: {
          username: this.sapUsername!,
          password: this.sapPassword!
        },
        headers: {
          'x-csrf-token': 'fetch', // Lowercase as per SAP requirement
          'Accept': 'application/json',
          'Content-Type': 'application/json'
        },
        timeout: this.sapTimeout,
        httpsAgent: process.env.SAP_DISABLE_SSL_VERIFY === 'true' ? new https.Agent({
          rejectUnauthorized: false
        }) : undefined,
        validateStatus: (status) => status < 500, // Don't throw on 4xx
        // Enable cookie handling to capture SAP session cookies
        withCredentials: true
      });

      // SAP returns CSRF token in response headers (check multiple case variations)
      const csrfToken = response.headers['x-csrf-token'] ||
        response.headers['X-CSRF-Token'] ||
        response.headers['X-Csrf-Token'] ||
        response.headers['x-csrf-token'];

      // Extract cookies from response headers
      // SAP sets cookies like: SAP_SESSIONID_DRE_200 and sap-usercontext
      const setCookieHeaders = response.headers['set-cookie'] as string | string[] | undefined;
      let cookies = '';

      if (setCookieHeaders) {
        if (Array.isArray(setCookieHeaders)) {
          // Extract cookie values from Set-Cookie headers
          cookies = setCookieHeaders.map((cookie: string) => {
            // Extract cookie name=value (before semicolon)
            return cookie.split(';')[0].trim();
          }).join('; ');
        } else {
          // It's a string
          cookies = setCookieHeaders.split(';')[0].trim();
        }
      }

      // Log full GET response for debugging
      logger.info(`[SAP] GET Response Status: ${response.status} ${response.statusText || ''}`);
      logger.info(`[SAP] GET Response Headers:`, JSON.stringify(response.headers, null, 2));
      logger.info(`[SAP] GET Response Data:`, JSON.stringify(response.data, null, 2));

      if (csrfToken && typeof csrfToken === 'string' && csrfToken !== 'fetch') {
        logger.info(`[SAP] CSRF token obtained successfully (length: ${csrfToken.length})`);
        logger.debug(`[SAP] CSRF token preview: ${csrfToken.substring(0, 20)}...`);

        if (cookies) {
          logger.debug(`[SAP] Session cookies captured: ${cookies.substring(0, 50)}...`);
        } else {
          logger.warn('[SAP] No cookies found in CSRF token response - POST may fail');
        }

        return { csrfToken, cookies };
      }

      logger.warn('[SAP] CSRF token not found in response headers or invalid');
      logger.debug('[SAP] Response status:', response.status);
      logger.debug('[SAP] Available headers:', Object.keys(response.headers).filter(h => h.toLowerCase().includes('csrf')));
      return null;
    } catch (error) {
      const axiosError = error as AxiosError;

      if (axiosError.response) {
        logger.error(`[SAP] Failed to get CSRF token: ${axiosError.response.status} ${axiosError.response.statusText}`);
        logger.error(`[SAP] Response data:`, axiosError.response.data);

        if (axiosError.response.status === 401 || axiosError.response.status === 403) {
          logger.error('[SAP] Authentication failed while fetching CSRF token - check SAP credentials');
        } else if (axiosError.response.status === 404) {
          logger.error('[SAP] Service not found - check SAP_SERVICE_NAME configuration');
        }
      } else if (axiosError.request) {
        logger.error('[SAP] No response received while fetching CSRF token:', axiosError.message);
        if (axiosError.code === 'ECONNREFUSED') {
          logger.error('[SAP] Connection refused - check if SAP server is reachable');
        } else if (axiosError.code === 'ENOTFOUND') {
          logger.error('[SAP] Host not found - check SAP_BASE_URL');
        }
      } else {
        logger.error('[SAP] Error setting up CSRF token request:', error instanceof Error ? error.message : 'Unknown error');
      }

      return null;
    }
  }

  /**
   * Create axios instance with basic auth for SAP API calls
   */
  private createSapClient() {
    // Check if SSL verification should be disabled (for testing with self-signed certs)
    const disableSSLVerification = process.env.SAP_DISABLE_SSL_VERIFY === 'true';

    const client = axios.create({
      baseURL: this.sapBaseUrl,
      timeout: this.sapTimeout,
      auth: {
        username: this.sapUsername!,
        password: this.sapPassword!
      },
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json'
      },
      validateStatus: (status) => status < 500, // Don't throw on 4xx errors
      // SSL/TLS configuration
      httpsAgent: disableSSLVerification ? new https.Agent({
        rejectUnauthorized: false
      }) : undefined
    });

    client.interceptors.request.use(
      (config) => {
        return config;
      },
      (error) => {
        logger.error('[SAP] Request interceptor error:', error);
        return Promise.reject(error);
      }
    );

    // Add response interceptor for debugging
    client.interceptors.response.use(
      (response) => {
        logger.debug(`[SAP] Response status: ${response.status}`);
        return response;
      },
      (error) => {
        if (error.response) {
          logger.error(`[SAP] Response error: ${error.response.status} ${error.response.statusText}`);
          logger.error(`[SAP] Response data:`, error.response.data);
        } else if (error.request) {
          logger.error('[SAP] No response received:', error.message);
          if (error.code === 'ECONNREFUSED') {
            logger.error('[SAP] Connection refused - check if SAP server is reachable');
          } else if (error.code === 'ENOTFOUND') {
            logger.error('[SAP] Host not found - check SAP_BASE_URL');
          } else if (error.code === 'CERT_HAS_EXPIRED' || error.code === 'UNABLE_TO_VERIFY_LEAF_SIGNATURE') {
            logger.error('[SAP] SSL certificate error');
          }
        }
        return Promise.reject(error);
      }
    );

    return client;
  }

  /**
   * Build SAP OData endpoint URL
   * @param entitySet - OData entity set name (e.g., GetSenderDataSet)
   * @param serviceName - Optional service name override (defaults to configured service)
   * @returns Full OData endpoint path
   */
  private buildODataEndpoint(entitySet: string, serviceName?: string): string {
    const service = serviceName || this.sapServiceName;
    return `/sap/opu/odata/sap/${service}/${entitySet}`;
  }

  /**
   * Validate IO number and get IO details
   * @param ioNumber - IO (Internal Order) number from SAP
   * @returns IO details including available balance, blocked amount, etc.
   */
  async validateIONumber(ioNumber: string): Promise<{
    isValid: boolean;
    ioNumber: string;
    availableBalance: number;
    blockedAmount: number;
    remainingBalance: number;
    currency: string;
    description?: string;
    error?: string;
  }> {
    try {
      if (!this.isConfigured()) {
        logger.warn('[SAP] SAP integration not configured, returning mock data');
        // Return mock data for development/testing
        return {
          isValid: true,
          ioNumber,
          availableBalance: 1000000,
          blockedAmount: 0,
          remainingBalance: 1000000,
          currency: 'INR',
          description: 'Mock IO Data (SAP not configured)'
        };
      }

      const sapClient = this.createSapClient();

      // SAP OData endpoint: GetSenderDataSet with filter on IONumber
      // Service name is configurable via SAP_SERVICE_NAME env variable
      const endpoint = this.buildODataEndpoint('GetSenderDataSet');

      // Build OData query parameters matching the working URL format
      // $filter: Filter by IO number
      // $select: Select specific fields (Sender, ResponseDate, GetIODetailsSet01)
      // $expand: Expand the nested GetIODetailsSet01 entity set to get IO details
      // $format: Explicitly request JSON format
      const queryParams = new URLSearchParams({
        '$filter': `IONumber eq '${ioNumber}'`,
        '$select': 'Sender,ResponseDate,GetIODetailsSet01',
        '$expand': 'GetIODetailsSet01',
        '$format': 'json'
      });

      const fullUrl = `${endpoint}?${queryParams.toString()}`;

      logger.info(`[SAP] Validating IO number: ${ioNumber} using service: ${this.sapServiceName}`);
      logger.debug(`[SAP] Request URL: ${this.sapBaseUrl}${fullUrl}`);

      const response = await sapClient.get(fullUrl);

      if (response.status === 200 && response.data) {
        // SAP OData response format: { d: { results: [...] } }
        const results = response.data.d?.results || response.data.results || [];

        if (results.length === 0) {
          logger.warn(`[SAP] IO number ${ioNumber} not found in SAP`);
          return {
            isValid: false,
            ioNumber,
            availableBalance: 0,
            blockedAmount: 0,
            remainingBalance: 0,
            currency: 'INR',
            error: 'IO number not found in SAP system'
          };
        }

        // Get first result (should be only one for a specific IO number)
        const senderData = results[0];

        // IO details are in the expanded GetIODetailsSet01 entity set
        // Structure: senderData.GetIODetailsSet01.results[0]
        const ioDetailsSet = senderData.GetIODetailsSet01;

        if (!ioDetailsSet || !ioDetailsSet.results || !Array.isArray(ioDetailsSet.results) || ioDetailsSet.results.length === 0) {
          logger.warn(`[SAP] No IO details found in expanded GetIODetailsSet01 for IO ${ioNumber}`);
          return {
            isValid: false,
            ioNumber,
            availableBalance: 0,
            blockedAmount: 0,
            remainingBalance: 0,
            currency: 'INR',
            error: 'IO details not found in SAP response'
          };
        }

        // Get the first IO detail from the results array
        const ioDetails = ioDetailsSet.results[0];

        // Map SAP response fields to our format based on actual response structure:
        // - AvailableAmount: string with trailing space (e.g., "14333415.00 ")
        // - IODescription: description text
        // - IONumber: IO number
        // - BlockedAmount: may not be present in response, default to 0
        // - Currency: may not be present, default to INR

        // Parse AvailableAmount - it's a string that may have trailing spaces
        const availableAmountStr = (ioDetails.AvailableAmount || '0').toString().trim();
        const availableBalance = parseFloat(availableAmountStr) || 0;

        // BlockedAmount may not be in the response, default to 0
        // If it exists, it might also be a string with trailing space
        const blockedAmountStr = (ioDetails.BlockedAmount || ioDetails.Blocked || '0').toString().trim();
        const blockedAmount = parseFloat(blockedAmountStr) || 0;

        const remainingBalance = availableBalance - blockedAmount;

        // Currency may not be in response, default to INR
        const currency = (ioDetails.Currency || ioDetails.CurrencyCode || ioDetails.Curr || 'INR').toString().trim();

        // Description from IODescription field
        const description = ioDetails.IODescription || ioDetails.Description || ioDetails.Text || ioDetails.ShortText || undefined;

        // IO Number from the IO details
        const validatedIONumber = ioDetails.IONumber || ioDetails.InternalOrder || ioNumber;

        logger.info(`[SAP] IO ${validatedIONumber} validated successfully. Available: ${availableBalance}, Blocked: ${blockedAmount}, Remaining: ${remainingBalance} ${currency}`);

        return {
          isValid: true,
          ioNumber: validatedIONumber,
          availableBalance,
          blockedAmount,
          remainingBalance,
          currency,
          description
        };
      } else if (response.status === 401 || response.status === 403) {
        logger.error('[SAP] Authentication failed - check SAP credentials');
        return {
          isValid: false,
          ioNumber,
          availableBalance: 0,
          blockedAmount: 0,
          remainingBalance: 0,
          currency: 'INR',
          error: 'SAP authentication failed - invalid credentials'
        };
      } else if (response.status === 404) {
        logger.warn(`[SAP] IO number ${ioNumber} not found (404)`);
        return {
          isValid: false,
          ioNumber,
          availableBalance: 0,
          blockedAmount: 0,
          remainingBalance: 0,
          currency: 'INR',
          error: 'IO number not found in SAP system'
        };
      } else if (response.status === 501) {
        logger.error(`[SAP] Not Implemented (501) - Check URL format and OData query parameters`);
        logger.error(`[SAP] Request URL was: ${this.sapBaseUrl}${fullUrl}`);
        return {
          isValid: false,
          ioNumber,
          availableBalance: 0,
          blockedAmount: 0,
          remainingBalance: 0,
          currency: 'INR',
          error: 'SAP API returned 501 Not Implemented - check OData query format'
        };
      } else {
        logger.error(`[SAP] Unexpected response status: ${response.status}`);
        logger.error(`[SAP] Response data:`, response.data);
        return {
          isValid: false,
          ioNumber,
          availableBalance: 0,
          blockedAmount: 0,
          remainingBalance: 0,
          currency: 'INR',
          error: `SAP API returned status ${response.status}`
        };
      }
    } catch (error) {
      const axiosError = error as AxiosError;

      if (axiosError.response) {
        // SAP returned an error response
        logger.error(`[SAP] Error validating IO number ${ioNumber}:`, {
          status: axiosError.response.status,
          statusText: axiosError.response.statusText,
          data: axiosError.response.data
        });

        return {
          isValid: false,
          ioNumber,
          availableBalance: 0,
          blockedAmount: 0,
          remainingBalance: 0,
          currency: 'INR',
          error: `SAP API error: ${axiosError.response.status} ${axiosError.response.statusText}`
        };
      } else if (axiosError.request) {
        // Request was made but no response received
        logger.error(`[SAP] No response from SAP API for IO ${ioNumber}:`, axiosError.message);
        return {
          isValid: false,
          ioNumber,
          availableBalance: 0,
          blockedAmount: 0,
          remainingBalance: 0,
          currency: 'INR',
          error: `SAP API connection failed: ${axiosError.message}`
        };
      } else {
        // Error setting up request
        logger.error('[SAP] Error setting up SAP API request:', error);
        return {
          isValid: false,
          ioNumber,
          availableBalance: 0,
          blockedAmount: 0,
          remainingBalance: 0,
          currency: 'INR',
          error: error instanceof Error ? error.message : 'Unknown error'
        };
      }
    }
  }

  /**
   * Block budget in SAP for a claim request
   * @param ioNumber - IO number
   * @param amount - Amount to block
   * @param requestNumber - Request number for reference
   * @param description - Description of the block
   * @returns Blocking confirmation details
   */
  async blockBudget(
    ioNumber: string,
    amount: number,
    requestNumber: string,
    description?: string
  ): Promise<{
    success: boolean;
    blockId?: string;
    blockedAmount: number;
    remainingBalance: number;
    error?: string;
  }> {
    try {
      if (!this.isConfigured()) {
        logger.warn('[SAP] SAP integration not configured, budget blocking skipped');
        return {
          success: true,
          blockedAmount: amount,
          remainingBalance: 1000000 - amount,
          error: 'SAP not configured - budget blocking simulated'
        };
      }

      const sapClient = this.createSapClient();

      // SAP OData endpoint for budget blocking
      // Service: ZFI_BUDGET_BLOCK_API_SRV
      // Entity Set: RequesterInputSet
      const endpoint = `/sap/opu/odata/sap/${this.sapBlockServiceName}/RequesterInputSet`;

      // Format current date/time in ISO format: "2025-08-29T10:51:00"
      const now = new Date();
      const requestDateTime = now.toISOString().replace(/\.\d{3}Z$/, ''); // Remove milliseconds and Z

      // Build request payload matching SAP API structure
      const requestPayload = {
        Request_Date_Time: requestDateTime,
        Requester: this.sapRequester,
        lt_io_input: [
          {
            IONumber: ioNumber,
            Amount: amount.toString() // Amount as string
          }
        ],
        lt_io_output: [],
        ls_response: []
      };

      logger.info(`[SAP] Blocking budget for IO ${ioNumber}, Amount: ${amount}, Request: ${requestNumber}`);
      logger.debug(`[SAP] Budget block request payload:`, JSON.stringify(requestPayload, null, 2));

      // Get CSRF token and cookies for POST request (SAP OData requires both)
      // SAP sets session cookies during CSRF token fetch that must be included in POST
      const csrfData = await this.getCsrfToken(this.sapBlockServiceName);

      if (!csrfData || !csrfData.csrfToken) {
        logger.warn('[SAP] CSRF token not available, request may fail with CSRF validation error');
        logger.warn('[SAP] This is expected if SAP requires CSRF tokens for POST requests');
      }

      // Build headers with CSRF token, cookies, and other required headers
      // Force JSON format via Accept header (SAP returns XML by default for POST)
      const headers: Record<string, string> = {
        'Accept': 'application/json, application/atom+xml;q=0.9', // Prefer JSON, fallback to XML
        'Content-Type': 'application/json'
      };

      // Add CSRF token if available (required by SAP for POST/PUT/DELETE)
      // Use lowercase 'x-csrf-token' as per SAP requirement
      if (csrfData?.csrfToken) {
        headers['x-csrf-token'] = csrfData.csrfToken;
        logger.debug(`[SAP] CSRF token added to request headers`);
      } else {
        logger.warn('[SAP] CSRF token not available - request may fail with CSRF validation error');
      }

      // Add cookies if available (SAP session cookies required for POST)
      // Cookies like: SAP_SESSIONID_DRE_200 and sap-usercontext
      if (csrfData?.cookies) {
        headers['Cookie'] = csrfData.cookies;
        logger.debug(`[SAP] Session cookies added to request headers`);
      } else {
        logger.warn('[SAP] No session cookies available - request may fail');
      }

      // Some SAP systems also require these headers
      headers['X-Requested-With'] = 'XMLHttpRequest';

      // NOTE: Do NOT add query parameters ($format, sap-client) to POST requests
      // SAP OData does not allow SystemQueryOptions in POST requests
      // Query parameters are only allowed for GET requests
      // Use the endpoint directly without query parameters
      const urlWithParams = endpoint;

      logger.debug(`[SAP] POST request URL: ${urlWithParams}`);
      logger.debug(`[SAP] Request headers (CSRF token and cookies masked):`, {
        ...headers,
        'x-csrf-token': csrfData?.csrfToken ? `${csrfData.csrfToken.substring(0, 10)}...` : 'not set',
        'Cookie': csrfData?.cookies ? `${csrfData.cookies.substring(0, 30)}...` : 'not set'
      });
      logger.debug(`[SAP] Using username: ${this.sapUsername}`);

      // Ensure auth is explicitly included in POST request config
      // The axios instance has auth configured, but we'll include it explicitly to be safe
      // This ensures auth is sent even if the instance config is overridden
      const postConfig = {
        headers,
        auth: {
          username: this.sapUsername!,
          password: this.sapPassword!
        },
        timeout: this.sapTimeout,
        httpsAgent: process.env.SAP_DISABLE_SSL_VERIFY === 'true' ? new https.Agent({
          rejectUnauthorized: false
        }) : undefined,
        validateStatus: (status: number) => status < 500 // Don't throw on 4xx
      };

      logger.debug(`[SAP] POST request config prepared (auth included)`);
      const response = await sapClient.post(urlWithParams, requestPayload, postConfig);

      // Log response status for visibility
      logger.info(`[SAP] POST Response Status: ${response.status} ${response.statusText || ''}`);
      // Headers relegated to debug level
      logger.debug(`[SAP] POST Response Headers:`, JSON.stringify(response.headers, null, 2));

      // Check if response is XML (SAP returns XML/Atom by default for POST)
      const contentType = response.headers['content-type'] || '';
      const isXML = contentType.includes('xml') || contentType.includes('atom') ||
        (typeof response.data === 'string' && response.data.trim().startsWith('<'));

      let responseData: any = response.data;

      // Parse XML if needed
      if (isXML && typeof response.data === 'string') {
        logger.info(`[SAP] Response is XML, parsing to JSON...`);
        try {
          const parser = new XMLParser({
            ignoreAttributes: false,
            attributeNamePrefix: '@_',
            textNodeName: '#text',
            parseAttributeValue: true,
            trimValues: true,
            preserveOrder: false,
            // Note: fast-xml-parser v4+ preserves namespace prefixes by default
            // So 'd:Available_Amount' should remain as 'd:Available_Amount' in the parsed object
            // If namespaces are being stripped, we check both 'd:Available_Amount' and 'Available_Amount'
          });
          responseData = parser.parse(response.data);
          logger.info(`[SAP] XML parsed successfully`);
        } catch (xmlError) {
          logger.error(`[SAP] Failed to parse XML response:`, xmlError);
          // Continue with original data
        }
      }

      // Log response data summary
      if (responseData) {
        if (responseData.entry) {
          logger.info(`[SAP] Response has 'entry' structure`);
        } else if (responseData.d) {
          logger.info(`[SAP] Response has OData 'd' wrapper`);
        }
      }

      // Log the request URL
      logger.info(`[SAP] POST Request URL: ${urlWithParams}`);
      // Payload relegated to debug level
      logger.debug(`[SAP] POST Request Payload:`, JSON.stringify(requestPayload, null, 2));

      if (response.status === 200 || response.status === 201) {
        // Parse SAP response
        // Response structure may vary, but typically contains:
        // - Success indicator
        // - Blocked amount confirmation
        // - Remaining balance (in lt_io_output[0].Available_Amount for XML)
        // - Block ID or reference number

        // Helper function to extract remaining balance from various field names
        // For XML: Available_Amount in lt_io_output[0] (may be prefixed with 'd:' namespace)
        // For JSON: RemainingBalance, Remaining, Available_Amount, etc.
        const extractRemainingBalance = (obj: any): number => {
          if (!obj) {
            logger.debug(`[SAP] extractRemainingBalance: obj is null/undefined`);
            return 0;
          }

          // Helper to extract value from field (handles both direct values and nested #text nodes)
          const getFieldValue = (fieldName: string): any => {
            const field = obj[fieldName];
            if (field === undefined || field === null) return null;

            // If it's an object with #text property (XML parser sometimes does this)
            if (typeof field === 'object' && field['#text'] !== undefined) {
              return field['#text'];
            }

            // Direct value
            return field;
          };

          // Try various field name variations (both JSON and XML formats)
          // XML namespace prefixes: 'd:Available_Amount', 'd:RemainingBalance', etc.
          // IMPORTANT: Check 'd:Available_Amount' first as that's what SAP returns in XML
          // Also check without namespace prefix as parser might strip it
          const value = getFieldValue('d:Available_Amount') ??  // XML format with namespace prefix (PRIORITY)
            getFieldValue('Available_Amount') ??     // XML format without prefix (parser might strip 'd:')
            getFieldValue('d:AvailableAmount') ??    // CamelCase variation with prefix
            getFieldValue('AvailableAmount') ??      // CamelCase variation without prefix
            getFieldValue('d:RemainingBalance') ??
            getFieldValue('RemainingBalance') ??
            getFieldValue('RemainingAmount') ??
            getFieldValue('Remaining') ??
            getFieldValue('AvailableBalance') ??
            getFieldValue('Balance') ??
            getFieldValue('Available') ??
            null;

          if (value === null || value === undefined) {
            logger.debug(`[SAP] extractRemainingBalance: No value found. Object keys:`, Object.keys(obj));
            // Log all keys that might be relevant
            const relevantKeys = Object.keys(obj).filter(k =>
              k.toLowerCase().includes('available') ||
              k.toLowerCase().includes('amount') ||
              k.toLowerCase().includes('remaining') ||
              k.toLowerCase().includes('balance')
            );
            if (relevantKeys.length > 0) {
              logger.debug(`[SAP] extractRemainingBalance: Found relevant keys but no value:`, relevantKeys.map(k => ({ key: k, value: obj[k], type: typeof obj[k] })));
            }
            return 0;
          }

          // Convert to string first, then parse (handles both string "14291525.00" and number)
          const valueStr = value?.toString().trim() || '0';
          const parsed = parseFloat(valueStr);

          if (isNaN(parsed)) {
            logger.warn(`[SAP] extractRemainingBalance: Failed to parse value "${valueStr}" as number`);
            return 0;
          }

          logger.debug(`[SAP] extractRemainingBalance: Extracted value "${valueStr}" -> ${parsed}`);
          return parsed;
        };

        // Helper function to extract blocked amount
        const extractBlockedAmount = (obj: any): number => {
          if (!obj) return amount;

          const value = obj.BlockedAmount ||
            obj.Amount ||
            obj.Blocked ||
            amount.toString();

          const parsed = parseFloat(value?.toString() || amount.toString());
          return isNaN(parsed) ? amount : parsed;
        };

        // Handle different possible response structures
        let success = false;
        let blockedAmount = amount;
        let remainingBalance = 0;
        let blockId: string | undefined;

        // Parse XML structure: entry -> link[@rel='lt_io_output'] -> inline -> feed -> entry -> content -> properties
        // Or JSON structure: { d: {...} } or { lt_io_output: [...] }

        // Check for XML structure first (parsed XML from fast-xml-parser)
        let ioOutputData: any = null;
        let message = '';
        let mainEntryProperties: any = null;

        // XML structure: entry.link (array) -> find link with @rel='lt_io_output' -> inline.feed.entry
        // OR JSON OData format: { d: { lt_io_output: { results: [...] } } }
        if (responseData.d) {
          // Handle OData JSON format
          if (responseData.d.entry) {
            responseData = responseData.d;
          } else if (responseData.d.lt_io_output) {
            // Extract from d.lt_io_output
            const ltIoOutput = responseData.d.lt_io_output;
            if (ltIoOutput.results && Array.isArray(ltIoOutput.results) && ltIoOutput.results.length > 0) {
              ioOutputData = ltIoOutput.results[0];
              message = ioOutputData.Message || ioOutputData['d:Message'] || '';
              logger.info(`[SAP] ✅ Extracted data from JSON OData format (d.lt_io_output.results[0])`);
            } else if (typeof ltIoOutput === 'object' && !Array.isArray(ltIoOutput)) {
              if (ltIoOutput.results && Array.isArray(ltIoOutput.results) && ltIoOutput.results.length > 0) {
                ioOutputData = ltIoOutput.results[0];
                message = ioOutputData?.Message || ioOutputData?.['d:Message'] || '';
                logger.info(`[SAP] ✅ Extracted data from JSON OData format (d.lt_io_output.results[0])`);
              } else {
                ioOutputData = ltIoOutput;
                message = ioOutputData.Message || ioOutputData['d:Message'] || '';
                logger.info(`[SAP] ✅ Extracted data from JSON OData format (d.lt_io_output)`);
              }
            } else if (Array.isArray(ltIoOutput) && ltIoOutput.length > 0) {
              ioOutputData = ltIoOutput[0];
              message = ioOutputData?.Message || ioOutputData?.['d:Message'] || '';
              logger.info(`[SAP] ✅ Extracted data from JSON OData format (d.lt_io_output[0])`);
            }
          } else if (Object.keys(responseData.d).some(key => key.includes('link') || key.includes('content'))) {
            responseData = responseData.d;
          }
        }

        // Sometimes XML parser might create the root element with a different name
        // Check if responseData itself IS the entry (if root element was <entry>)
        let actualEntry = responseData.entry;
        if (!actualEntry && responseData && typeof responseData === 'object' && !Array.isArray(responseData)) {
          const currentKeys = Object.keys(responseData);
          if (currentKeys.includes('link') || currentKeys.includes('content') || currentKeys.includes('id') || currentKeys.includes('title')) {
            actualEntry = responseData;
          }
        }

        // Check if responseData might be an array (sometimes XML parser returns arrays)
        if (Array.isArray(responseData) && responseData.length > 0 && responseData[0]?.entry) {
          responseData = responseData[0];
        }

        // Use actualEntry if we found it, otherwise try responseData.entry
        const entry = actualEntry || responseData.entry;

        if (entry && !ioOutputData) {

          // Also check main entry properties (sometimes Available_Amount is here)
          const mainContent = entry.content || {};
          mainEntryProperties = mainContent['m:properties'] || mainContent.properties || (mainContent['@_type'] === 'application/xml' ? mainContent : null);

          if (mainEntryProperties) {
            logger.info(`[SAP] Found main entry properties, keys:`, Object.keys(mainEntryProperties));
          }

          // Find lt_io_output link in XML structure
          const links = Array.isArray(entry.link) ? entry.link : (entry.link ? [entry.link] : []);
          const ioOutputLink = links.find((link: any) => {
            const rel = link['@_rel'] || link.rel || '';
            const title = link['@_title'] || link.title || '';
            return rel.includes('lt_io_output') || title === 'IOOutputSet' || title === 'lt_io_output';
          });

          if (ioOutputLink?.inline?.feed?.entry) {
            const ioEntry = Array.isArray(ioOutputLink.inline.feed.entry)
              ? ioOutputLink.inline.feed.entry[0]
              : ioOutputLink.inline.feed.entry;

            const content = ioEntry.content || {};
            const properties = content['m:properties'] || content.properties || (content['@_type'] === 'application/xml' ? content : null);

            if (properties) {
              ioOutputData = properties;
              message = ioOutputData['d:Message'] || ioOutputData.Message || ioOutputData['#text'] || '';
              logger.info(`[SAP] ✅ Found XML structure - lt_io_output data extracted`);
            }
          }
        }

        // Extract data from ioOutputData (already extracted above for both XML and JSON formats)
        if (ioOutputData) {
          // XML parsed structure - extract from lt_io_output properties
          // Available_Amount is the remaining balance after blocking
          success = message.includes('Successful') || message.includes('Success') || !message.includes('Error');
          blockedAmount = amount; // Use the amount we sent (from lt_io_input)
          remainingBalance = extractRemainingBalance(ioOutputData); // Available_Amount from XML

          // If not found in lt_io_output, try main entry properties
          if (remainingBalance === 0 && mainEntryProperties) {
            logger.info(`[SAP] Available_Amount not found in lt_io_output, trying main entry properties`);
            remainingBalance = extractRemainingBalance(mainEntryProperties);
            if (remainingBalance > 0) {
              logger.info(`[SAP] Found Available_Amount in main entry properties: ${remainingBalance}`);
            }
          }

          // Helper function to extract SAP reference number (similar to extractRemainingBalance)
          const extractSapReference = (obj: any): string | undefined => {
            if (!obj) return undefined;

            const getFieldValue = (fieldName: string): any => {
              const field = obj[fieldName];
              if (field === undefined || field === null) return null;

              // If it's an object with #text property (XML parser sometimes does this)
              if (typeof field === 'object' && field['#text'] !== undefined) {
                return field['#text'];
              }

              // Direct value
              return field;
            };

            // Try various field name variations for SAP reference number
            const value = getFieldValue('d:Sap_Reference_no') ??  // XML format with namespace prefix (PRIORITY)
              getFieldValue('Sap_Reference_no') ??     // XML format without prefix
              getFieldValue('d:SapReferenceNo') ??
              getFieldValue('SapReferenceNo') ??
              getFieldValue('d:Reference') ??
              getFieldValue('Reference') ??
              getFieldValue('d:BlockId') ??
              getFieldValue('BlockId') ??
              getFieldValue('d:DocumentNumber') ??
              getFieldValue('DocumentNumber') ??
              null;

            if (value === null || value === undefined) {
              logger.debug(`[SAP] extractSapReference: No value found. Object keys:`, Object.keys(obj));
              return undefined;
            }

            // Convert to string and trim
            const valueStr = String(value).trim();
            logger.debug(`[SAP] extractSapReference: Extracted value "${valueStr}"`);
            return valueStr || undefined;
          };

          // Extract SAP reference number using helper function
          blockId = extractSapReference(ioOutputData) || extractSapReference(mainEntryProperties) || undefined;

          // Log detailed information for debugging
          logger.info(`[SAP] Extracted from XML lt_io_output:`, {
            message,
            availableAmount: remainingBalance,
            sapReference: blockId,
            allKeys: Object.keys(ioOutputData),
            sampleKeys: Object.keys(ioOutputData).slice(0, 10), // First 10 keys for debugging
            foundInMainEntry: remainingBalance > 0 && mainEntryProperties ? true : false,
            ioOutputDataSample: Object.keys(ioOutputData).reduce((acc: any, key: string) => {
              if (key.toLowerCase().includes('available') ||
                key.toLowerCase().includes('amount') ||
                key.toLowerCase().includes('reference') ||
                key.toLowerCase().includes('sap')) {
                acc[key] = ioOutputData[key];
              }
              return acc;
            }, {})
          });
        } else if (responseData.ls_response && Array.isArray(responseData.ls_response) && responseData.ls_response.length > 0) {
          // Response in ls_response array
          const responseItem = responseData.ls_response[0];
          success = responseItem.Success !== false && responseItem.Message !== 'Error';
          blockedAmount = extractBlockedAmount(responseItem);
          remainingBalance = extractRemainingBalance(responseItem);
          blockId = responseItem.BlockId || responseItem.Reference || responseItem.DocumentNumber || undefined;
        } else if (responseData.lt_io_output && Array.isArray(responseData.lt_io_output) && responseData.lt_io_output.length > 0) {
          // Response in lt_io_output array (JSON format)
          const outputItem = responseData.lt_io_output[0];
          success = outputItem.Success !== false && outputItem.Message !== 'Error';
          blockedAmount = extractBlockedAmount(outputItem);
          remainingBalance = extractRemainingBalance(outputItem);
          blockId = outputItem.BlockId || outputItem.Reference || outputItem.DocumentNumber || outputItem.Sap_Reference_no || undefined;
        } else if (responseData.Success !== undefined) {
          // Direct success field
          success = responseData.Success === true || responseData.Success === 'true';
          blockedAmount = extractBlockedAmount(responseData);
          remainingBalance = extractRemainingBalance(responseData);
          blockId = responseData.BlockId || responseData.Reference || responseData.DocumentNumber || undefined;
        } else {
          // If no clear success indicator, assume success if status is 200/201
          success = true;
          blockedAmount = amount;
          remainingBalance = 0; // Will be calculated in dealerClaim.service
          logger.warn('[SAP] Budget block response structure unclear, assuming success');
          logger.warn('[SAP] Response data keys:', Object.keys(responseData || {}));
        }

        // Log what we extracted
        logger.info(`[SAP] Extracted from response:`, {
          success,
          blockedAmount,
          remainingBalance,
          blockId,
          responseStructure: ioOutputData ? 'XML lt_io_output' : responseData.d ? 'JSON d' : responseData.ls_response ? 'ls_response' : responseData.lt_io_output ? 'lt_io_output' : 'unknown',
          note: remainingBalance === 0 ? '⚠️ Remaining balance is 0 - will be calculated from availableBalance - blockedAmount' : '✅ Remaining balance from SAP response',
          hasIoOutputData: !!ioOutputData,
          ioOutputDataKeys: ioOutputData ? Object.keys(ioOutputData) : null,
          hasMainEntryProperties: !!mainEntryProperties,
          mainEntryPropertiesKeys: mainEntryProperties ? Object.keys(mainEntryProperties) : null
        });

        // If ioOutputData exists but we didn't extract values, log detailed info
        if (ioOutputData && (remainingBalance === 0 || !blockId)) {
          logger.warn(`[SAP] ⚠️ ioOutputData exists but extraction failed. Full ioOutputData:`, JSON.stringify(ioOutputData, null, 2));
          logger.warn(`[SAP] ⚠️ All keys in ioOutputData:`, Object.keys(ioOutputData));
          logger.warn(`[SAP] ⚠️ Sample values:`, Object.keys(ioOutputData).slice(0, 10).reduce((acc: any, key: string) => {
            acc[key] = ioOutputData[key];
            return acc;
          }, {}));
        }

        // If remaining balance is 0, log the full response structure for debugging
        if (remainingBalance === 0 && response.status === 200 || response.status === 201) {
          logger.warn(`[SAP] ⚠️ Remaining balance is 0, but request was successful. Full response structure:`, JSON.stringify(responseData, null, 2));
        }

        if (success) {
          logger.info(`[SAP] Budget blocked successfully for IO ${ioNumber}. Blocked: ${blockedAmount}, Remaining: ${remainingBalance}`);

          // Only return blockId if SAP provided a reference number
          // Don't generate a fallback - we want the actual SAP document number
          if (blockId) {
            logger.info(`[SAP] SAP Reference Number received: ${blockId}`);
          } else {
            logger.warn(`[SAP] ⚠️ No SAP Reference Number (Sap_Reference_no) found in response`);
          }

          return {
            success: true,
            blockId: blockId || undefined, // Only return actual SAP reference number, no fallback
            blockedAmount,
            remainingBalance
          };
        } else {
          const errorMessage = responseData.Message || responseData.Error || 'Budget blocking failed';
          logger.error(`[SAP] Budget blocking failed for IO ${ioNumber}: ${errorMessage}`);
          return {
            success: false,
            blockedAmount: 0,
            remainingBalance: 0,
            error: errorMessage
          };
        }
      } else if (response.status === 401 || response.status === 403) {
        logger.error(`[SAP] Authentication failed during budget blocking (Status: ${response.status}) - check SAP credentials`);
        logger.error(`[SAP] Response data:`, response.data);
        logger.error(`[SAP] Response headers:`, response.headers);

        // Check if it's actually a CSRF error disguised as auth error
        const responseText = JSON.stringify(response.data || {});
        if (responseText.includes('CSRF') || responseText.includes('csrf') || responseText.includes('token')) {
          logger.error('[SAP] This might be a CSRF token validation error, not authentication');
          return {
            success: false,
            blockedAmount: 0,
            remainingBalance: 0,
            error: 'SAP CSRF token validation failed - token may have expired or be invalid'
          };
        }

        return {
          success: false,
          blockedAmount: 0,
          remainingBalance: 0,
          error: `SAP authentication failed (${response.status}) - check SAP credentials`
        };
      } else {
        // Handle 400 Bad Request - usually means invalid request format
        let errorMessage = `SAP API returned status ${response.status}`;

        if (response.status === 400) {
          errorMessage = 'SAP API returned 400 Bad Request - check request payload format';

          // Try to extract error message from response
          if (response.data) {
            try {
              const errorData = typeof response.data === 'string' ? JSON.parse(response.data) : response.data;

              // Check for common SAP error fields
              if (errorData.error) {
                errorMessage = errorData.error.message?.value || errorData.error.message || errorMessage;
              } else if (errorData.message) {
                errorMessage = errorData.message;
              } else if (errorData.Message) {
                errorMessage = errorData.Message;
              } else if (errorData.d?.error) {
                errorMessage = errorData.d.error.message?.value || errorData.d.error.message || errorMessage;
              }

              logger.error(`[SAP] SAP Error Details:`, JSON.stringify(errorData, null, 2));
            } catch (e) {
              logger.error(`[SAP] Error parsing response data:`, response.data);
            }
          }
        }

        logger.error(`[SAP] Unexpected response status during budget blocking: ${response.status}`);
        logger.error(`[SAP] Response data:`, response.data);
        return {
          success: false,
          blockedAmount: 0,
          remainingBalance: 0,
          error: errorMessage
        };
      }
    } catch (error) {
      const axiosError = error as AxiosError;

      if (axiosError.response) {
        // SAP returned an error response
        logger.error(`[SAP] Error blocking budget for IO ${ioNumber}:`, {
          status: axiosError.response.status,
          statusText: axiosError.response.statusText,
          data: axiosError.response.data
        });

        return {
          success: false,
          blockedAmount: 0,
          remainingBalance: 0,
          error: `SAP API error: ${axiosError.response.status} ${axiosError.response.statusText}`
        };
      } else if (axiosError.request) {
        // Request was made but no response received
        logger.error(`[SAP] No response from SAP API during budget blocking for IO ${ioNumber}:`, axiosError.message);
        return {
          success: false,
          blockedAmount: 0,
          remainingBalance: 0,
          error: `SAP API connection failed: ${axiosError.message}`
        };
      } else {
        // Error setting up request
        logger.error('[SAP] Error setting up budget blocking request:', error);
        return {
          success: false,
          blockedAmount: 0,
          remainingBalance: 0,
          error: error instanceof Error ? error.message : 'Unknown error'
        };
      }
    }
  }

  /**
   * Release blocked budget in SAP
   * @param ioNumber - IO number
   * @param amount - Amount to release
   * @param requestNumber - Request number for reference
   * @param blockId - Block ID from previous block operation (optional)
   * @returns Release confirmation
   */
  async releaseBudget(
    ioNumber: string,
    amount: number,
    requestNumber: string,
    blockId?: string
  ): Promise<{
    success: boolean;
    releasedAmount: number;
    error?: string;
  }> {
    try {
      if (!this.isConfigured()) {
        logger.warn('[SAP] SAP integration not configured, budget release skipped');
        return {
          success: true,
          releasedAmount: 0,
          error: 'SAP not configured - budget release simulated'
        };
      }



      logger.warn('[SAP] SAP budget release not implemented, simulating release');
      return {
        success: true,
        releasedAmount: 0,
        error: 'SAP API not implemented - budget release simulated'
      };
    } catch (error) {
      logger.error('[SAP] Error releasing budget:', error);
      return {
        success: false,
        releasedAmount: 0,
        error: error instanceof Error ? error.message : 'Unknown error'
      };
    }
  }

  /**
   * Get dealer information from SAP
   * @param dealerCode - Dealer code
   * @returns Dealer details from SAP
   */
  async getDealerInfo(dealerCode: string): Promise<{
    isValid: boolean;
    dealerCode: string;
    dealerName?: string;
    dealerEmail?: string;
    dealerPhone?: string;
    dealerAddress?: string;
    error?: string;
  }> {
    try {
      if (!this.isConfigured()) {
        logger.warn('[SAP] SAP integration not configured, returning mock dealer data');
        return {
          isValid: true,
          dealerCode,
          dealerName: `Dealer ${dealerCode}`,
          dealerEmail: `dealer${dealerCode}@example.com`,
          dealerPhone: '+91-XXXXXXXXXX',
          dealerAddress: 'Mock Address'
        };
      }



      logger.warn('[SAP] SAP dealer lookup not implemented, returning mock data');
      return {
        isValid: true,
        dealerCode,
        dealerName: `Dealer ${dealerCode}`,
        dealerEmail: `dealer${dealerCode}@example.com`,
        dealerPhone: '+91-XXXXXXXXXX',
        dealerAddress: 'Mock Address'
      };
    } catch (error) {
      logger.error('[SAP] Error getting dealer info:', error);
      return {
        isValid: false,
        dealerCode,
        error: error instanceof Error ? error.message : 'Unknown error'
      };
    }
  }
}

export const sapIntegrationService = new SAPIntegrationService();

