<?php

namespace Devikit\Ifirma\Api;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * iFirma API Client
 * 
 * iFirma API uses HMAC-SHA1 authentication with separate keys for:
 * - Abonent (subscriber data)
 * - Faktura (VAT invoices)
 * - Rachunek (non-VAT invoices)
 */
class Client {

	/**
	 * iFirma API URL
	 */
	const API_URL = 'https://www.ifirma.pl/iapi/';

	/**
	 * API Login (email)
	 * 
	 * @var string
	 */
	private $api_login;

	/**
	 * API Key for Abonent (subscriber) operations
	 * 
	 * @var string
	 */
	private $api_key_abonent;

	/**
	 * API Key for Faktura (VAT invoice) operations
	 * 
	 * @var string
	 */
	private $api_key_faktura;

	/**
	 * API Key for Rachunek (non-VAT invoice) operations
	 * 
	 * @var string
	 */
	private $api_key_rachunek;

	/**
	 * Constructor
	 * 
	 * @param string $api_login iFirma login (email)
	 * @param string $api_key_abonent API key for subscriber data
	 * @param string $api_key_faktura API key for VAT invoices
	 * @param string $api_key_rachunek API key for rachunki (optional)
	 */
	public function __construct( $api_login, $api_key_abonent = '', $api_key_faktura = '', $api_key_rachunek = '' ) {
		$this->api_login = $api_login;
		$this->api_key_abonent = $api_key_abonent;
		$this->api_key_faktura = $api_key_faktura;
		$this->api_key_rachunek = $api_key_rachunek;
	}

	/**
	 * Generate HMAC-SHA1 authentication hash
	 * 
	 * iFirma API hash formula: hmac(klucz, url + nazwaUsera + nazwaKlucza + requestContent)
	 * Result is hexadecimal string (40 chars for SHA1)
	 * 
	 * @param string $url Full URL for the request
	 * @param string $key API key (hex encoded)
	 * @param string $key_name Key name: 'abonent', 'faktura', 'rachunek', 'wydatek'
	 * @param string $content Request body content (empty for GET)
	 * @return string Authentication hash (hex)
	 */
	private function generate_auth_hash( $url, $key, $key_name, $content = '' ) {
		// Build message to sign: url + nazwaUsera + nazwaKlucza + requestContent
		$message = $url . $this->api_login . $key_name . $content;
		
		// Decode hex key to binary
		$key_binary = hex2bin( $key );
		
		// Generate HMAC-SHA1 and return as hexadecimal string
		return hash_hmac( 'sha1', $message, $key_binary, false );
	}

	/**
	 * Get headers for API requests
	 * 
	 * @param string $key_type Key type: 'abonent', 'faktura', 'rachunek'
	 * @param string $url Full URL (may contain query parameters)
	 * @param string $content Request body
	 * @return array
	 */
	private function get_headers( $key_type, $url, $content = '' ) {
		$key = '';
		$key_name = $key_type; // Key name for hash: abonent, faktura, rachunek
		
		switch ( $key_type ) {
			case 'abonent':
				$key = $this->api_key_abonent;
				break;
			case 'faktura':
				$key = $this->api_key_faktura;
				break;
			case 'rachunek':
				$key = $this->api_key_rachunek;
				break;
		}

		if ( empty( $key ) ) {
			return [];
		}

		// Per iFirma docs: for URLs with query params, hash must be built WITHOUT query params
		$url_for_hash = strtok( $url, '?' );
		
		$auth_hash = $this->generate_auth_hash( $url_for_hash, $key, $key_name, $content );

		return [
			'Authentication'   => 'IAPIS user=' . $this->api_login . ', hmac-sha1=' . $auth_hash,
			'Content-Type'     => 'application/json; charset=utf-8',
			'Accept'           => 'application/json',
		];
	}

	/**
	 * Get current accounting month and year from iFirma.
	 *
	 * Requires the Abonent API key. Returns an associative array with 'rok' and 'miesiac'.
	 *
	 * @return array{rok: int, miesiac: int}|WP_Error
	 */
	public function get_accounting_month() {
		if ( empty( $this->api_login ) || empty( $this->api_key_abonent ) ) {
			return new \WP_Error( 'no_abonent_key', __( 'API Key Abonent is required to read the accounting month', 'invoicing-integration-for-ifirma-and-woocommerce' ) );
		}

		$response = $this->get( 'abonent/miesiacksiegowy.json', 'abonent' );
		if ( is_wp_error( $response ) ) {
			return $response;
		}

		$rok     = isset( $response['response']['RokKsiegowy'] ) ? intval( $response['response']['RokKsiegowy'] ) : 0;
		$miesiac = isset( $response['response']['MiesiacKsiegowy'] ) ? intval( $response['response']['MiesiacKsiegowy'] ) : 0;

		if ( ! $rok || ! $miesiac ) {
			return new \WP_Error( 'invalid_response', __( 'Could not parse accounting month from iFirma response', 'invoicing-integration-for-ifirma-and-woocommerce' ) );
		}

		return [ 'rok' => $rok, 'miesiac' => $miesiac ];
	}

	/**
	 * Advance or rewind iFirma accounting month by one step.
	 *
	 * @param string $direction 'NAST' (next month) or 'POPRZ' (previous month)
	 * @return true|\WP_Error
	 */
	private function step_accounting_month( $direction ) {
		$response = $this->put( 'abonent/miesiacksiegowy.json', [ 'MiesiacKsiegowy' => $direction ], 'abonent' );
		if ( is_wp_error( $response ) ) {
			return $response;
		}
		return true;
	}

	/**
	 * Sync iFirma accounting month so it matches the month/year of a given document date.
	 *
	 * iFirma requires the active accounting month to match the document sale date
	 * (DataSprzedazy). The issue date (DataWystawienia) may be in a different month;
	 * numbering chronology is preserved relative to the issue date.
	 *
	 * Silently no-ops when the Abonent key is not configured.
	 * On API failure it logs a warning but does NOT block document creation.
	 *
	 * @param string $date Document date in Y-m-d format.
	 * @return void
	 */
	public function sync_accounting_month_to_date( $date ) {
		if ( empty( $this->api_login ) || empty( $this->api_key_abonent ) ) {
			return;
		}

		$ts = strtotime( (string) $date );
		if ( ! $ts ) {
			return;
		}

		$target_year  = intval( gmdate( 'Y', $ts ) );
		$target_month = intval( gmdate( 'n', $ts ) );

		// Safety cap: never step more than 24 months in either direction.
		$max_steps = 24;

		for ( $i = 0; $i < $max_steps; $i++ ) {
			$current = $this->get_accounting_month();

			if ( is_wp_error( $current ) ) {
				$this->log( 'Accounting month sync skipped: ' . $current->get_error_message(), 'warning' );
				return;
			}

			if ( $current['rok'] === $target_year && $current['miesiac'] === $target_month ) {
				return;
			}

			$behind = $current['rok'] < $target_year
				|| ( $current['rok'] === $target_year && $current['miesiac'] < $target_month );

			$step = $this->step_accounting_month( $behind ? 'NAST' : 'POPRZ' );

			if ( is_wp_error( $step ) ) {
				$this->log( 'Accounting month sync: step failed - ' . $step->get_error_message(), 'warning' );
				return;
			}
		}

		$this->log( 'Accounting month sync: safety limit reached without matching date ' . $date, 'warning' );
	}

	/**
	 * Test connection to iFirma API
	 * 
	 * Tests all configured API keys and returns detailed results.
	 * 
	 * @return array Results for each key type: ['abonent' => bool, 'faktura' => bool|null, 'rachunek' => bool|null]
	 */
	public function test_connection() {
		$results = [
			'abonent'  => false,
			'faktura'  => null, // null = not configured
			'rachunek' => null, // null = not configured
		];
		
		// Test Abonent key (required)
		if ( ! empty( $this->api_login ) && ! empty( $this->api_key_abonent ) ) {
			try {
				$response = $this->get( 'abonent/miesiacksiegowy.json', 'abonent' );
				$results['abonent'] = ! is_wp_error( $response );
			} catch ( \Exception $e ) {
				$this->log( 'Test connection (abonent) exception: ' . $e->getMessage(), 'error' );
				$results['abonent'] = false;
			}
		}
		
		// Test Faktura key (optional)
		if ( ! empty( $this->api_login ) && ! empty( $this->api_key_faktura ) ) {
			try {
				// Use fakturakraj/list.json with limit=1 for minimal data
				$response = $this->get( 'fakturakraj/list.json?limit=1', 'faktura' );
				$results['faktura'] = ! is_wp_error( $response );
		} catch ( \Exception $e ) {
				$this->log( 'Test connection (faktura) exception: ' . $e->getMessage(), 'error' );
				$results['faktura'] = false;
			}
		}
		
		// Test Rachunek key (optional)
		if ( ! empty( $this->api_login ) && ! empty( $this->api_key_rachunek ) ) {
			try {
				// Use rachunekkraj/list.json with limit=1 for minimal data
				$response = $this->get( 'rachunekkraj/list.json?limit=1', 'rachunek' );
				$results['rachunek'] = ! is_wp_error( $response );
			} catch ( \Exception $e ) {
				$this->log( 'Test connection (rachunek) exception: ' . $e->getMessage(), 'error' );
				$results['rachunek'] = false;
			}
		}
		
		return $results;
	}
	
	/**
	 * Simple test connection (legacy)
	 * 
	 * @return bool True if at least abonent key works
	 */
	public function test_connection_simple() {
		$results = $this->test_connection();
		return $results['abonent'] === true;
	}

	/**
	 * Create invoice
	 * 
	 * @param array $data Invoice data
	 * @return array|WP_Error
	 */
	public function create_invoice( $data ) {
		if ( empty( $this->api_login ) || empty( $this->api_key_faktura ) ) {
			return new \WP_Error( 'no_credentials', __( 'Login and API Key Faktura are required', 'invoicing-integration-for-ifirma-and-woocommerce' ) );
		}

		$this->sync_accounting_month_to_date( $data['sale_date'] ?? $data['date'] ?? '' );

		$this->log( 'Creating invoice with data: ' . $this->redact_sensitive( wp_json_encode( $data, JSON_UNESCAPED_UNICODE ) ), 'debug' );
		
		$payload = $this->build_invoice_payload( $data );
		
		$response = $this->post( 'fakturakraj.json', $payload, 'faktura' );

		if ( is_wp_error( $response ) ) {
			return $response;
		}

		return $response;
	}

	/**
	 * Create proforma invoice
	 * 
	 * @param array $data Invoice data
	 * @return array|WP_Error
	 */
	public function create_proforma( $data ) {
		if ( empty( $this->api_login ) || empty( $this->api_key_faktura ) ) {
			return new \WP_Error( 'no_credentials', __( 'Login and API Key Faktura are required', 'invoicing-integration-for-ifirma-and-woocommerce' ) );
		}

		$this->sync_accounting_month_to_date( $data['date'] ?? '' );

		$this->log( 'Creating proforma with data: ' . $this->redact_sensitive( wp_json_encode( $data, JSON_UNESCAPED_UNICODE ) ), 'debug' );
		
		$payload = $this->build_proforma_payload( $data );
		
		// Correct endpoint: fakturaproformakraj.json (not fakturaproforma.json)
		$response = $this->post( 'fakturaproformakraj.json', $payload, 'faktura' );

		if ( is_wp_error( $response ) ) {
			return $response;
		}

		return $response;
	}

	/**
	 * Create rachunek (non-VAT invoice)
	 * 
	 * @param array $data Invoice data
	 * @return array|WP_Error
	 */
	public function create_rachunek( $data ) {
		if ( empty( $this->api_login ) || empty( $this->api_key_rachunek ) ) {
			return new \WP_Error( 'no_credentials', __( 'Login and API Key Rachunek are required', 'invoicing-integration-for-ifirma-and-woocommerce' ) );
		}

		$this->sync_accounting_month_to_date( $data['sale_date'] ?? $data['date'] ?? '' );

		$this->log( 'Creating rachunek with data: ' . $this->redact_sensitive( wp_json_encode( $data, JSON_UNESCAPED_UNICODE ) ), 'debug' );
		
		$payload = $this->build_rachunek_payload( $data );
		
		$response = $this->post( 'rachunekkraj.json', $payload, 'rachunek' );

		if ( is_wp_error( $response ) ) {
			return $response;
		}

		return $response;
	}

	/**
	 * Create correction invoice (faktura korygująca)
	 * 
	 * Endpoint: POST /iapi/fakturakraj/korekta/{identyfikator_faktury}.json
	 * 
	 * @param int $original_invoice_id ID of the original invoice to correct
	 * @param array $correction_data Correction data including reason and items
	 * @return array|WP_Error
	 */
	public function create_correction( $original_invoice_id, $correction_data ) {
		if ( empty( $this->api_login ) || empty( $this->api_key_faktura ) ) {
			return new \WP_Error( 'no_credentials', __( 'Login and API Key Faktura are required', 'invoicing-integration-for-ifirma-and-woocommerce' ) );
		}

		$this->log( 'Creating correction for invoice ID: ' . $original_invoice_id, 'debug' );
		
		$payload = $this->build_correction_payload( $correction_data );
		
		$this->log( 'Correction payload: ' . $this->redact_sensitive( wp_json_encode( $payload, JSON_UNESCAPED_UNICODE ) ), 'debug' );
		
		// Endpoint includes invoice ID in URL
		$response = $this->post( 'fakturakraj/korekta/' . $original_invoice_id . '.json', $payload, 'faktura' );

		if ( is_wp_error( $response ) ) {
			return $response;
		}

		return $response;
	}

	/**
	 * Build correction invoice payload
	 * 
	 * @param array $data Correction data
	 * @return array
	 */
	private function build_correction_payload( $data ) {
		// Build corrected items - state after correction ("powinno być")
		$positions = [];
		foreach ( $data['items'] as $item ) {
			$vat_rate = $this->map_vat_symbol_to_ifirma( $item['vat'] ?? '23' );
			$vat_type = $this->get_vat_type( $item['vat'] ?? '23' );
			
			// Use floatval(number_format()) to avoid IEEE 754 precision issues
			// e.g., 0.23 becomes 0.23000000000001 without this fix
			$position = [
				'StawkaVat'       => $vat_rate !== null ? floatval( number_format( $vat_rate, 2, '.', '' ) ) : null,
				'Ilosc'           => floatval( number_format( floatval( $item['quantity'] ), 4, '.', '' ) ),
				'CenaJednostkowa' => floatval( number_format( $this->get_item_unit_price_for_ifirma_position( $item ), 2, '.', '' ) ),
				'NazwaPelna'      => $item['name'],
				'Jednostka'       => $item['unit'] ?? 'szt.',
				'TypStawkiVat'    => $vat_type,
			];
			
			$positions[] = $position;
		}
		
		// Map reason to API enum values
		$reason_map = [
			'refund'   => 'ZWR_SPRZ_TOW', // Zwrot sprzedawcy towarów
			'rabat'    => 'OBOW_RABAT',   // Obowiązkowe rabaty
			'cena'     => 'PODW_CENY',    // Podwyższenie ceny
			'pomylka'  => 'POMYLKI',      // Pomyłki
			'zwrot'    => 'ZWR_NAB_KWOT', // Zwrot nabywcy kwot nienależnych
			'zaliczka' => 'ZWR_NAB_ZAL',  // Zwrot nabywcy zaliczek
		];
		
		// Default to ZWR_SPRZ_TOW for refunds
		$powod_korekty = 'ZWR_SPRZ_TOW';
		
		$payload = [
			'PowodKorekty'          => $powod_korekty,
			'SpelnionoWarunki'      => true,
			'DataWystawienia'       => $data['date'] ?? current_time( 'Y-m-d' ),
			'SposobZaplaty'         => 'PRZ', // Przelew
			'RodzajPodpisuOdbiorcy' => 'BPO',
			'Zaplacono'             => 0,
			'LiczOd'                => $this->get_licz_od_for_vat_split_document(),
			'Pozycje'               => $positions,
		];
		
		// Add place of issue if set
		if ( ! empty( $data['place_of_issue'] ) ) {
			$payload['MiejsceWystawienia'] = $data['place_of_issue'];
		}
		
		// Add notes with refund reason
		if ( ! empty( $data['reason'] ) ) {
			$payload['Uwagi'] = $data['reason'];
		}
		
		return $payload;
	}

	/**
	 * Get correction invoice details
	 * 
	 * @param int $correction_id Correction invoice ID
	 * @return array|WP_Error
	 */
	public function get_correction_details( $correction_id ) {
		$this->log( 'Getting correction details for ID: ' . $correction_id, 'debug' );
		return $this->get( 'fakturakraj/korekta/' . $correction_id . '.json', 'faktura' );
	}

	/**
	 * Download correction PDF
	 * 
	 * @param int $correction_id Correction invoice ID
	 * @return string|WP_Error PDF content or error
	 */
	public function download_correction_pdf( $correction_id ) {
		$this->log( 'Downloading correction PDF for ID: ' . $correction_id, 'debug' );
		return $this->get( 'fakturakraj/korekta/' . $correction_id . '.pdf', 'faktura', true );
	}

	/**
	 * Build proforma payload for iFirma API
	 * 
	 * Proforma has different structure than invoice - no paid amount, no sale date,
	 * but requires TypFakturyKrajowej = 'sprz'
	 * 
	 * @param array $data Invoice data
	 * @return array
	 */
	private function build_proforma_payload( $data ) {
		$customer = $data['customer'];
		$is_non_vat_payer = ! $this->is_tax_enabled();
		
		// Map payment method
		$payment_method = 'PRZ'; // Bank transfer by default
		if ( isset( $data['payment_method'] ) ) {
			$wc_payment = $data['payment_method'];
			if ( in_array( $wc_payment, [ 'cod', 'cash' ], true ) ) {
				$payment_method = 'GTK'; // Cash
			} elseif ( in_array( $wc_payment, [ 'paypal', 'stripe', 'blik', 'przelewy24', 'payu', 'tpay' ], true ) ) {
				$payment_method = 'ELE'; // Electronic payment
			}
		}
		
		// Get VAT exemption reason for non-VAT payers
		$vat_exemption_reason = $data['vat_exemption_reason'] ?? '';
		if ( $is_non_vat_payer && empty( $vat_exemption_reason ) ) {
			// Default exemption reason for non-VAT payers (Art. 113 ust. 1 ustawy o VAT)
			$settings = get_option( 'devikit_ifirma_settings', [] );
			$vat_exemption_reason = $settings['vat_exemption_reason'] ?? 'zw. z VAT na podst. art. 113 ust. 1 ustawy o VAT';
		}
		
		// Build items
		$positions = [];
		foreach ( $data['items'] as $item ) {
			// For non-VAT payers, force ZW (exempt) for all items
			if ( $is_non_vat_payer ) {
				$vat_rate = null;
				$vat_type = 'ZW';
				
				// For non-VAT payers, use gross price
				$price = floatval( $item['price_gross'] ?? $item['price_net'] );
				
				$position = [
					'StawkaVat'       => $vat_rate,
					'Ilosc'           => floatval( number_format( floatval( $item['quantity'] ), 4, '.', '' ) ),
					'CenaJednostkowa' => floatval( number_format( $price, 2, '.', '' ) ),
					'NazwaPelna'      => $item['name'],
					'Jednostka'       => $item['unit'] ?? 'szt.',
					'TypStawkiVat'    => $vat_type,
					// For ZW, PKWiU acts as legal basis in iFirma.
					'PKWiU'           => ( ! empty( $item['pkwiu'] ) ? $item['pkwiu'] : $vat_exemption_reason ),
				];

				// Lump sum (ryczałt) rate if provided (decimal, e.g. 0.0850)
				if ( isset( $item['lump_sum_rate_decimal'] ) && $item['lump_sum_rate_decimal'] !== null ) {
					$position['StawkaRyczaltu'] = floatval( number_format( floatval( $item['lump_sum_rate_decimal'] ), 4, '.', '' ) );
				}

				// Add GTU if present (also for ZW)
				if ( isset( $item['gtu'] ) && ! empty( $item['gtu'] ) ) {
					$position['GTU'] = $item['gtu'];
				}
			} else {
				$vat_rate = $this->map_vat_symbol_to_ifirma( $item['vat'] ?? '23' );
				$vat_type = $this->get_vat_type( $item['vat'] ?? '23' );
				
				// Use floatval(number_format()) to avoid IEEE 754 precision issues
				$position = [
					'StawkaVat'       => $vat_rate,
					'Ilosc'           => floatval( number_format( floatval( $item['quantity'] ), 4, '.', '' ) ),
					'CenaJednostkowa' => floatval( number_format( $this->get_item_unit_price_for_ifirma_position( $item ), 2, '.', '' ) ),
					'NazwaPelna'      => $item['name'],
					'Jednostka'       => $item['unit'] ?? 'szt.',
					'TypStawkiVat'    => $vat_type,
				];

				// Lump sum (ryczałt) rate if provided (decimal, e.g. 0.0850)
				if ( isset( $item['lump_sum_rate_decimal'] ) && $item['lump_sum_rate_decimal'] !== null ) {
					$position['StawkaRyczaltu'] = floatval( number_format( floatval( $item['lump_sum_rate_decimal'] ), 4, '.', '' ) );
				}
				
				// Add GTU if present (also for ZW)
				if ( isset( $item['gtu'] ) && ! empty( $item['gtu'] ) ) {
					$position['GTU'] = $item['gtu'];
				}
				
				// Add PKWiU - for ZW items it contains the legal basis for exemption
				if ( $vat_type === 'ZW' ) {
					// For ZW, PKWiU must contain the legal basis (article of VAT law)
					if ( isset( $item['pkwiu'] ) && ! empty( $item['pkwiu'] ) ) {
						$position['PKWiU'] = $item['pkwiu'];
					} elseif ( ! empty( $vat_exemption_reason ) ) {
						$position['PKWiU'] = $vat_exemption_reason;
					}
				} elseif ( isset( $item['pkwiu'] ) && ! empty( $item['pkwiu'] ) ) {
					// For non-ZW items, PKWiU is the product code
					$position['PKWiU'] = $item['pkwiu'];
				}
			}
			
			$positions[] = $position;
		}
		
		// Build kontrahent object
		$kontrahent = [
			'Nazwa'       => $customer['name'],
			'Ulica'       => $this->ifirma_street_for_payload( $customer['street'] ?? '' ),
			'KodPocztowy' => $this->ifirma_kod_pocztowy_for_payload( (string) ( $customer['postcode'] ?? '' ), (string) ( $customer['country_code'] ?? '' ) ),
			'Miejscowosc' => $this->ifirma_city_for_payload( $customer['city'] ?? '', $customer['country'] ?? '' ),
			'Kraj'        => $customer['country'] ?? 'Polska',
			'Email'       => $customer['email'] ?? '',
			'Telefon'     => $customer['phone'] ?? '',
		];
		
		// Add NIP to kontrahent if present
		if ( ! empty( $customer['nip'] ) ) {
			$kontrahent['NIP'] = $customer['nip'];
		}
		
		// Proforma payload - different from invoice
		$payload = [
			'TypFakturyKrajowej'     => 'sprz', // Required for proforma
			'LiczOd'                 => $this->get_licz_od_for_document( $is_non_vat_payer ),
			'DataWystawienia'        => $data['date'],
			'SposobZaplaty'          => $payment_method,
			'RodzajPodpisuOdbiorcy'  => 'BPO', // bez podpisu odbiorcy
			'WidocznyNumerGios'      => true,
			'Kontrahent'             => $kontrahent,
			'Pozycje'                => $positions,
		];
		if ( ! empty( $data['contractor_identifier'] ) ) {
			$payload['IdentyfikatorKontrahenta'] = (string) $data['contractor_identifier'];
		}
		
		// Add place of issue if set
		if ( ! empty( $data['place_of_issue'] ) ) {
			$payload['MiejsceWystawienia'] = $data['place_of_issue'];
		}
		
		// Add payment deadline
		if ( ! empty( $data['payment_date'] ) ) {
			$payload['TerminPlatnosci'] = $data['payment_date'];
		}
		
		// Add bank account if set
		if ( ! empty( $data['bank_account'] ) ) {
			$payload['NumerKontaBankowego'] = str_replace( [ ' ', '-' ], '', $data['bank_account'] );
		}
		
		// Add series if set
		if ( ! empty( $data['series'] ) ) {
			$payload['NazwaSeriiNumeracji'] = $data['series'];
		}

		// Add notes (Uwagi) if provided
		if ( ! empty( $data['notes'] ) ) {
			$payload['Uwagi'] = (string) $data['notes'];
		}
		
		return $payload;
	}

	/**
	 * Build invoice payload for iFirma API
	 * 
	 * @param array $data Invoice data
	 * @param string $type Document type (invoice/proforma)
	 * @return array
	 */
	private function build_invoice_payload( $data, $type = 'invoice' ) {
		$customer = $data['customer'];
		$is_non_vat_payer = ! $this->is_tax_enabled();
		
		// Map payment method
		$payment_method = 'PRZ'; // Bank transfer by default
		if ( isset( $data['payment_method'] ) ) {
			$wc_payment = $data['payment_method'];
			if ( in_array( $wc_payment, [ 'cod', 'cash' ], true ) ) {
				$payment_method = 'GTK'; // Cash
			} elseif ( in_array( $wc_payment, [ 'paypal', 'stripe', 'blik', 'przelewy24', 'payu', 'tpay' ], true ) ) {
				$payment_method = 'ELE'; // Electronic payment
			}
		}
		
		// Get VAT exemption reason for non-VAT payers
		$vat_exemption_reason = $data['vat_exemption_reason'] ?? '';
		if ( $is_non_vat_payer && empty( $vat_exemption_reason ) ) {
			// Default exemption reason for non-VAT payers (Art. 113 ust. 1 ustawy o VAT)
			$settings = get_option( 'devikit_ifirma_settings', [] );
			$vat_exemption_reason = $settings['vat_exemption_reason'] ?? 'zw. z VAT na podst. art. 113 ust. 1 ustawy o VAT';
		}
		
		// Build items
		$positions = [];
		foreach ( $data['items'] as $index => $item ) {
			// For non-VAT payers, force ZW (exempt) for all items
			if ( $is_non_vat_payer ) {
				$vat_rate = null; // No VAT rate for exempt
				$vat_type = 'ZW';
				
				// For non-VAT payers, use gross price
				$price = floatval( $item['price_gross'] ?? $item['price_net'] );
				
				$position = [
					'StawkaVat'       => $vat_rate,
					'Ilosc'           => floatval( number_format( floatval( $item['quantity'] ), 4, '.', '' ) ),
					'CenaJednostkowa' => floatval( number_format( $price, 2, '.', '' ) ),
					'NazwaPelna'      => $item['name'],
					'Jednostka'       => $item['unit'] ?? 'szt.',
					'TypStawkiVat'    => $vat_type,
					// For ZW, PKWiU acts as legal basis in iFirma.
					'PKWiU'           => ( ! empty( $item['pkwiu'] ) ? $item['pkwiu'] : $vat_exemption_reason ),
				];

				// Lump sum (ryczałt) rate if provided (decimal, e.g. 0.0850)
				if ( isset( $item['lump_sum_rate_decimal'] ) && $item['lump_sum_rate_decimal'] !== null ) {
					$position['StawkaRyczaltu'] = floatval( number_format( floatval( $item['lump_sum_rate_decimal'] ), 4, '.', '' ) );
				}

				// Add GTU if present (also for ZW)
				if ( isset( $item['gtu'] ) && ! empty( $item['gtu'] ) ) {
					$position['GTU'] = $item['gtu'];
				}
			} else {
				$vat_rate = $this->map_vat_symbol_to_ifirma( $item['vat'] ?? '23' );
				$vat_type = $this->get_vat_type( $item['vat'] ?? '23' );
			
				// Use floatval(number_format()) to avoid IEEE 754 precision issues
				$position = [
					'StawkaVat'       => $vat_rate,
					'Ilosc'           => floatval( number_format( floatval( $item['quantity'] ), 4, '.', '' ) ),
					'CenaJednostkowa' => floatval( number_format( $this->get_item_unit_price_for_ifirma_position( $item ), 2, '.', '' ) ),
					'NazwaPelna'      => $item['name'],
					'Jednostka'       => $item['unit'] ?? 'szt.',
					'TypStawkiVat'    => $vat_type,
				];

				// Lump sum (ryczałt) rate if provided (decimal, e.g. 0.0850)
				if ( isset( $item['lump_sum_rate_decimal'] ) && $item['lump_sum_rate_decimal'] !== null ) {
					$position['StawkaRyczaltu'] = floatval( number_format( floatval( $item['lump_sum_rate_decimal'] ), 4, '.', '' ) );
				}
			
				// Add GTU if present (also for ZW)
				if ( isset( $item['gtu'] ) && ! empty( $item['gtu'] ) ) {
					$position['GTU'] = $item['gtu'];
				}
			
				// Add PKWiU - for ZW items it contains the legal basis for exemption
				if ( $vat_type === 'ZW' ) {
					// For ZW, PKWiU must contain the legal basis (article of VAT law)
					if ( isset( $item['pkwiu'] ) && ! empty( $item['pkwiu'] ) ) {
						$position['PKWiU'] = $item['pkwiu'];
					} elseif ( ! empty( $vat_exemption_reason ) ) {
						$position['PKWiU'] = $vat_exemption_reason;
					}
				} elseif ( isset( $item['pkwiu'] ) && ! empty( $item['pkwiu'] ) ) {
					// For non-ZW items, PKWiU is the product code
					$position['PKWiU'] = $item['pkwiu'];
				}
			}
			
			$positions[] = $position;
		}
		
		// Calculate total for ZaplaconoNaDokumencie
		$paid_amount = floatval( $data['paid'] ?? 0 );
		
		// Build kontrahent object
		$kontrahent = [
			'Nazwa'       => $customer['name'],
			'Ulica'       => $this->ifirma_street_for_payload( $customer['street'] ?? '' ),
			'KodPocztowy' => $this->ifirma_kod_pocztowy_for_payload( (string) ( $customer['postcode'] ?? '' ), (string) ( $customer['country_code'] ?? '' ) ),
			'Miejscowosc' => $this->ifirma_city_for_payload( $customer['city'] ?? '', $customer['country'] ?? '' ),
			'Kraj'        => $customer['country'] ?? 'Polska',
			'Email'       => $customer['email'] ?? '',
			'Telefon'     => $customer['phone'] ?? '',
		];
		
		// Add NIP to kontrahent if present
		if ( ! empty( $customer['nip'] ) ) {
			$kontrahent['NIP'] = $customer['nip'];
		}
		
		$payload = [
			'Zaplacono'              => $paid_amount,
			'ZaplaconoNaDokumencie'  => $paid_amount,
			'LiczOd'                 => $this->get_licz_od_for_document( $is_non_vat_payer ),
			'DataWystawienia'        => $data['date'],
			'DataSprzedazy'          => $data['sale_date'],
			'FormatDatySprzedazy'    => 'DZN', // Full date
			'SposobZaplaty'          => $payment_method,
			'RodzajPodpisuOdbiorcy'  => 'BPO', // bez podpisu odbiorcy
			'WidocznyNumerGios'      => false,
			'Numer'                  => null, // Auto-numbering
			'Kontrahent'             => $kontrahent,
			'Pozycje'                => $positions,
		];
		if ( ! empty( $data['contractor_identifier'] ) ) {
			$payload['IdentyfikatorKontrahenta'] = (string) $data['contractor_identifier'];
		}
		
		// Add place of issue if set
		if ( ! empty( $data['place_of_issue'] ) ) {
			$payload['MiejsceWystawienia'] = $data['place_of_issue'];
		}
		
		// Add payment deadline for invoices
		if ( $type === 'invoice' && ! empty( $data['payment_date'] ) ) {
			$payload['TerminPlatnosci'] = $data['payment_date'];
		}
		
		// Add bank account if set and payment is bank transfer
		if ( $payment_method === 'PRZ' && ! empty( $data['bank_account'] ) ) {
			$payload['NumerKontaBankowego'] = str_replace( [ ' ', '-' ], '', $data['bank_account'] );
		} elseif ( $payment_method === 'PRZ' ) {
			// If bank transfer but no account specified, set BRAK to hide it
			$payload['NumerKontaBankowego'] = 'BRAK';
		}
		
		// Add series if set
		if ( ! empty( $data['series'] ) ) {
			$payload['NazwaSeriiNumeracji'] = $data['series'];
		}

		// Add notes (Uwagi) if provided
		if ( ! empty( $data['notes'] ) ) {
			$payload['Uwagi'] = (string) $data['notes'];
		}
		
		return $payload;
	}
	
	/**
	 * Get VAT type for iFirma API
	 * 
	 * @param string $vat VAT symbol
	 * @return string PRC (percentage) or ZW (exempt)
	 */
	private function get_vat_type( $vat ) {
		$vat = strtolower( trim( $vat ) );
		
		// VAT exempt
		if ( in_array( $vat, [ 'zw', 'exempt', 'zwolniony' ], true ) ) {
			return 'ZW';
		}
		
		return 'PRC';
	}

	/**
	 * Whether WooCommerce catalog/order prices are entered including tax.
	 *
	 * @return bool
	 */
	private function wc_store_prices_include_tax() {
		return function_exists( 'wc_prices_include_tax' ) && wc_prices_include_tax();
	}

	/**
	 * Unit price for iFirma line (CenaJednostkowa): gross if store uses tax-inclusive prices, else net.
	 *
	 * @param array $item Invoice line from integration (price_net, price_gross).
	 * @return float
	 */
	private function get_item_unit_price_for_ifirma_position( array $item ) {
		if ( $this->wc_store_prices_include_tax() ) {
			$gross = isset( $item['price_gross'] ) ? floatval( $item['price_gross'] ) : 0.0;
			if ( $gross > 0 ) {
				return $gross;
			}
		}
		return floatval( $item['price_net'] ?? 0 );
	}

	/**
	 * LiczOd: BRT when document uses tax-inclusive unit prices, NET otherwise. Non-VAT payers always BRT.
	 *
	 * @param bool $is_non_vat_payer
	 * @return string NET|BRT
	 */
	private function get_licz_od_for_document( $is_non_vat_payer ) {
		if ( $is_non_vat_payer ) {
			return 'BRT';
		}
		return $this->wc_store_prices_include_tax() ? 'BRT' : 'NET';
	}

	/**
	 * LiczOd for documents where unit price follows wc_prices_include_tax() (no separate non-VAT payer branch).
	 *
	 * @return string NET|BRT
	 */
	private function get_licz_od_for_vat_split_document() {
		return $this->wc_store_prices_include_tax() ? 'BRT' : 'NET';
	}

	/**
	 * Build rachunek payload for iFirma API
	 * 
	 * @param array $data Invoice data
	 * @return array
	 */
	private function build_rachunek_payload( $data ) {
		$customer = $data['customer'];
		
		// Map payment method
		$payment_method = 'PRZ';
		if ( isset( $data['payment_method'] ) ) {
			$wc_payment = $data['payment_method'];
			if ( in_array( $wc_payment, [ 'cod', 'cash' ], true ) ) {
				$payment_method = 'GTK';
			} elseif ( in_array( $wc_payment, [ 'paypal', 'stripe', 'blik', 'przelewy24', 'payu', 'tpay' ], true ) ) {
				$payment_method = 'ELE';
			}
		}
		
		// Build positions - rachunek has no VAT fields (no StawkaVat, TypStawkiVat, PKWiU, GTU)
		$positions = [];
		foreach ( $data['items'] as $item ) {
			// Use floatval(number_format()) to avoid IEEE 754 precision issues
			$positions[] = [
				'Ilosc'           => floatval( number_format( floatval( $item['quantity'] ), 4, '.', '' ) ),
				'CenaJednostkowa' => floatval( number_format( $this->get_item_unit_price_for_ifirma_position( $item ), 2, '.', '' ) ),
				'NazwaPelna'      => $item['name'],
				'Jednostka'       => $item['unit'] ?? 'szt.',
			];
		}
		
		// Build kontrahent - rachunek doesn't need OsobaFizyczna field
		$kontrahent = [
			'Nazwa'       => $customer['name'],
			'Ulica'       => $this->ifirma_street_for_payload( $customer['street'] ?? '' ),
			'KodPocztowy' => $this->ifirma_kod_pocztowy_for_payload( (string) ( $customer['postcode'] ?? '' ), (string) ( $customer['country_code'] ?? '' ) ),
			'Miejscowosc' => $this->ifirma_city_for_payload( $customer['city'] ?? '', $customer['country'] ?? '' ),
			'Kraj'        => $customer['country'] ?? 'Polska',
			'Email'       => $customer['email'] ?? '',
			'Telefon'     => $customer['phone'] ?? '',
		];
		
		// Add NIP if present
		if ( ! empty( $customer['nip'] ) ) {
			$kontrahent['NIP'] = $customer['nip'];
		}
		
		$payload = [
			'Zaplacono'           => floatval( number_format( floatval( $data['paid'] ?? 0 ), 2, '.', '' ) ),
			'DataWystawienia'     => $data['date'],
			'DataSprzedazy'       => $data['sale_date'],
			'FormatDatySprzedazy' => 'DZN', // DZN = daily, MSC = monthly
			'SposobZaplaty'       => $payment_method,
			'WpisDoKpir'          => 'TOW', // Required for rachunek
			'Kontrahent'          => $kontrahent,
			'Pozycje'             => $positions,
		];
		if ( ! empty( $data['contractor_identifier'] ) ) {
			$payload['IdentyfikatorKontrahenta'] = (string) $data['contractor_identifier'];
		}
		
		// Add payment deadline
		if ( ! empty( $data['payment_date'] ) ) {
			$payload['TerminPlatnosci'] = $data['payment_date'];
		}
		
		// Add place of issue if set
		if ( ! empty( $data['place_of_issue'] ) ) {
			$payload['MiejsceWystawienia'] = $data['place_of_issue'];
		}
		
		// Add bank account if set and payment is bank transfer
		if ( $payment_method === 'PRZ' && ! empty( $data['bank_account'] ) ) {
			$payload['NumerKontaBankowego'] = str_replace( [ ' ', '-' ], '', $data['bank_account'] );
		}
		
		// Add series if set
		if ( ! empty( $data['series'] ) ) {
			$payload['NazwaSeriiNumeracji'] = $data['series'];
		}

		// Add notes (Uwagi) if provided
		if ( ! empty( $data['notes'] ) ) {
			$payload['Uwagi'] = (string) $data['notes'];
		}
		
		return $payload;
	}

	/**
	 * Map VAT symbol to iFirma format
	 * 
	 * Returns exact decimal values accepted by iFirma API.
	 * Uses string conversion to avoid IEEE 754 float precision issues.
	 * 
	 * @param string $symbol VAT symbol (23, 8, 5, 0, zw, np)
	 * @return float|null
	 */
	private function map_vat_symbol_to_ifirma( $symbol ) {
		$symbol = strtolower( trim( $symbol ) );
		
		// Exact allowed values by iFirma API: 0.00, 0.03, 0.05, 0.07, 0.08, 0.22, 0.23, or null for ZW
		// Using string values and floatval() to avoid IEEE 754 precision issues
		$map = [
			'23' => '0.23',
			'22' => '0.22',
			'8'  => '0.08',
			'7'  => '0.07',
			'5'  => '0.05',
			'3'  => '0.03',
			'0'  => '0.00',
			'zw' => null, // VAT exempt - StawkaVat should be null
			'exempt' => null,
			'zwolniony' => null,
		];
		
		if ( array_key_exists( $symbol, $map ) ) {
			$value = $map[ $symbol ];
			return $value !== null ? floatval( $value ) : null;
		}
		
		// Try to parse as number
		if ( is_numeric( $symbol ) ) {
			return floatval( number_format( floatval( $symbol ) / 100, 2, '.', '' ) );
		}
		
		return floatval( '0.23' );
	}

	/**
	 * Get invoice details
	 * 
	 * @param int $document_id Document ID
	 * @param string $type Document type (invoice/proforma/rachunek)
	 * @return array|WP_Error Invoice data or error
	 */
	public function get_invoice_details( $document_id, $type = 'invoice' ) {
		$key_type = 'faktura';
		$endpoint = '';
		
		switch ( $type ) {
			case 'invoice':
				$endpoint = 'fakturakraj/' . $document_id . '.json';
				break;
			case 'proforma':
				// Correct endpoint: fakturaproformakraj (not fakturaproforma)
				$endpoint = 'fakturaproformakraj/' . $document_id . '.json';
				break;
			case 'rachunek':
				$key_type = 'rachunek';
				$endpoint = 'rachunekkraj/' . $document_id . '.json';
				break;
			case 'wdt':
				$endpoint = 'fakturawdt/' . $document_id . '.json';
				break;
			case 'foreign':
				$endpoint = 'fakturawaluta/' . $document_id . '.json';
				break;
			case 'oss':
				$endpoint = 'fakturaoss/' . $document_id . '.json';
				break;
			default:
				return new \WP_Error( 'invalid_type', __( 'Invalid document type', 'invoicing-integration-for-ifirma-and-woocommerce' ) );
		}

		$this->log( 'Getting details for ' . $type . ' ID: ' . $document_id, 'debug' );
		
		return $this->get( $endpoint, $key_type );
	}

	/**
	 * Download PDF
	 * 
	 * @param int $document_id Document ID
	 * @param string $type Document type (invoice/proforma/rachunek/wdt/foreign)
	 * @return string|WP_Error PDF content or error
	 */
	public function download_pdf( $document_id, $type = 'invoice' ) {
		$key_type = 'faktura';
		$endpoint = '';
		
		switch ( $type ) {
			case 'invoice':
				$key_type = 'faktura';
				$endpoint = 'fakturakraj/' . $document_id . '.pdf';
				break;
			case 'proforma':
				$key_type = 'faktura';
				// Correct endpoint: fakturaproformakraj (not fakturakrajpdf)
				$endpoint = 'fakturaproformakraj/' . $document_id . '.pdf';
				break;
			case 'rachunek':
				$key_type = 'rachunek';
				$endpoint = 'rachunekkraj/' . $document_id . '.pdf';
				break;
			case 'wdt':
				$key_type = 'faktura';
				$endpoint = 'fakturawdt/' . $document_id . '.pdf';
				break;
			case 'foreign':
				$key_type = 'faktura';
				$endpoint = 'fakturawaluta/' . $document_id . '.pdf';
				break;
			case 'oss':
				$key_type = 'faktura';
				$endpoint = 'fakturaoss/' . $document_id . '.pdf';
				break;
			default:
				return new \WP_Error( 'invalid_type', __( 'Invalid document type', 'invoicing-integration-for-ifirma-and-woocommerce' ) );
		}

		$this->log( 'Downloading PDF for ' . $type . ' ID: ' . $document_id, 'debug' );

		return $this->get( $endpoint, $key_type, true );
	}

	/**
	 * Get company accounts (bank accounts)
	 * 
	 * Note: iFirma API does not support fetching bank accounts.
	 * Bank account must be entered manually in settings.
	 * 
	 * @param bool $refresh Force refresh from API (no cache)
	 * @return array Empty array - not supported by iFirma API
	 */
	public function get_company_accounts( $refresh = false ) {
		// iFirma API does not provide endpoint for bank accounts
		// User must enter bank account manually
			return [];
	}

	/**
	 * Get invoice series
	 * 
	 * Note: iFirma API does not support fetching invoice series.
	 * Series name must be entered manually in settings.
	 * 
	 * @param string $type Series type (normal, proforma, rachunek)
	 * @return array Empty series list - not supported by iFirma API
	 */
	public function get_series( $type = 'normal' ) {
		// iFirma API does not provide endpoint for series
		// User must enter series name manually
		return [
			'series_parsed' => [],
		];
	}

	/**
	 * Clear API cache
	 */
	public static function clear_cache() {
		global $wpdb;
		
		// Delete all transients starting with our prefix
		// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$wpdb->query(
			$wpdb->prepare(
				"DELETE FROM {$wpdb->options} WHERE option_name LIKE %s",
				$wpdb->esc_like( '_transient_devikit_ifirma_' ) . '%'
			)
		);
		$wpdb->query(
			$wpdb->prepare(
				"DELETE FROM {$wpdb->options} WHERE option_name LIKE %s",
				$wpdb->esc_like( '_transient_timeout_devikit_ifirma_' ) . '%'
			)
		);
		// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
	}

	/**
	 * Send GET request
	 * 
	 * @param string $endpoint API endpoint (without base URL)
	 * @param string $key_type Key type: 'abonent', 'faktura', 'rachunek'
	 * @param bool $pdf Whether to expect PDF response
	 * @return array|string|WP_Error
	 */
	private function get( $endpoint, $key_type = 'abonent', $pdf = false ) {
		$url = self::API_URL . $endpoint;
		$headers = $this->get_headers( $key_type, $url );
		
		if ( empty( $headers ) ) {
			return new \WP_Error( 'no_api_key', __( 'API key not configured', 'invoicing-integration-for-ifirma-and-woocommerce' ) );
		}
		
		$args = [
			'headers'   => $headers,
			'timeout'   => 30,
			'sslverify' => true,
		];

		$this->log( 'GET Request to: ' . $url, 'debug' );
		$response = wp_remote_get( $url, $args );

		if ( is_wp_error( $response ) ) {
			$this->log( 'GET Error: ' . $response->get_error_message(), 'error' );
			return $response;
		}

		return $this->parse_response( $response, $pdf );
	}

	/**
	 * Normalize floats before JSON encoding.
	 *
	 * Some hosting environments use PHP settings that cause JSON encoding to output
	 * float artifacts like 0.23000000000000001. To avoid using ini_set(), we convert
	 * floats to trimmed decimal strings.
	 *
	 * @param mixed $value Any value.
	 * @return mixed
	 */
	private function normalize_for_json( $value ) {
		if ( is_array( $value ) ) {
			foreach ( $value as $k => $v ) {
				$value[ $k ] = $this->normalize_for_json( $v );
			}
			return $value;
		}

		if ( is_object( $value ) ) {
			foreach ( get_object_vars( $value ) as $k => $v ) {
				$value->$k = $this->normalize_for_json( $v );
			}
			return $value;
		}

		if ( is_float( $value ) ) {
			$formatted = number_format( $value, 8, '.', '' );
			return rtrim( rtrim( $formatted, '0' ), '.' );
		}

		return $value;
	}

	/**
	 * Send POST request
	 * 
	 * @param string $endpoint API endpoint (without base URL)
	 * @param array $params Request parameters
	 * @param string $key_type Key type: 'abonent', 'faktura', 'rachunek'
	 * @param bool $pdf Whether to expect PDF response
	 * @return array|WP_Error
	 */
	private function post( $endpoint, $params = [], $key_type = 'faktura', $pdf = false ) {
		$url = self::API_URL . $endpoint;
		
		$content = wp_json_encode( $this->normalize_for_json( $params ), JSON_UNESCAPED_UNICODE );
		if ( false === $content ) {
			return new \WP_Error( 'json_encode_error', __( 'Failed to encode request body', 'invoicing-integration-for-ifirma-and-woocommerce' ) );
		}
		
		$headers = $this->get_headers( $key_type, $url, $content );
		
		if ( empty( $headers ) ) {
			return new \WP_Error( 'no_api_key', __( 'API key not configured', 'invoicing-integration-for-ifirma-and-woocommerce' ) );
		}
		
		$args = [
			'headers'   => $headers,
			'body'      => $content,
			'timeout'   => 30,
			'sslverify' => true,
		];

		$this->log( 'POST Request to: ' . $url . ' with body: ' . $this->redact_sensitive( $content ), 'debug' );
		$response = wp_remote_post( $url, $args );

		if ( is_wp_error( $response ) ) {
			$this->log( 'POST Error: ' . $response->get_error_message(), 'error' );
			return $response;
		}

		return $this->parse_response( $response, $pdf );
	}

	/**
	 * Send PUT request
	 *
	 * Used for updating contractors (kontrahenci/{IDENTYFIKATOR}.json).
	 *
	 * @param string $endpoint API endpoint (without base URL)
	 * @param array  $params   Request parameters
	 * @param string $key_type Key type: 'abonent', 'faktura', 'rachunek'
	 * @return array|WP_Error
	 */
	private function put( $endpoint, $params = [], $key_type = 'faktura' ) {
		$url = self::API_URL . $endpoint;
		$content = wp_json_encode( $this->normalize_for_json( $params ), JSON_UNESCAPED_UNICODE );
		if ( false === $content ) {
			return new \WP_Error( 'json_encode_error', __( 'Failed to encode request body', 'invoicing-integration-for-ifirma-and-woocommerce' ) );
		}

		$headers = $this->get_headers( $key_type, $url, $content );
		if ( empty( $headers ) ) {
			return new \WP_Error( 'no_api_key', __( 'API key not configured', 'invoicing-integration-for-ifirma-and-woocommerce' ) );
		}

		$args = [
			'headers'   => $headers,
			'body'      => $content,
			'timeout'   => 30,
			'sslverify' => true,
			'method'    => 'PUT',
		];

		$this->log( 'PUT Request to: ' . $url . ' with body: ' . $this->redact_sensitive( $content ), 'debug' );
		$response = wp_remote_request( $url, $args );

		if ( is_wp_error( $response ) ) {
			$this->log( 'PUT Error: ' . $response->get_error_message(), 'error' );
			return $response;
		}

		return $this->parse_response( $response, false );
	}

	/**
	 * Search contractors.
	 *
	 * This endpoint returns Kod=208 when not found (which should NOT be treated as an error).
	 *
	 * @param string $phrase Search phrase: identifier, name, or NIP
	 * @return array|WP_Error
	 */
	public function search_contractors( $phrase ) {
		if ( empty( $this->api_login ) || empty( $this->api_key_faktura ) ) {
			return new \WP_Error( 'no_credentials', __( 'Login and API Key Faktura are required', 'invoicing-integration-for-ifirma-and-woocommerce' ) );
		}

		$phrase = (string) $phrase;
		$endpoint = 'kontrahenci/' . rawurlencode( $phrase ) . '.json';
		$url = self::API_URL . $endpoint;

		$headers = $this->get_headers( 'faktura', $url, '' );
		$args = [
			'headers'   => $headers,
			'timeout'   => 30,
			'sslverify' => true,
		];

		$this->log( 'GET Request to: ' . $url, 'debug' );
		$response = wp_remote_get( $url, $args );
		if ( is_wp_error( $response ) ) {
			return $response;
		}

		$body = wp_remote_retrieve_body( $response );
		$data = json_decode( $body, true );
		if ( json_last_error() !== JSON_ERROR_NONE ) {
			return new \WP_Error( 'ifirma_json_error', __( 'Failed to parse JSON response', 'invoicing-integration-for-ifirma-and-woocommerce' ) );
		}

		// Do not treat Kod=208 as error (not found).
		if ( isset( $data['response']['Kod'] ) && (int) $data['response']['Kod'] === 208 ) {
			return $data;
		}

		// For all other cases, use standard validation behavior.
		if ( isset( $data['response']['Kod'] ) && (int) $data['response']['Kod'] !== 0 ) {
			$error_message = $data['response']['Informacja'] ?? __( 'Unknown error from iFirma API', 'invoicing-integration-for-ifirma-and-woocommerce' );
			return new \WP_Error( 'ifirma_api_error', $error_message );
		}

		return $data;
	}

	/**
	 * Create contractor in iFirma.
	 *
	 * Endpoint: POST /iapi/kontrahenci.json
	 *
	 * @param array $contractor Contractor payload
	 * @return array|WP_Error
	 */
	public function create_contractor( $contractor ) {
		$this->log( 'Creating contractor: ' . $this->redact_sensitive( wp_json_encode( $contractor, JSON_UNESCAPED_UNICODE ) ), 'debug' );
		return $this->post( 'kontrahenci.json', $contractor, 'faktura' );
	}

	/**
	 * Update contractor in iFirma.
	 *
	 * Endpoint: PUT /iapi/kontrahenci/{IDENTYFIKATOR}.json
	 *
	 * @param string $identifier Contractor identifier
	 * @param array  $contractor Contractor payload (full data)
	 * @return array|WP_Error
	 */
	public function update_contractor( $identifier, $contractor ) {
		$identifier = (string) $identifier;
		$this->log( 'Updating contractor: ' . $identifier . ' with data: ' . $this->redact_sensitive( wp_json_encode( $contractor, JSON_UNESCAPED_UNICODE ) ), 'debug' );
		return $this->put( 'kontrahenci/' . rawurlencode( $identifier ) . '.json', $contractor, 'faktura' );
	}

	/**
	 * Parse API response
	 * 
	 * @param array $response WordPress HTTP response
	 * @param bool $pdf Whether to expect PDF response
	 * @return array|string|WP_Error
	 */
	private function parse_response( $response, $pdf = false ) {
		$code = wp_remote_retrieve_response_code( $response );
		$body = wp_remote_retrieve_body( $response );

		// Handle PDF response
		if ( $pdf ) {
			$content_type = wp_remote_retrieve_header( $response, 'content-type' );
			if ( strpos( $content_type, 'application/pdf' ) !== false ) {
				if ( $code === 200 ) {
					return $body;
				}
			}
		}

		// Parse JSON response
		$data = json_decode( $body, true );

		if ( json_last_error() !== JSON_ERROR_NONE ) {
			// If PDF was expected but we got non-PDF response
			if ( $pdf ) {
				$this->log( 'PDF Download Error - Invalid response type', 'error' );
				return new \WP_Error( 'ifirma_pdf_error', __( 'Failed to download PDF', 'invoicing-integration-for-ifirma-and-woocommerce' ) );
			}
			$this->log( 'JSON Parse Error: ' . json_last_error_msg(), 'error' );
			return new \WP_Error( 'ifirma_json_error', __( 'Failed to parse JSON response', 'invoicing-integration-for-ifirma-and-woocommerce' ) );
		}

		// Log the response for debugging
		$this->log( 'API Response: ' . wp_json_encode( $data, JSON_UNESCAPED_UNICODE ), 'debug' );

		// Check for API errors - only if response has this structure
		if ( isset( $data['response']['Kod'] ) && $data['response']['Kod'] !== 0 ) {
			$error_message = $data['response']['Informacja'] ?? __( 'Unknown error from iFirma API', 'invoicing-integration-for-ifirma-and-woocommerce' );
			$this->log( 'iFirma API Error: ' . $error_message, 'error' );
			return new \WP_Error( 'ifirma_api_error', $error_message );
		}

		if ( $code !== 200 ) {
			$this->log( 'API Response Error. Status: ' . $code . ', Body: ' . $body, 'error' );
			/* translators: %d: HTTP status code */
			return new \WP_Error( 'ifirma_api_error', sprintf( __( 'API returned status %d', 'invoicing-integration-for-ifirma-and-woocommerce' ), $code ) );
		}

		return $data;
	}

	/**
	 * Redact sensitive data from logs
	 * 
	 * @param string $data Data to redact
	 * @return string
	 */
	private function redact_sensitive( $data ) {
		// Redact API keys
		$data = preg_replace( '/hmac-sha1=[A-Za-z0-9+\/=]+/', 'hmac-sha1=[REDACTED]', $data );
		
		// Redact NIP
		$data = preg_replace( '/"nip"\s*:\s*"[0-9]{10}"/', '"nip":"[REDACTED]"', $data );
		$data = preg_replace( '/"NIP"\s*:\s*"[0-9]{10}"/', '"NIP":"[REDACTED]"', $data );
		
		// Redact bank account
		$data = preg_replace( '/"bank_account"\s*:\s*"[0-9 ]{20,32}"/', '"bank_account":"[REDACTED]"', $data );
		$data = preg_replace( '/"NumerKontaBankowego"\s*:\s*"[0-9 ]{20,32}"/', '"NumerKontaBankowego":"[REDACTED]"', $data );
		
		return $data;
	}

	/**
	 * Build stable contractor identifier (<= 15 chars).
	 *
	 * When we provide a stable identifier (based on NIP/VAT or email), iFirma can match and
	 * update the same contractor instead of creating a new one for every document.
	 *
	 * @param array $customer Customer data
	 * @return string|null
	 */
	private function build_contractor_identifier( $customer ) {
		$nip = isset( $customer['nip'] ) ? (string) $customer['nip'] : '';
		$nip = preg_replace( '/[\s\-]/', '', $nip );

		if ( $nip !== '' ) {
			$nip = strtoupper( $nip );
			$nip = preg_replace( '/[^A-Z0-9]/', '', $nip );
			if ( $nip !== '' && strlen( $nip ) <= 15 ) {
				return $nip;
			}
			return substr( md5( $nip ), 0, 15 );
		}

		$email = isset( $customer['email'] ) ? (string) $customer['email'] : '';
		$email = trim( strtolower( $email ) );
		if ( $email !== '' ) {
			return 'E' . substr( md5( $email ), 0, 14 );
		}

		return null;
	}

	/**
	 * Zamienia kod kraju z WooCommerce na notację akceptowaną przez iFirma przy polach UE (VIES).
	 * Grecja: ISO 3166-1 to GR, w VAT UE / wielu endpointach iFirma oczekuje EL (m.in. PrefiksUE,
	 * OSS: KrajDostawy i Kontrahent.KodKraju, synchronizacja Kontrahent.KodKraju).
	 *
	 * @param string $prefix Dwuliterowy kod (prefiks VAT lub ISO z WC).
	 * @return string
	 */
	private function normalize_prefiks_ue_for_ifirma( $prefix ) {
		$prefix = strtoupper( (string) $prefix );
		return 'GR' === $prefix ? 'EL' : $prefix;
	}

	/**
	 * Kod pocztowy adresu kontrahenta na fakturze / w synchr. Kontrahent: iFirma waliduje pole jako niepuste.
	 * WooCommerce często zapisuje brak kodu (np. ZEA/Dubaj brak kodów przy checkoutcie, zagraniczni klienci pomijają pole).
	 *
	 * Filtr `devikit_ifirma_empty_postcode_placeholder` pozwala nadpisać placeholder (wartość zwrócona przy pustym polu; drugi argument hooka to kod kraju ISO).
	 *
	 * @param string $postcode     Z zamówienia / pola kontrahenta.
	 * @param string $country_code Kod kraju ISO 3166 (dla kontekstu filtrowania).
	 * @return string
	 */
	private function ifirma_kod_pocztowy_for_payload( $postcode, $country_code = '' ) {
		$postcode = trim( preg_replace( '/\s+/u', ' ', (string) $postcode ) );
		if ( '' !== $postcode ) {
			return $postcode;
		}
		$country_code = strtoupper( (string) $country_code );

		return (string) apply_filters( 'devikit_ifirma_empty_postcode_placeholder', '-', $country_code );
	}

	/**
	 * City/Miejscowosc for iFirma payload.
	 *
	 * Falls back to the country name when the billing city is empty — common for
	 * countries where city is not required at checkout (e.g. Singapore, UAE).
	 * Truncates to $max_length: kontrahenci endpoint allows 50 chars, invoice
	 * endpoints allow 65 chars.
	 *
	 * @param string $city       Billing city.
	 * @param string $country    Country name (human-readable, used as fallback).
	 * @param int    $max_length Max character length for the target endpoint.
	 * @return string
	 */
	/**
	 * Street/Ulica for iFirma payload — truncated to the endpoint limit (65 chars).
	 *
	 * @param string $street
	 * @param int    $max_length
	 * @return string
	 */
	private function ifirma_street_for_payload( $street, $max_length = 65 ) {
		return mb_substr( trim( (string) $street ), 0, $max_length );
	}

	private function ifirma_city_for_payload( $city, $country = '', $max_length = 65 ) {
		$city = trim( (string) $city );
		if ( $city !== '' ) {
			return mb_substr( $city, 0, $max_length );
		}
		$country = trim( (string) $country );
		if ( $country !== '' ) {
			return mb_substr( $country, 0, $max_length );
		}
		return (string) apply_filters( 'devikit_ifirma_empty_city_placeholder', '-' );
	}

	/**
	 * NIP + PrefiksUE dla iFirma: PrefiksUE tylko, gdy prefiks to kraj członkowski UE (VIES),
	 * inaczej API zwraca błąd (np. losowy numer zaczynający się od "TE" albo kraj spoza UE).
	 *
	 * @param string $tax_id        NIP / VAT (może zawierać prefiks UE).
	 * @param string $country_code  Kod ISO kraju odbiorcy.
	 * @return array{ nip: string, prefiks_ue: string }
	 */
	private function ifirma_split_tax_id_for_kontrahent( $tax_id, $country_code ) {
		$tax_id = preg_replace( '/[\s\-]/', '', (string) $tax_id );
		$country_code = strtoupper( (string) $country_code );
		$empty = [ 'nip' => '', 'prefiks_ue' => '' ];
		if ( $tax_id === '' ) {
			return $empty;
		}
		$eu = $this->get_eu_countries();
		if ( preg_match( '/^([A-Z]{2})(.+)$/i', $tax_id, $m ) ) {
			$prefix = strtoupper( $m[1] );
			$body   = preg_replace( '/[^0-9A-Za-z]/', '', (string) $m[2] );
			if ( in_array( $prefix, $eu, true ) && 'PL' !== $prefix ) {
				return [
					'nip'         => $body,
					'prefiks_ue'  => $this->normalize_prefiks_ue_for_ifirma( $prefix ),
				];
			}
			return [ 'nip' => preg_replace( '/[^0-9A-Za-z]/', '', $tax_id ), 'prefiks_ue' => '' ];
		}
		$body  = preg_replace( '/[^0-9A-Za-z]/', '', $tax_id );
		$pref  = '';
		if ( $this->is_eu_country( $country_code ) && 'PL' !== $country_code ) {
			$pref = $this->normalize_prefiks_ue_for_ifirma( $country_code );
		}
		return [ 'nip' => $body, 'prefiks_ue' => $pref ];
	}

	/**
	 * Build contractor payload for POST/PUT /kontrahenci.
	 *
	 * @param array       $customer Customer data
	 * @param string|null $identifier Optional identifier to set (for create). For update, identifier comes from URL.
	 * @return array
	 */
	private function build_contractor_payload( $customer, $identifier = null ) {
		$country_code = strtoupper( (string) ( $customer['country_code'] ?? '' ) );
		$country_name = (string) ( $customer['country'] ?? '' );

		$is_company = isset( $customer['is_company'] ) ? (bool) $customer['is_company'] : ! empty( $customer['nip'] );

		$is_foreign = 'PL' !== $country_code && '' !== $country_code;

		$payload = [
			'Nazwa'        => (string) ( $customer['name'] ?? '' ),
			'KodPocztowy'  => $this->ifirma_kod_pocztowy_for_payload( (string) ( $customer['postcode'] ?? '' ), $country_code ),
			'Miejscowosc'  => $this->ifirma_city_for_payload( (string) ( $customer['city'] ?? '' ), $country_name, 50 ),
			'Ulica'        => $this->ifirma_street_for_payload( (string) ( $customer['street'] ?? '' ) ),
			'Kraj'         => $country_name,
			'KodKraju'     => '' !== $country_code ? $this->normalize_prefiks_ue_for_ifirma( $country_code ) : '',
			'Email'        => (string) ( $customer['email'] ?? '' ),
			'Telefon'      => (string) ( $customer['phone'] ?? '' ),
			'OsobaFizyczna' => ! $is_company,
			'JestOdbiorca'  => true,
		];

		// iFirma support explicitly recommends passing AdresZagraniczny=true for
		// non-Polish addresses so that Miejscowosc/KodPocztowy are treated as
		// optional instead of required — prevents validation errors for countries
		// where these fields are not used (e.g. Singapore, UAE).
		if ( $is_foreign ) {
			$payload['AdresZagraniczny'] = true;
		}

		if ( ! empty( $identifier ) ) {
			$payload['Identyfikator'] = (string) $identifier;
		}

		$tax = $this->ifirma_split_tax_id_for_kontrahent( (string) ( $customer['nip'] ?? '' ), $country_code );
		if ( $tax['nip'] !== '' ) {
			$payload['NIP'] = $tax['nip'];
		}
		if ( $tax['prefiks_ue'] !== '' ) {
			$payload['PrefiksUE'] = $tax['prefiks_ue'];
		}

		return $payload;
	}

	/**
	 * Normalize contractor field for comparison.
	 *
	 * @param mixed  $value Field value
	 * @param string $type  Field type: text|email|phone|nip|prefiks|bool
	 * @return mixed
	 */
	private function normalize_contractor_field( $value, $type = 'text' ) {
		if ( $type === 'bool' ) {
			return (bool) $value;
		}

		$value = (string) ( $value ?? '' );

		if ( $type === 'email' ) {
			return strtolower( trim( $value ) );
		}

		if ( $type === 'phone' ) {
			return preg_replace( '/\s+/', '', $value );
		}

		if ( $type === 'prefiks' ) {
			return strtoupper( preg_replace( '/[^A-Z]/', '', $value ) );
		}

		if ( $type === 'nip' ) {
			$value = str_replace( [ ' ', '-' ], '', $value );
			return strtoupper( preg_replace( '/[^A-Z0-9]/', '', $value ) );
		}

		// text
		$value = preg_replace( '/\s+/', ' ', trim( $value ) );
		return $value;
	}

	/**
	 * Compare two contractors (subset of fields) to decide whether data changed.
	 *
	 * @param array $existing Contractor from API search result
	 * @param array $desired  Contractor payload we would send
	 * @return bool
	 */
	private function contractor_matches( $existing, $desired ) {
		$map = [
			'Nazwa'        => 'text',
			'Ulica'        => 'text',
			'KodPocztowy'  => 'text',
			'Miejscowosc'  => 'text',
			'Kraj'         => 'text',
			'KodKraju'     => 'prefiks',
			'PrefiksUE'    => 'prefiks',
			'NIP'          => 'nip',
			'Email'        => 'email',
			'Telefon'      => 'phone',
			'OsobaFizyczna' => 'bool',
			'JestOdbiorca'  => 'bool',
		];

		foreach ( $map as $field => $type ) {
			$existing_val = $this->normalize_contractor_field( $existing[ $field ] ?? '', $type );
			$desired_val  = $this->normalize_contractor_field( $desired[ $field ] ?? '', $type );

			// Some fields may be empty in iFirma even if we pass them; ignore if both are empty.
			if ( $existing_val === '' && $desired_val === '' ) {
				continue;
			}

			if ( $existing_val !== $desired_val ) {
				return false;
			}
		}

		return true;
	}

	/**
	 * Build a new unique contractor identifier for the same NIP when data differs.
	 *
	 * @param array  $customer Customer data
	 * @param array  $desired_payload Desired contractor payload
	 * @param int    $attempt Attempt counter
	 * @return string
	 */
	private function build_contractor_identifier_for_variant( $customer, $desired_payload, $attempt = 0 ) {
		$nip = isset( $customer['nip'] ) ? (string) $customer['nip'] : '';
		$nip = preg_replace( '/[\s\-]/', '', $nip );
		$nip = strtoupper( preg_replace( '/[^A-Z0-9]/', '', $nip ) );

		$fingerprint = wp_json_encode( $desired_payload, JSON_UNESCAPED_UNICODE );
		$salt = md5( $fingerprint . '|' . (string) $attempt );

		if ( $nip !== '' && strlen( $nip ) <= 10 ) {
			// 10 + 5 = 15
			return $nip . substr( $salt, 0, 5 );
		}

		return substr( md5( $nip . '|' . $salt ), 0, 15 );
	}

	/**
	 * Ensure contractor exists and is up to date (by NIP/VAT).
	 *
	 * - If customer has no NIP, returns null (do not create contractors for B2C).
	 * - If found and data is the same, returns its Identyfikator.
	 * - If found but data differs, creates a NEW contractor (safe mode) and returns new Identyfikator.
	 * - If not found, creates contractor via POST and returns its Identyfikator.
	 *
	 * @param array $customer Customer data
	 * @return string|null|WP_Error
	 */
	public function ensure_contractor( $customer ) {
		$nip = isset( $customer['nip'] ) ? (string) $customer['nip'] : '';
		$nip = preg_replace( '/[\s\-]/', '', $nip );
		if ( $nip === '' ) {
			return null;
		}

		// Use the same NIP cleaning as build_contractor_payload so that tax IDs
		// containing dots or slashes (e.g. Brazilian CNPJ "49.057.017/0001-74")
		// don't produce a %2F in the URL and cause a 404.
		$cleaned = $this->ifirma_split_tax_id_for_kontrahent( $nip, $customer['country_code'] ?? '' );
		$search  = $cleaned['nip'] !== '' ? $cleaned['nip'] : $nip;
		$search_response = $this->search_contractors( $search );
		if ( is_wp_error( $search_response ) ) {
			return $search_response;
		}

		$kod = isset( $search_response['response']['Kod'] ) ? (int) $search_response['response']['Kod'] : null;

		$desired_payload = $this->build_contractor_payload( $customer, null );

		if ( $kod === 0 && ! empty( $search_response['response']['Wynik'] ) && is_array( $search_response['response']['Wynik'] ) ) {
			// Find best match by NIP/PrefiksUE first.
			$desired_nip = $this->normalize_contractor_field( $desired_payload['NIP'] ?? '', 'nip' );
			$desired_pref = $this->normalize_contractor_field( $desired_payload['PrefiksUE'] ?? '', 'prefiks' );

			foreach ( $search_response['response']['Wynik'] as $candidate ) {
				$cand_id = $candidate['Identyfikator'] ?? '';
				if ( $cand_id === '' ) {
					continue;
				}

				$cand_nip  = $this->normalize_contractor_field( $candidate['NIP'] ?? '', 'nip' );
				$cand_pref = $this->normalize_contractor_field( $candidate['PrefiksUE'] ?? '', 'prefiks' );

				if ( $desired_nip !== '' && $cand_nip !== '' && $cand_nip !== $desired_nip ) {
					continue;
				}
				if ( $desired_pref !== '' && $cand_pref !== '' && $cand_pref !== $desired_pref ) {
					continue;
				}

				// If data matches, just reuse existing contractor (safe mode: no PUT).
				if ( $this->contractor_matches( $candidate, $desired_payload ) ) {
					return (string) $cand_id;
				}
			}

			// Found contractor(s) but data differs -> create a NEW contractor (safe mode).
			for ( $attempt = 0; $attempt < 3; $attempt++ ) {
				$new_identifier = $this->build_contractor_identifier_for_variant( $customer, $desired_payload, $attempt );
				$payload = $this->build_contractor_payload( $customer, $new_identifier );
				$create = $this->create_contractor( $payload );
				if ( ! is_wp_error( $create ) ) {
					return (string) $new_identifier;
				}
			}

			return $create;
		}

		if ( $kod === 208 ) {
			// Not found -> create base contractor.
			$new_identifier = $this->build_contractor_identifier( $customer );
			$payload = $this->build_contractor_payload( $customer, $new_identifier );
			$create = $this->create_contractor( $payload );
			if ( is_wp_error( $create ) ) {
				return $create;
			}

			return (string) $new_identifier;
		}

		// Unexpected structure; do nothing.
		return null;
	}

	/**
	 * Log message
	 * 
	 * @param string $message Log message
	 * @param string $level Log level: 'error', 'warning', 'info', 'debug'
	 */
	public function log( $message, $level = 'error' ) {
		if ( ! class_exists( 'WC_Logger' ) ) {
			return;
		}

		$logger = wc_get_logger();
		
		// Check if debug logging is enabled
		$settings = get_option( 'devikit_ifirma_settings', [] );
		$debug_enabled = isset( $settings['enable_debug_logging'] ) && $settings['enable_debug_logging'] === 'yes';
		
		if ( $debug_enabled ) {
			// Log to debug file when enabled
			$logger->log( $level, $message, [ 'source' => 'devikit_ifirma_debug' ] );
		} elseif ( $level === 'error' ) {
			// Always log errors to main file
			$logger->error( $message, [ 'source' => 'invoicing-integration-for-ifirma-and-woocommerce' ] );
		}
	}

	/**
	 * Sanitize floats to avoid IEEE 754 precision issues
	 * 
	 * Recursively processes array and rounds floats to appropriate precision.
	 * This prevents issues like 0.23 becoming 0.23000000000000001 in JSON.
	 * 
	 * @param mixed $data Data to sanitize
	 * @return mixed Sanitized data
	 */
	private function sanitize_floats( $data ) {
		if ( is_array( $data ) ) {
			foreach ( $data as $key => $value ) {
				$data[ $key ] = $this->sanitize_floats( $value );
			}
			return $data;
		}
		
		if ( is_float( $data ) ) {
			// Round to 4 decimal places, then convert through string to ensure clean value
			return floatval( number_format( $data, 4, '.', '' ) );
		}
		
		return $data;
	}

	/**
	 * Get EU country codes (excluding Poland for WDT purposes)
	 * 
	 * @return array EU country codes
	 */
	public function get_eu_countries() {
		return [
			'AT', // Austria
			'BE', // Belgium
			'BG', // Bulgaria
			'CY', // Cyprus
			'CZ', // Czech Republic
			'DE', // Germany
			'DK', // Denmark
			'EE', // Estonia
			'EL', // Greece (alternative code)
			'GR', // Greece
			'ES', // Spain
			'FI', // Finland
			'FR', // France
			'HR', // Croatia
			'HU', // Hungary
			'IE', // Ireland
			'IT', // Italy
			'LT', // Lithuania
			'LU', // Luxembourg
			'LV', // Latvia
			'MT', // Malta
			'NL', // Netherlands
			'PT', // Portugal
			'RO', // Romania
			'SE', // Sweden
			'SI', // Slovenia
			'SK', // Slovakia
		];
	}

	/**
	 * Check if country is in EU (excluding Poland)
	 * 
	 * @param string $country_code Country code (ISO 2-letter)
	 * @return bool
	 */
	public function is_eu_country( $country_code ) {
		$country_code = strtoupper( $country_code );
		return in_array( $country_code, $this->get_eu_countries(), true );
	}

	/**
	 * Check if WooCommerce tax is enabled
	 * 
	 * @return bool
	 */
	public function is_tax_enabled() {
		return function_exists( 'wc_tax_enabled' ) && wc_tax_enabled();
	}

	/**
	 * Get NBP average exchange rate for the last business day BEFORE given date.
	 * Used for foreign-currency invoices (fakturawaluta) to fill:
	 * KursWalutyZDniaPoprzedzajacegoDzienWystawieniaFaktury.
	 *
	 * @param string $currency Currency code, e.g. EUR
	 * @param string $date     Date in Y-m-d
	 * @return float
	 */
	private function get_nbp_rate_before_date( $currency, $date ) {
		$currency = strtoupper( trim( (string) $currency ) );
		if ( $currency === '' || $currency === 'PLN' ) {
			return 1.0;
		}

		$cache_key = 'devikit_ifirma_nbp_rate_before_' . strtolower( $currency ) . '_' . preg_replace( '/[^0-9\-]/', '', (string) $date );
		$cached = get_transient( $cache_key );
		if ( $cached !== false && is_numeric( $cached ) ) {
			return floatval( $cached );
		}

		$base_ts = strtotime( (string) $date . ' 00:00:00' );
		if ( ! $base_ts ) {
			$base_ts = time();
		}

		// Try up to 10 days back to find last business day rate.
		for ( $i = 1; $i <= 10; $i++ ) {
			$try_date = gmdate( 'Y-m-d', $base_ts - ( $i * DAY_IN_SECONDS ) );
			$url = 'https://api.nbp.pl/api/exchangerates/rates/A/' . rawurlencode( $currency ) . '/' . rawurlencode( $try_date ) . '/?format=json';

			$response = wp_remote_get(
				$url,
				[
					'timeout' => 10,
					'headers' => [
						'Accept' => 'application/json',
					],
				]
			);

			if ( is_wp_error( $response ) ) {
				continue;
			}

			$code = wp_remote_retrieve_response_code( $response );
			if ( (int) $code !== 200 ) {
				continue;
			}

			$body = wp_remote_retrieve_body( $response );
			$data = json_decode( $body, true );
			$mid = $data['rates'][0]['mid'] ?? null;
			if ( $mid !== null && is_numeric( $mid ) ) {
				$rate = floatval( $mid );
				// Cache for 24h.
				set_transient( $cache_key, (string) $rate, DAY_IN_SECONDS );
				return $rate;
			}
		}

		// Fallback to 1.0 if we can't fetch a rate.
		$this->log( 'NBP rate not found for ' . $currency . ' before ' . $date . ' - falling back to 1.0', 'warning' );
		return 1.0;
	}

	/**
	 * Detect invoice type based on order data
	 * 
	 * Invoice type detection rules:
	 * - Currency PLN + Poland = domestic invoice (fakturakraj)
	 * - EU + 0% VAT + EU VAT number = WDT (fakturawdt)
	 * - EU + VAT + no EU VAT number + OSS enabled = OSS (fakturaoss)
	 * - Currency != PLN (and not WDT/OSS) = Foreign currency invoice (fakturawaluta)
	 * - Country != PL (and not WDT/OSS) = Foreign invoice (fakturawaluta), also when order currency is PLN (common in PL shops)
	 * - Country PL + currency PLN = domestic (fakturakraj)
	 * 
	 * Note: For non-VAT payers (wc_tax_enabled = false), same endpoints are used
	 * but with modified payload (all items as ZW with PodstawaPrawna)
	 * 
	 * @param array $data Invoice data with customer info
	 * @return string Invoice type: 'domestic', 'wdt', 'oss', 'foreign'
	 */
	public function detect_invoice_type( $data ) {
		$country = strtoupper( $data['customer']['country_code'] ?? 'PL' );
		$currency = strtoupper( $data['currency'] ?? 'PLN' );
		
		// Note: we intentionally do NOT force PL customers to "domestic" here.
		// If currency != PLN, we will return "foreign" at the end (fakturawaluta),
		// which allows issuing domestic sales in a foreign currency (e.g. EUR store).
		
		// If tax is not enabled, use domestic invoice (will be modified to ZW in payload)
		if ( ! $this->is_tax_enabled() ) {
			return 'domestic';
		}
		
		// Check for EU VAT number (starts with EU country code, not Poland)
		$nip = $data['customer']['nip'] ?? '';
		$has_eu_vat_number = $this->is_eu_vat_number( $nip );
		
		// Check if order has 0% tax (all items have vat = '0' or 'zw')
		$is_zero_tax = $this->is_zero_tax_order( $data );
		
		// EU country + 0% VAT + EU VAT number = WDT (regardless of currency)
		if ( $this->is_eu_country( $country ) && $is_zero_tax && $has_eu_vat_number ) {
			return 'wdt';
		}
		
		// Check if OSS is enabled. PRO gates this via filter (license check inside PRO).
		$settings = get_option( 'devikit_ifirma_settings', [] );
		$oss_enabled = isset( $settings['enable_oss'] ) && $settings['enable_oss'] === 'yes';
		$oss_enabled = (bool) apply_filters( 'devikit_ifirma_oss_enabled', $oss_enabled );
		
		// EU country + VAT (not 0%) + no EU VAT number + OSS enabled = OSS
		if ( $this->is_eu_country( $country ) && ! $is_zero_tax && ! $has_eu_vat_number && $oss_enabled ) {
			return 'oss';
		}
		
		// Only Polish customers (billing country PL) with PLN can use fakturakraj. Foreign billing address
		// must use fakturawaluta (TypSprzedazy WYSYLKOWA), even when the order is priced in PLN.
		if ( $country === 'PL' ) {
			if ( $currency === 'PLN' ) {
				return 'domestic';
			}
			return 'foreign';
		}
		
		return 'foreign';
	}

	/**
	 * Check if VAT number is EU VAT number (starts with EU country prefix, not Poland)
	 * 
	 * @param string $vat_number VAT number
	 * @return bool
	 */
	public function is_eu_vat_number( $vat_number ) {
		if ( empty( $vat_number ) ) {
			return false;
		}
		
		$prefix = strtoupper( substr( $vat_number, 0, 2 ) );
		
		// Must be EU country and not Poland
		return $prefix !== 'PL' && in_array( $prefix, $this->get_eu_countries(), true );
	}

	/**
	 * Check if order has 0% tax (for WDT detection)
	 * 
	 * @param array $data Invoice data
	 * @return bool
	 */
	private function is_zero_tax_order( $data ) {
		if ( empty( $data['items'] ) ) {
			return false;
		}
		
		foreach ( $data['items'] as $item ) {
			$vat = strtolower( $item['vat'] ?? '23' );
			// 0%, zw (zwolniony) are considered "zero tax"
			if ( ! in_array( $vat, [ '0', 'zw' ], true ) ) {
				return false;
			}
		}
		
		return true;
	}

	/**
	 * Create WDT invoice (intra-community delivery)
	 * 
	 * @param array $data Invoice data
	 * @return array|WP_Error
	 */
	public function create_wdt_invoice( $data ) {
		if ( empty( $this->api_login ) || empty( $this->api_key_faktura ) ) {
			return new \WP_Error( 'no_credentials', __( 'Login and API Key Faktura are required', 'invoicing-integration-for-ifirma-and-woocommerce' ) );
		}

		$this->sync_accounting_month_to_date( $data['sale_date'] ?? $data['date'] ?? '' );

		$this->log( 'Creating WDT invoice with data: ' . $this->redact_sensitive( wp_json_encode( $data, JSON_UNESCAPED_UNICODE ) ), 'debug' );
		
		$payload = $this->build_wdt_payload( $data );
		
		$response = $this->post( 'fakturawdt.json', $payload, 'faktura' );

		if ( is_wp_error( $response ) ) {
			return $response;
		}

		$this->log( 'WDT Invoice API response: ' . wp_json_encode( $response, JSON_UNESCAPED_UNICODE ), 'debug' );
		return $response;
	}

	/**
	 * Build WDT invoice payload for iFirma API
	 * 
	 * @param array $data Invoice data
	 * @return array
	 */
	private function build_wdt_payload( $data ) {
		$customer = $data['customer'];
		
		// Map payment method
		$payment_method = 'PRZ';
		if ( isset( $data['payment_method'] ) ) {
			$wc_payment = $data['payment_method'];
			if ( in_array( $wc_payment, [ 'cod', 'cash' ], true ) ) {
				$payment_method = 'GTK';
			} elseif ( in_array( $wc_payment, [ 'paypal', 'stripe', 'blik', 'przelewy24', 'payu', 'tpay' ], true ) ) {
				$payment_method = 'ELE';
			}
		}
		
		// Build items - WDT has 0% VAT (NP), no PKWiU
		$positions = [];
		foreach ( $data['items'] as $item ) {
			$position = [
				'StawkaVat'       => 0.00, // WDT is 0% VAT (NP)
				'Ilosc'           => floatval( number_format( floatval( $item['quantity'] ), 4, '.', '' ) ),
				'CenaJednostkowa' => floatval( number_format( $this->get_item_unit_price_for_ifirma_position( $item ), 2, '.', '' ) ),
				'NazwaPelna'      => $item['name'],
				'NazwaPelnaObca'  => $item['name'], // Foreign name (same as Polish for now)
				'Jednostka'       => $item['unit'] ?? 'szt.',
				'JednostkaObca'   => 'pcs', // Foreign unit
				'TypStawkiVat'    => 'PRC',
			];

			// Lump sum (ryczałt) rate if provided (decimal, e.g. 0.0850)
			if ( isset( $item['lump_sum_rate_decimal'] ) && $item['lump_sum_rate_decimal'] !== null ) {
				$position['StawkaRyczaltu'] = floatval( number_format( floatval( $item['lump_sum_rate_decimal'] ), 4, '.', '' ) );
			}
			
			$positions[] = $position;
		}
		
		$tax = $this->ifirma_split_tax_id_for_kontrahent( (string) ( $customer['nip'] ?? '' ), (string) ( $customer['country_code'] ?? '' ) );
		
		// Build kontrahent object
		$kontrahent = [
			'Nazwa'       => $customer['name'],
			'Ulica'       => $this->ifirma_street_for_payload( $customer['street'] ?? '' ),
			'KodPocztowy' => $this->ifirma_kod_pocztowy_for_payload( (string) ( $customer['postcode'] ?? '' ), (string) ( $customer['country_code'] ?? '' ) ),
			'Miejscowosc' => $this->ifirma_city_for_payload( $customer['city'] ?? '', $customer['country'] ?? '' ),
			'Kraj'        => $customer['country'] ?? '',
			'Email'       => $customer['email'] ?? '',
			'Telefon'     => $customer['phone'] ?? '',
		];
		
		if ( $tax['nip'] !== '' ) {
			$kontrahent['NIP'] = $tax['nip'];
		}
		if ( $tax['prefiks_ue'] !== '' ) {
			$kontrahent['PrefiksUE'] = $tax['prefiks_ue'];
		}
		
		// Get language for invoice based on country
		$language = $this->get_language_for_country( $customer['country_code'] ?? '' );
		
		$currency = strtoupper( $data['currency'] ?? 'PLN' );
		
		$paid_amount = floatval( number_format( floatval( $data['paid'] ?? 0 ), 2, '.', '' ) );
		
		$payload = [
			'Zaplacono'              => $paid_amount,
			'ZaplaconoNaDokumencie'  => $paid_amount,
			'DataWystawienia'        => $data['date'],
			'DataSprzedazy'          => $data['sale_date'],
			'FormatDatySprzedazy'    => 'DZN',
			'SposobZaplaty'          => $payment_method,
			'RodzajPodpisuOdbiorcy'  => 'BPO',
			'Jezyk'                  => $language,
			'Waluta'                 => $currency,
			'Kontrahent'             => $kontrahent,
			'Pozycje'                => $positions,
			'Uwagi'                  => __( 'Reverse charge - VAT is to be accounted for by the recipient.', 'invoicing-integration-for-ifirma-and-woocommerce' ),
		];
		if ( ! empty( $data['notes'] ) ) {
			$payload['Uwagi'] = trim( (string) $payload['Uwagi'] . "\n" . (string) $data['notes'] );
		}
		if ( ! empty( $data['contractor_identifier'] ) ) {
			$payload['IdentyfikatorKontrahenta'] = (string) $data['contractor_identifier'];
		}
		
		// Add place of issue if set
		if ( ! empty( $data['place_of_issue'] ) ) {
			$payload['MiejsceWystawienia'] = $data['place_of_issue'];
		}
		
		// Add payment deadline
		if ( ! empty( $data['payment_date'] ) ) {
			$payload['TerminPlatnosci'] = $data['payment_date'];
		}
		
		// Add bank account if set and payment is bank transfer
		if ( $payment_method === 'PRZ' && ! empty( $data['bank_account'] ) ) {
			$payload['NumerKontaBankowego'] = str_replace( [ ' ', '-' ], '', $data['bank_account'] );
		}
		
		// Add series if set
		if ( ! empty( $data['series'] ) ) {
			$payload['NazwaSeriiNumeracji'] = $data['series'];
		}
		
		// Required by iFirma for WDT when Waluta != PLN (kurs NBP z dnia poprzedzającego wystawienie).
		if ( $currency !== 'PLN' ) {
			$rate = $this->get_nbp_rate_before_date( $currency, $data['sale_date'] ?? $data['date'] ?? '' );
			$payload['KursWalutyZDniaPoprzedzajacegoDzienWystawieniaFaktury'] = floatval( number_format( $rate, 4, '.', '' ) );
		}
		
		return $payload;
	}

	/**
	 * Get invoice language code for country
	 * 
	 * @param string $country_code Country code
	 * @return string Language code for iFirma API
	 */
	private function get_language_for_country( $country_code ) {
		$country_code = strtoupper( $country_code );
		
		// Only languages actually supported by iFirma API are listed here.
		// Romanian (RO) and Portuguese (PT) are NOT supported — they fall back to 'en'.
		$language_map = [
			'DE' => 'de', // German
			'AT' => 'de', // Austria - German
			'FR' => 'fr', // French
			'ES' => 'es', // Spanish
			'IT' => 'it', // Italian
			'NL' => 'nl', // Dutch
			'BE' => 'nl', // Belgium - Dutch
			'SE' => 'sv', // Swedish
			'DK' => 'da', // Danish
			'FI' => 'fi', // Finnish
			'CZ' => 'cs', // Czech
			'SK' => 'sk', // Slovak
			'HU' => 'hu', // Hungarian
			'BG' => 'bg', // Bulgarian
			'HR' => 'hr', // Croatian
			'SI' => 'sl', // Slovenian
			'EE' => 'et', // Estonian
			'LV' => 'lv', // Latvian
			'LT' => 'lt', // Lithuanian
			'GR' => 'gr', // Greek
			'EL' => 'gr', // Greece (alternative ISO code)
		];

		return $language_map[ $country_code ] ?? 'en'; // Default to English for unsupported languages
	}

	/**
	 * Get WDT invoice details
	 * 
	 * @param int $document_id Document ID
	 * @return array|WP_Error
	 */
	public function get_wdt_invoice_details( $document_id ) {
		$endpoint = 'fakturawdt/' . $document_id . '.json';
		return $this->get( $endpoint, 'faktura' );
	}

	/**
	 * Download WDT invoice PDF
	 * 
	 * @param int $document_id Document ID
	 * @return string|WP_Error PDF content
	 */
	public function download_wdt_pdf( $document_id ) {
		$endpoint = 'fakturawdt/' . $document_id . '.pdf';
		return $this->get( $endpoint, 'faktura', true );
	}

	/**
	 * Create foreign currency invoice (for non-EU or EU without EU VAT number)
	 * 
	 * @param array $data Invoice data
	 * @return array|WP_Error
	 */
	public function create_foreign_invoice( $data ) {
		if ( empty( $this->api_login ) || empty( $this->api_key_faktura ) ) {
			return new \WP_Error( 'no_credentials', __( 'Login and API Key Faktura are required', 'invoicing-integration-for-ifirma-and-woocommerce' ) );
		}

		$this->sync_accounting_month_to_date( $data['sale_date'] ?? $data['date'] ?? '' );

		$this->log( 'Creating foreign currency invoice with data: ' . $this->redact_sensitive( wp_json_encode( $data, JSON_UNESCAPED_UNICODE ) ), 'debug' );
		
		$payload = $this->build_foreign_payload( $data );
		
		$response = $this->post( 'fakturawaluta.json', $payload, 'faktura' );

		if ( is_wp_error( $response ) ) {
			return $response;
		}

		$this->log( 'Foreign Invoice API response: ' . wp_json_encode( $response, JSON_UNESCAPED_UNICODE ), 'debug' );
		return $response;
	}

	/**
	 * Build foreign currency invoice payload for iFirma API
	 * 
	 * Foreign currency invoice supports VAT rates - it's for EU/foreign orders
	 * with non-PLN currency. VAT rate is passed from the order.
	 * 
	 * @param array $data Invoice data
	 * @return array
	 */
	private function build_foreign_payload( $data ) {
		$customer = $data['customer'];
		
		// Map payment method
		$payment_method = 'PRZ';
		if ( isset( $data['payment_method'] ) ) {
			$wc_payment = $data['payment_method'];
			if ( in_array( $wc_payment, [ 'cod', 'cash' ], true ) ) {
				$payment_method = 'GTK';
			} elseif ( in_array( $wc_payment, [ 'paypal', 'stripe', 'blik', 'przelewy24', 'payu', 'tpay' ], true ) ) {
				$payment_method = 'ELE';
			}
		}
		
		// Build items - pass VAT rates from order (unlike WDT which is always 0%)
		$positions = [];
		foreach ( $data['items'] as $item ) {
			$vat_rate = $this->map_vat_symbol_to_ifirma( $item['vat'] ?? '23' );
			$vat_type = $this->get_vat_type( $item['vat'] ?? '23' );
			
			$position = [
				'StawkaVat'       => $vat_rate,
				'Ilosc'           => floatval( number_format( floatval( $item['quantity'] ), 4, '.', '' ) ),
				'CenaJednostkowa' => floatval( number_format( $this->get_item_unit_price_for_ifirma_position( $item ), 2, '.', '' ) ),
				'NazwaPelna'      => $item['name'],
				'NazwaPelnaObca'  => $item['name'], // Foreign name
				'Jednostka'       => $item['unit'] ?? 'szt.',
				'JednostkaObca'   => 'pcs', // Foreign unit
				'TypStawkiVat'    => $vat_type,
			];

			// Lump sum (ryczałt) rate if provided (decimal, e.g. 0.0850)
			if ( isset( $item['lump_sum_rate_decimal'] ) && $item['lump_sum_rate_decimal'] !== null ) {
				$position['StawkaRyczaltu'] = floatval( number_format( floatval( $item['lump_sum_rate_decimal'] ), 4, '.', '' ) );
			}
			
			$positions[] = $position;
		}
		
		// Build kontrahent object
		$kontrahent = [
			'Nazwa'       => $customer['name'],
			'Ulica'       => $this->ifirma_street_for_payload( $customer['street'] ?? '' ),
			'KodPocztowy' => $this->ifirma_kod_pocztowy_for_payload( (string) ( $customer['postcode'] ?? '' ), (string) ( $customer['country_code'] ?? '' ) ),
			'Miejscowosc' => $this->ifirma_city_for_payload( $customer['city'] ?? '', $customer['country'] ?? '' ),
			'Kraj'        => $customer['country'] ?? '',
			'Email'       => $customer['email'] ?? '',
			'Telefon'     => $customer['phone'] ?? '',
		];
		
		// Add NIP if present
		if ( ! empty( $customer['nip'] ) ) {
			$kontrahent['NIP'] = $customer['nip'];
		}
		
		// Get language for invoice based on country
		$language = $this->get_language_for_country( $customer['country_code'] ?? '' );
		
		// Get currency (default to PLN if not set)
		$currency = strtoupper( trim( (string) ( $data['currency'] ?? 'PLN' ) ) );
		if ( $currency === '' ) {
			$currency = 'PLN';
		}
		
		$paid_amount = floatval( number_format( floatval( $data['paid'] ?? 0 ), 2, '.', '' ) );
		
		$payload = [
			'Zaplacono'              => $paid_amount,
			'ZaplaconoNaDokumencie'  => $paid_amount,
			'LiczOd'                 => $this->get_licz_od_for_vat_split_document(),
			'DataWystawienia'        => $data['date'],
			'DataSprzedazy'          => $data['sale_date'],
			'FormatDatySprzedazy'    => 'DZN',
			'SposobZaplaty'          => $payment_method,
			'RodzajPodpisuOdbiorcy'  => 'BPO',
			'Jezyk'                  => $language,
			'Waluta'                 => $currency,
			'Kontrahent'             => $kontrahent,
			'Pozycje'                => $positions,
		];
		if ( ! empty( $data['contractor_identifier'] ) ) {
			$payload['IdentyfikatorKontrahenta'] = (string) $data['contractor_identifier'];
		}

		// Sale type (TypSprzedazy) is required by iFirma for fakturawaluta.
		// Allowed values (per API validation): KRAJOWA or WYSYLKOWA.
		$customer_country_code = strtoupper( (string) ( $customer['country_code'] ?? 'PL' ) );
		if ( $customer_country_code === 'PL' ) {
			$payload['TypSprzedazy'] = 'KRAJOWA';
		} else {
			$payload['TypSprzedazy'] = 'WYSYLKOWA';
		}

		// Kurs i KursWalutyWidoczny tylko przy walucie innej niż PLN; przy PLN iFirma zwraca błąd w stylu
		// "Kurs waluty jest podawany tylko w przypadku faktury wystawianej w walucie innej niż PLN" jeśli pole kursu jest w JSON.
		if ( $currency !== 'PLN' ) {
			$payload['KursWalutyWidoczny'] = false;
			$rate = $this->get_nbp_rate_before_date( $currency, $data['sale_date'] ?? $data['date'] ?? '' );
			$payload['KursWalutyZDniaPoprzedzajacegoDzienWystawieniaFaktury'] = floatval( number_format( $rate, 4, '.', '' ) );
		}
		
		// Add place of issue if set
		if ( ! empty( $data['place_of_issue'] ) ) {
			$payload['MiejsceWystawienia'] = $data['place_of_issue'];
		}
		
		// Add payment deadline
		if ( ! empty( $data['payment_date'] ) ) {
			$payload['TerminPlatnosci'] = $data['payment_date'];
		}
		
		// Add bank account if set and payment is bank transfer
		if ( $payment_method === 'PRZ' && ! empty( $data['bank_account'] ) ) {
			$payload['NumerKontaBankowego'] = str_replace( [ ' ', '-' ], '', $data['bank_account'] );
		}
		
		// Add series if set
		if ( ! empty( $data['series'] ) ) {
			$payload['NazwaSeriiNumeracji'] = $data['series'];
		}

		// Add notes (Uwagi) if provided
		if ( ! empty( $data['notes'] ) ) {
			$payload['Uwagi'] = (string) $data['notes'];
		}
		
		return $payload;
	}

	/**
	 * Get foreign currency invoice details
	 * 
	 * @param int $document_id Document ID
	 * @return array|WP_Error
	 */
	public function get_foreign_invoice_details( $document_id ) {
		$endpoint = 'fakturawaluta/' . $document_id . '.json';
		return $this->get( $endpoint, 'faktura' );
	}

	/**
	 * Download foreign currency invoice PDF
	 * 
	 * @param int $document_id Document ID
	 * @return string|WP_Error PDF content
	 */
	public function download_foreign_pdf( $document_id ) {
		$endpoint = 'fakturawaluta/' . $document_id . '.pdf';
		return $this->get( $endpoint, 'faktura', true );
	}

	/**
	 * Create OSS invoice (One-Stop Shop for EU B2C sales)
	 * 
	 * Used for B2C sales to EU countries with customer's country VAT rate.
	 * 
	 * @param array $data Invoice data
	 * @return array|WP_Error
	 */
	public function create_oss_invoice( $data ) {
		if ( empty( $this->api_login ) || empty( $this->api_key_faktura ) ) {
			return new \WP_Error( 'no_credentials', __( 'Login and API Key Faktura are required', 'invoicing-integration-for-ifirma-and-woocommerce' ) );
		}

		$this->sync_accounting_month_to_date( $data['sale_date'] ?? $data['date'] ?? '' );

		$this->log( 'Creating OSS invoice with data: ' . $this->redact_sensitive( wp_json_encode( $data, JSON_UNESCAPED_UNICODE ) ), 'debug' );
		
		$payload = $this->build_oss_payload( $data );
		
		$response = $this->post( 'fakturaoss.json', $payload, 'faktura' );

		if ( is_wp_error( $response ) ) {
			return $response;
		}

		$this->log( 'OSS Invoice API response: ' . wp_json_encode( $response, JSON_UNESCAPED_UNICODE ), 'debug' );
		return $response;
	}

	/**
	 * Build OSS invoice payload for iFirma API
	 * 
	 * OSS invoice is similar to domestic but with foreign customer and their country's VAT rate.
	 * 
	 * @param array $data Invoice data
	 * @return array
	 */
	private function build_oss_payload( $data ) {
		$customer = $data['customer'];
		
		// Map payment method
		$payment_method = 'PRZ';
		if ( isset( $data['payment_method'] ) ) {
			$wc_payment = $data['payment_method'];
			if ( in_array( $wc_payment, [ 'cod', 'cash' ], true ) ) {
				$payment_method = 'GTK';
			} elseif ( in_array( $wc_payment, [ 'paypal', 'stripe', 'blik', 'przelewy24', 'payu', 'tpay' ], true ) ) {
				$payment_method = 'ELE';
			}
		}
		
		// Build items - use actual VAT rate from order (customer's country VAT)
		// For OSS invoices, we must use the actual VAT rate from the order, not a symbol
		$positions = [];
		foreach ( $data['items'] as $item ) {
			$price_net = floatval( $item['price_net'] ?? 0 );
			$price_gross = floatval( $item['price_gross'] ?? $price_net );
			
			// Use actual VAT rate from order if available, otherwise calculate from prices
			$vat_rate = null;
			$vat_type = 'ZW'; // Default to exempt
			
			// Prefer vat_rate_decimal from order data (calculated from tax rates)
			if ( isset( $item['vat_rate_decimal'] ) && $item['vat_rate_decimal'] !== null ) {
				$vat_rate = floatval( $item['vat_rate_decimal'] );
			} elseif ( $price_net > 0 && $price_gross > $price_net ) {
				// Fallback: calculate VAT rate from prices
				$vat_amount = $price_gross - $price_net;
				$vat_rate = floatval( number_format( $vat_amount / $price_net, 4, '.', '' ) );
			} elseif ( $price_net > 0 && $price_gross == $price_net ) {
				// Zero VAT
				$vat_rate = 0.00;
			}
			
			// Determine VAT type based on rate
			// For OSS, we use POD (podstawowa) for standard rates, PR1/PR2/PR3 for reduced rates
			// Since OSS rates vary by country, we'll use POD for most cases
			// API will validate if the rate is correct for the delivery country
			if ( $vat_rate !== null ) {
				if ( $vat_rate > 0 ) {
					$vat_type = 'POD'; // Standard rate (podstawowa)
				} else {
					$vat_type = 'ZW'; // Zero VAT
				}
			}
			
			$position = [
				'StawkaVat'       => $vat_rate, // Actual VAT rate from order (e.g., 0.19 for 19% in Germany)
				'Ilosc'           => floatval( number_format( floatval( $item['quantity'] ), 4, '.', '' ) ),
				'CenaJednostkowa' => floatval( number_format( $this->get_item_unit_price_for_ifirma_position( $item ), 2, '.', '' ) ),
				'NazwaPelna'      => $item['name'],
				'NazwaPelnaObca'  => $item['name'],
				'Jednostka'       => $item['unit'] ?? 'szt.',
				'JednostkaObca'   => 'pcs',
				'TypStawkiVat'    => $vat_type,
			];

			// Lump sum (ryczałt) rate if provided (decimal, e.g. 0.0850)
			if ( isset( $item['lump_sum_rate_decimal'] ) && $item['lump_sum_rate_decimal'] !== null ) {
				$position['StawkaRyczaltu'] = floatval( number_format( floatval( $item['lump_sum_rate_decimal'] ), 4, '.', '' ) );
			}
			
			$positions[] = $position;
		}
		
		// WooCommerce: Grecja ma kod GR; OSS w iFirma oczekuje EL w KrajDostawy / KodKraju kontrahenta.
		$woo_customer_country    = strtoupper( (string) ( $customer['country_code'] ?? '' ) );
		$ifirma_customer_country = '' !== $woo_customer_country ? $this->normalize_prefiks_ue_for_ifirma( $woo_customer_country ) : '';
		
		// Get customer country name (full name, not code)
		$customer_country_name = $customer['country'] ?? '';
		if ( empty( $customer_country_name ) && function_exists( 'WC' ) && WC()->countries ) {
			$countries = WC()->countries->get_countries();
			$customer_country_name = $countries[ $woo_customer_country ] ?? $woo_customer_country;
		}
		
		// Get shop base country (country of store owner)
		$shop_base_country = 'PL'; // Default to Poland
		if ( function_exists( 'WC' ) && WC()->countries ) {
			$shop_base_country = strtoupper( WC()->countries->get_base_country() );
		}
		
		// Determine if this is a service sale (check if all items are services)
		// For OSS, typically if there are products it's goods (false), if only services it's services (true)
		// We'll default to false (goods) unless we detect only services
		$is_service_sale = false;
		// Check if all items are services (e.g., shipping, digital products)
		// For now, we'll assume it's goods unless explicitly marked as service
		// This can be enhanced later with product meta to mark items as services
		
		// Determine if customer is a natural person (no NIP = natural person)
		$is_natural_person = empty( $customer['nip'] );
		
		// Build kontrahent object - matching API documentation structure
		$kontrahent = [
			'Nazwa'        => $customer['name'],
			'OsobaFizyczna'=> $is_natural_person,
			'JestOdbiorca' => true,
			'Ulica'        => $this->ifirma_street_for_payload( $customer['street'] ?? '' ),
			'KodPocztowy'  => $this->ifirma_kod_pocztowy_for_payload( (string) ( $customer['postcode'] ?? '' ), $woo_customer_country ),
			'Miejscowosc'  => $this->ifirma_city_for_payload( $customer['city'] ?? '', $customer_country_name ?? '' ),
			'Kraj'         => $customer_country_name, // Full country name (e.g., "Niemcy") - required for OSS
			'KodKraju'     => $ifirma_customer_country, // Kody UE jak w VAT (GR → EL dla Grecji)
			'Email'        => $customer['email'] ?? '',
			'Telefon'      => $customer['phone'] ?? '',
		];
		
		// Add NIP if present (though typically OSS is for B2C without VAT number)
		if ( ! empty( $customer['nip'] ) ) {
			$kontrahent['NIP'] = $customer['nip'];
		}
		
		// Get language for invoice based on customer country
		$language = $this->get_language_for_country( $woo_customer_country );
		
		// Get currency
		$currency = strtoupper( $data['currency'] ?? 'PLN' );
		
		$paid_amount = floatval( number_format( floatval( $data['paid'] ?? 0 ), 2, '.', '' ) );
		
		$payload = [
			'Zaplacono'              => $paid_amount,
			'ZaplaconoNaDokumencie'  => $paid_amount,
			'LiczOd'                 => $this->get_licz_od_for_vat_split_document(),
			'DataWystawienia'        => $data['date'],
			'DataSprzedazy'          => $data['sale_date'],
			'FormatDatySprzedazy'    => 'DZN',
			'SposobZaplaty'          => $payment_method,
			'RodzajPodpisuOdbiorcy'  => 'BPO',
			'WidocznyNumerBdo'       => false, // Required for OSS - visibility of BDO/GIOŚ number
			'Numer'                  => null,
			'Jezyk'                  => $language,
			'Waluta'                 => $currency, // Required for OSS - currency code
			'SprzedazUslug'          => $is_service_sale, // Required for OSS - true if services, false if goods
			'KrajDostawy'            => $ifirma_customer_country, // Kody członkowskie UE jak w VAT (np. EL zamiast GR)
			'KrajWysylki'            => $shop_base_country, // Required for OSS - Shop owner country - ISO 2-letter code (e.g., "PL")
			'Kontrahent'             => $kontrahent,
			'Pozycje'                => $positions,
		];
		if ( ! empty( $data['contractor_identifier'] ) ) {
			$payload['IdentyfikatorKontrahenta'] = (string) $data['contractor_identifier'];
		}

		// When Waluta != PLN, iFirma requires NBP exchange rate from the last business day before the sale/issue date.
		if ( strtoupper( (string) $currency ) !== 'PLN' ) {
			$rate_date = $data['sale_date'] ?? $data['date'] ?? '';
			$rate = $this->get_nbp_rate_before_date( $currency, $rate_date );
			$payload['KursWalutyZDniaPoprzedzajacegoDzienWystawieniaFaktury'] = floatval( number_format( $rate, 4, '.', '' ) );
		}
		
		// Add place of issue if set
		if ( ! empty( $data['place_of_issue'] ) ) {
			$payload['MiejsceWystawienia'] = $data['place_of_issue'];
		}
		
		// Add payment deadline
		if ( ! empty( $data['payment_date'] ) ) {
			$payload['TerminPlatnosci'] = $data['payment_date'];
		}
		
		// Add bank account if set and payment is bank transfer
		if ( $payment_method === 'PRZ' && ! empty( $data['bank_account'] ) ) {
			$payload['NumerKontaBankowego'] = str_replace( [ ' ', '-' ], '', $data['bank_account'] );
		} elseif ( $payment_method === 'PRZ' ) {
			$payload['NumerKontaBankowego'] = 'BRAK';
		}
		
		// Add series if set
		if ( ! empty( $data['series'] ) ) {
			$payload['NazwaSeriiNumeracji'] = $data['series'];
		}

		// Add notes (Uwagi) if provided
		if ( ! empty( $data['notes'] ) ) {
			$payload['Uwagi'] = (string) $data['notes'];
		}
		
		return $payload;
	}

	/**
	 * Get OSS invoice details
	 * 
	 * @param int $document_id Document ID
	 * @return array|WP_Error
	 */
	public function get_oss_invoice_details( $document_id ) {
		$endpoint = 'fakturaoss/' . $document_id . '.json';
		return $this->get( $endpoint, 'faktura' );
	}

	/**
	 * Download OSS invoice PDF
	 * 
	 * @param int $document_id Document ID
	 * @return string|WP_Error PDF content
	 */
	public function download_oss_pdf( $document_id ) {
		$endpoint = 'fakturaoss/' . $document_id . '.pdf';
		return $this->get( $endpoint, 'faktura', true );
	}
}
