<?php
/**
 * Audit CSV Generator
 *
 * Builds a structured UTF-8 CSV: report cover + sync summary, then all product rows (streaming).
 *
 * @category   WordPress_Plugin
 * @package    Common
 * @subpackage Services\Auditing
 * @author     miniOrange <info@miniorange.com>
 * @license    MIT/Expat
 * @link       https://plugins.miniorange.com/woocommerce-api-product-sync-with-woocommerce-rest-apis
 */

namespace MOWCPS\Common\Services\Auditing;

use MOWCPS\Common\Constants\Constants;
use MOWCPS\Common\Services\Auditing\Supplier_Audit_Logger;

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

/**
 * Class Audit_CSV_Generator
 *
 * Converts a session JSONL file to a readable multi-section CSV.
 */
class Audit_CSV_Generator {

	/**
	 * Write a full audit CSV from a session JSONL path.
	 *
	 * @param string     $session_path Absolute path to session *.jsonl.
	 * @param string     $output_path  Destination CSV path.
	 * @param array|null $parsed       Optional parse_session_file() result: header, summary, products (per-line arrays).
	 * @return bool True on success.
	 */
	public function generate( $session_path, $output_path, $parsed = null ) {
		if ( ! is_string( $session_path ) || '' === $session_path || ! is_readable( $session_path ) ) {
			return false;
		}
		if ( ! is_string( $output_path ) || '' === $output_path ) {
			return false;
		}

		if ( function_exists( 'set_time_limit' ) ) {
			// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Large exports may need more than default.
			@set_time_limit( 0 );
		}

		if (
			is_array( $parsed )
			&& isset( $parsed['header'] )
			&& is_array( $parsed['header'] )
			&& ! empty( $parsed['header'] )
			&& isset( $parsed['summary'] )
			&& is_array( $parsed['summary'] )
		) {
			$header_row = $parsed['header'];
			$summary    = $parsed['summary'];
		} else {
			$parsed = Supplier_Audit_Logger::parse_session_file( $session_path );
			if (
				! is_array( $parsed )
				|| ! isset( $parsed['header'] )
				|| ! is_array( $parsed['header'] )
				|| empty( $parsed['header'] )
			) {
				return false;
			}
			$header_row = $parsed['header'];
			$summary    = isset( $parsed['summary'] ) && is_array( $parsed['summary'] ) ? $parsed['summary'] : array();
		}

		$parent_dir = dirname( $output_path );
		if ( ! is_string( $parent_dir ) || '' === $parent_dir ) {
			return false;
		}
		if ( ! is_dir( $parent_dir ) ) {
			if ( ! wp_mkdir_p( $parent_dir ) ) {
				return false;
			}
		}
		if ( ! is_dir( $parent_dir ) ) {
			return false;
		}

		$use_product_buffer = isset( $parsed['products'] ) && is_array( $parsed['products'] );

		$out = null;
		$in  = null;
		$ok  = true;

		try {
			// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen -- Stream CSV output.
			$out = fopen( $output_path, 'wb' );
			if ( false === $out ) {
				return false;
			}

			$ok = $ok && $this->fwrite_full( $out, "\xEF\xBB\xBF" );
			$ok = $ok && $this->write_cover_and_summary_sections( $out, $header_row, $summary );
			$ok = $ok && $this->fwrite_full( $out, "\r\n" );
			$ok = $ok && $this->fputcsv_ok(
				$out,
				array(
					__( 'SECTION', Constants::PLUGIN_TEXT_DOMAIN ),
					__( 'Product-level detail (one row per product line)', Constants::PLUGIN_TEXT_DOMAIN ),
				)
			);
			$ok = $ok && $this->fwrite_full( $out, "\r\n" );

			$product_columns = array(
				__( '#', Constants::PLUGIN_TEXT_DOMAIN ),
				__( 'Product type', Constants::PLUGIN_TEXT_DOMAIN ),
				__( 'Connection', Constants::PLUGIN_TEXT_DOMAIN ),
				__( 'Sync role', Constants::PLUGIN_TEXT_DOMAIN ),
				__( 'Parent WC ID', Constants::PLUGIN_TEXT_DOMAIN ),
				__( 'Parent SKU', Constants::PLUGIN_TEXT_DOMAIN ),
				__( 'Operation', Constants::PLUGIN_TEXT_DOMAIN ),
				__( 'Status', Constants::PLUGIN_TEXT_DOMAIN ),
				__( 'Success', Constants::PLUGIN_TEXT_DOMAIN ),
				__( 'Reason / notes', Constants::PLUGIN_TEXT_DOMAIN ),
				__( 'WC product ID', Constants::PLUGIN_TEXT_DOMAIN ),
				__( 'SKU', Constants::PLUGIN_TEXT_DOMAIN ),
				__( 'Unique identifier', Constants::PLUGIN_TEXT_DOMAIN ),
				__( 'Product status', Constants::PLUGIN_TEXT_DOMAIN ),
				__( 'Batch #', Constants::PLUGIN_TEXT_DOMAIN ),
				__( 'Page #', Constants::PLUGIN_TEXT_DOMAIN ),
				__( 'All fields (JSON)', Constants::PLUGIN_TEXT_DOMAIN ),
			);
			$ok = $ok && $this->fputcsv_ok( $out, $product_columns );

			$line_num = 0;

			if ( $use_product_buffer ) {
				foreach ( $parsed['products'] as $row ) {
					if ( ! is_array( $row ) || empty( $row['type'] ) || 'product' !== (string) $row['type'] ) {
						continue;
					}
					$ok = $ok && $this->write_product_csv_row( $out, $row, $line_num );
					if ( ! $ok ) {
						break;
					}
				}
			} else {
				// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen -- Fallback when product buffer unavailable.
				$in = fopen( $session_path, 'rb' );
				if ( false === $in ) {
					$ok = false;
				} else {
					// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fgets -- Line stream.
					while ( $ok && ( $line = fgets( $in ) ) !== false ) {
						$line = trim( $line );
						if ( '' === $line ) {
							continue;
						}
						$row = json_decode( $line, true );
						if ( JSON_ERROR_NONE !== json_last_error() || ! is_array( $row ) || empty( $row['type'] ) || 'product' !== (string) $row['type'] ) {
							continue;
						}
						$ok = $ok && $this->write_product_csv_row( $out, $row, $line_num );
					}
				}
			}
		} finally {
			if ( is_resource( $in ) ) {
				fclose( $in );
			}
			if ( is_resource( $out ) ) {
				fclose( $out );
			}
		}

		return $ok && file_exists( $output_path ) && filesize( $output_path ) > 0;
	}

	/**
	 * Write full string to stream or return false on short write.
	 *
	 * @param resource $out Stream.
	 * @param string   $buf Payload.
	 * @return bool
	 */
	private function fwrite_full( $out, $buf ) {
		if ( '' === $buf ) {
			return true;
		}
		$len = strlen( $buf );
		$w   = fwrite( $out, $buf );
		return false !== $w && $w === $len;
	}

	/**
	 * @param resource $out Stream.
	 * @param array    $fields CSV row.
	 * @return bool
	 */
	private function fputcsv_ok( $out, array $fields ) {
		return false !== fputcsv( $out, $fields );
	}

	/**
	 * Prefix cells that could be interpreted as spreadsheet formulas (=, +, -, @).
	 *
	 * @param string $value Raw cell value.
	 * @return string
	 */
	private function escape_csv_formula_injection( $value ) {
		$s = (string) $value;
		if ( '' === $s ) {
			return $s;
		}
		$c = $s[0];
		if ( in_array( $c, array( '=', '+', '-', '@', "\t", "\r" ), true ) ) {
			return "'" . $s;
		}
		return $s;
	}

	/**
	 * Append one product JSON line as a CSV row.
	 *
	 * @param resource $out      Output stream.
	 * @param array    $row      Decoded product line.
	 * @param int      $line_num Running row index (incremented).
	 * @return bool
	 */
	private function write_product_csv_row( $out, array $row, &$line_num ) {
		++$line_num;
		$fields = isset( $row['fields'] ) && is_array( $row['fields'] ) ? $row['fields'] : array();
		$sku    = isset( $fields['sku'] ) ? (string) $fields['sku'] : '';
		$uid    = isset( $fields['unique_identifier'] ) ? (string) $fields['unique_identifier'] : '';
		$pst    = isset( $fields['product_status'] ) ? (string) $fields['product_status'] : '';
		$bn     = isset( $fields['batch_number'] ) ? (string) $fields['batch_number'] : '';
		$pn     = isset( $fields['page_number'] ) ? (string) $fields['page_number'] : '';

		$fields_json = wp_json_encode( $fields, JSON_INVALID_UTF8_SUBSTITUTE );
		if ( false === $fields_json ) {
			$fields_json = __( '(JSON encoding failed)', Constants::PLUGIN_TEXT_DOMAIN );
		}

		$is_success = Supplier_Audit_Logger::audit_product_row_is_success( $row );

		return $this->fputcsv_ok(
			$out,
			array(
				(string) $line_num,
				isset( $row['product_type'] ) ? (string) $row['product_type'] : '',
				isset( $row['connection_name'] ) ? (string) $row['connection_name'] : '',
				isset( $row['sync_role'] ) ? (string) $row['sync_role'] : '',
				isset( $row['parent_wc_id'] ) ? (string) (int) $row['parent_wc_id'] : '',
				isset( $row['parent_sku'] ) ? (string) $row['parent_sku'] : '',
				isset( $row['operation'] ) ? (string) $row['operation'] : '',
				isset( $row['status'] ) ? (string) $row['status'] : '',
				$is_success
					? __( 'Yes', Constants::PLUGIN_TEXT_DOMAIN )
					: __( 'No', Constants::PLUGIN_TEXT_DOMAIN ),
				isset( $row['reason'] ) ? (string) $row['reason'] : '',
				isset( $row['wc_product_id'] ) ? (string) (int) $row['wc_product_id'] : '',
				$sku,
				$uid,
				$pst,
				$bn,
				$pn,
				$fields_json,
			)
		);
	}

	/**
	 * Cover block + formatted summary metrics (two-column layout for Excel).
	 *
	 * @param resource $out     Writable handle.
	 * @param array    $header  Header JSON line.
	 * @param array    $summary Summary JSON line.
	 * @return bool
	 */
	private function write_cover_and_summary_sections( $out, array $header, array $summary ) {
		$col_label = __( 'Label', Constants::PLUGIN_TEXT_DOMAIN );
		$col_value = __( 'Value', Constants::PLUGIN_TEXT_DOMAIN );

		$ok = true;
		$ok = $ok && $this->fputcsv_ok( $out, array( $col_label, $col_value ) );
		$ok = $ok && $this->fputcsv_ok(
			$out,
			array(
				__( 'Report title', Constants::PLUGIN_TEXT_DOMAIN ),
				__( 'WooCommerce Product Sync — Detailed audit report', Constants::PLUGIN_TEXT_DOMAIN ),
			)
		);
		$ok = $ok && $this->fputcsv_ok( $out, array( '', '' ) );

		$ok = $ok && $this->fputcsv_ok(
			$out,
			array(
				'--- ' . __( 'Supplier & session', Constants::PLUGIN_TEXT_DOMAIN ) . ' ---',
				'',
			)
		);

		if ( ! empty( $header ) ) {
			$ok = $ok && $this->put_pair( $out, __( 'Supplier', Constants::PLUGIN_TEXT_DOMAIN ), isset( $header['supplier_name'] ) ? (string) $header['supplier_name'] : '' );
			$ok = $ok && $this->put_pair( $out, __( 'Supplier ID', Constants::PLUGIN_TEXT_DOMAIN ), isset( $header['supplier_id'] ) ? (string) (int) $header['supplier_id'] : '' );
			$ok = $ok && $this->put_pair( $out, __( 'Site URL', Constants::PLUGIN_TEXT_DOMAIN ), isset( $header['site_url'] ) ? (string) $header['site_url'] : '' );
			$ok = $ok && $this->put_pair( $out, __( 'Plugin version', Constants::PLUGIN_TEXT_DOMAIN ), isset( $header['plugin_version'] ) ? (string) $header['plugin_version'] : '' );
			$ok = $ok && $this->put_pair( $out, __( 'Sync started (UTC)', Constants::PLUGIN_TEXT_DOMAIN ), isset( $header['start_time'] ) ? (string) $header['start_time'] : '' );
		}

		if ( ! empty( $summary ) ) {
			if ( isset( $summary['end_time'] ) ) {
				$ok = $ok && $this->put_pair( $out, __( 'Sync ended (UTC)', Constants::PLUGIN_TEXT_DOMAIN ), (string) $summary['end_time'] );
			}
			if ( isset( $summary['duration_human'] ) && '' !== (string) $summary['duration_human'] ) {
				$ok = $ok && $this->put_pair( $out, __( 'Duration', Constants::PLUGIN_TEXT_DOMAIN ), (string) $summary['duration_human'] );
			} elseif ( isset( $summary['duration_seconds'] ) ) {
				$ok = $ok && $this->put_pair(
					$out,
					__( 'Duration (seconds)', Constants::PLUGIN_TEXT_DOMAIN ),
					(string) (int) $summary['duration_seconds']
				);
			}
		}

		$ok = $ok && $this->fputcsv_ok( $out, array( '', '' ) );
		$ok = $ok && $this->fputcsv_ok(
			$out,
			array(
				'--- ' . __( 'Sync summary (counts)', Constants::PLUGIN_TEXT_DOMAIN ) . ' ---',
				'',
			)
		);
		$ok = $ok && $this->fputcsv_ok(
			$out,
			array(
				__( 'Metric', Constants::PLUGIN_TEXT_DOMAIN ),
				__( 'Count', Constants::PLUGIN_TEXT_DOMAIN ),
			)
		);

		if ( ! empty( $summary ) ) {
			$map = array(
				'total_parsed'  => __( 'TOTAL PARSED', Constants::PLUGIN_TEXT_DOMAIN ),
				'total_success' => __( 'SUCCESS', Constants::PLUGIN_TEXT_DOMAIN ),
				'total_failed'  => __( 'FAILED', Constants::PLUGIN_TEXT_DOMAIN ),
				'total_added'   => __( 'ADDED', Constants::PLUGIN_TEXT_DOMAIN ),
				'total_updated' => __( 'UPDATED', Constants::PLUGIN_TEXT_DOMAIN ),
				'total_skipped' => __( 'SKIPPED', Constants::PLUGIN_TEXT_DOMAIN ),
			);
			foreach ( $map as $key => $label ) {
				$val = isset( $summary[ $key ] ) ? (int) $summary[ $key ] : 0;
				$ok  = $ok && $this->fputcsv_ok( $out, array( $label, (string) $val ) );
			}
		}

		$ok = $ok && $this->write_connection_context_rows( $out, $summary );

		return $ok;
	}

	/**
	 * Output one CSV row as label/value pair.
	 *
	 * @param resource $out Writable handle.
	 * @param string   $label Label.
	 * @param string   $value Value.
	 * @return bool
	 */
	private function put_pair( $out, $label, $value ) {
		return $this->fputcsv_ok( $out, array( $label, $value ) );
	}

	/**
	 * Optional per-connection snapshot blocks from summary.
	 *
	 * @param resource $out     Writable handle.
	 * @param array    $summary Summary row.
	 * @return bool
	 */
	private function write_connection_context_rows( $out, array $summary ) {
		$snaps = isset( $summary['audit_connection_snapshots'] ) && is_array( $summary['audit_connection_snapshots'] )
			? $summary['audit_connection_snapshots']
			: array();

		if ( empty( $snaps ) ) {
			$from_flat = $this->snapshot_from_summary_flat( $summary );
			if ( null !== $from_flat ) {
				return $this->write_connection_fetch_details_section( $out, array( $from_flat ) );
			}
			return true;
		}

		return $this->write_connection_fetch_details_section( $out, $snaps );
	}

	/**
	 * Build a normalized snapshot from flat audit_* keys on the summary row (when snapshot list is empty).
	 *
	 * @param array $summary Summary row.
	 * @return array|null Same shape as normalized audit_context snapshot, or null if no connection info.
	 */
	private function snapshot_from_summary_flat( array $summary ) {
		$id   = isset( $summary['audit_connection_id'] ) ? (int) $summary['audit_connection_id'] : 0;
		$name = isset( $summary['audit_connection_name'] ) ? (string) $summary['audit_connection_name'] : '';
		if ( $id <= 0 && '' === $name ) {
			return null;
		}

		return array(
			'audit_connection_id'         => $id,
			'audit_connection_name'       => $name,
			'audit_connection_type_label' => isset( $summary['audit_connection_type_label'] ) ? (string) $summary['audit_connection_type_label'] : '',
			'audit_batch_setting_enabled' => ! empty( $summary['audit_batch_setting_enabled'] ),
			'audit_batch_size'            => isset( $summary['audit_batch_size'] ) ? (int) $summary['audit_batch_size'] : 0,
			'audit_batch_runtime_active'  => ! empty( $summary['audit_batch_runtime_active'] ),
			'audit_page_setting_enabled'  => ! empty( $summary['audit_page_setting_enabled'] ),
			'audit_pagination_mode'       => isset( $summary['audit_pagination_mode'] ) ? (string) $summary['audit_pagination_mode'] : '',
			'audit_page_runtime_active'   => ! empty( $summary['audit_page_runtime_active'] ),
		);
	}

	/**
	 * Connection fetch details block (PDF-aligned labels).
	 *
	 * @param resource $out   Writable handle.
	 * @param array    $snaps List of normalized snapshot arrays.
	 * @return bool
	 */
	private function write_connection_fetch_details_section( $out, array $snaps ) {
		$ok = true;
		$ok = $ok && $this->fputcsv_ok( $out, array( '', '' ) );
		$ok = $ok && $this->fputcsv_ok(
			$out,
			array(
				'--- ' . __( 'Connection fetch details', Constants::PLUGIN_TEXT_DOMAIN ) . ' ---',
				'',
			)
		);
		$ok = $ok && $this->fputcsv_ok(
			$out,
			array(
				__( 'Setting', Constants::PLUGIN_TEXT_DOMAIN ),
				__( 'Value', Constants::PLUGIN_TEXT_DOMAIN ),
			)
		);

		foreach ( $snaps as $snap ) {
			if ( ! is_array( $snap ) ) {
				continue;
			}
			$name = isset( $snap['audit_connection_name'] ) ? (string) $snap['audit_connection_name'] : '';
			if ( '' === $name ) {
				$name = __( '(Connection)', Constants::PLUGIN_TEXT_DOMAIN );
			}
			$ok = $ok && $this->fputcsv_ok( $out, array( '| ' . $name, '' ) );
			$ok = $ok && $this->put_pair( $out, __( 'Connection ID', Constants::PLUGIN_TEXT_DOMAIN ), isset( $snap['audit_connection_id'] ) ? (string) (int) $snap['audit_connection_id'] : '' );
			$ok = $ok && $this->put_pair( $out, __( 'Connection type', Constants::PLUGIN_TEXT_DOMAIN ), isset( $snap['audit_connection_type_label'] ) ? (string) $snap['audit_connection_type_label'] : '' );
			$ok = $ok && $this->put_pair(
				$out,
				__( 'Pagination (setting)', Constants::PLUGIN_TEXT_DOMAIN ),
				$this->format_pagination_setting_for_report( $snap )
			);
			$ok = $ok && $this->put_pair(
				$out,
				__( 'Pagination (this run)', Constants::PLUGIN_TEXT_DOMAIN ),
				$this->format_pagination_runtime_for_report( $snap )
			);
			$ok = $ok && $this->put_pair(
				$out,
				__( 'Batch processing (setting)', Constants::PLUGIN_TEXT_DOMAIN ),
				$this->format_batch_setting_for_report( $snap )
			);
			$ok = $ok && $this->put_pair(
				$out,
				__( 'Batch (runtime)', Constants::PLUGIN_TEXT_DOMAIN ),
				$this->format_batch_runtime_for_report( $snap )
			);
			$ok = $ok && $this->fputcsv_ok( $out, array( '', '' ) );
		}

		return $ok;
	}

	/**
	 * Pagination setting line for the report (Disabled / mode / Enabled).
	 *
	 * @param array $snap Normalized connection snapshot.
	 * @return string
	 */
	private function format_pagination_setting_for_report( array $snap ) {
		if ( empty( $snap['audit_page_setting_enabled'] ) ) {
			return __( 'Disabled', Constants::PLUGIN_TEXT_DOMAIN );
		}
		$mode = isset( $snap['audit_pagination_mode'] ) ? trim( (string) $snap['audit_pagination_mode'] ) : '';
		if ( '' !== $mode ) {
			return $this->escape_csv_formula_injection( $mode );
		}
		return __( 'Enabled', Constants::PLUGIN_TEXT_DOMAIN );
	}

	/**
	 * Pagination runtime state (Active / Inactive).
	 *
	 * @param array $snap Normalized connection snapshot.
	 * @return string
	 */
	private function format_pagination_runtime_for_report( array $snap ) {
		return ! empty( $snap['audit_page_runtime_active'] )
			? __( 'Active', Constants::PLUGIN_TEXT_DOMAIN )
			: __( 'Inactive', Constants::PLUGIN_TEXT_DOMAIN );
	}

	/**
	 * Batch setting line (Disabled or Enabled — size N).
	 *
	 * @param array $snap Normalized connection snapshot.
	 * @return string
	 */
	private function format_batch_setting_for_report( array $snap ) {
		if ( empty( $snap['audit_batch_setting_enabled'] ) ) {
			return __( 'Disabled', Constants::PLUGIN_TEXT_DOMAIN );
		}
		$sz = isset( $snap['audit_batch_size'] ) ? (int) $snap['audit_batch_size'] : 0;
		return sprintf(
			/* translators: %d: batch size. */
			__( 'Enabled — size %d', Constants::PLUGIN_TEXT_DOMAIN ),
			$sz
		);
	}

	/**
	 * Batch runtime state (Active / Inactive).
	 *
	 * @param array $snap Normalized connection snapshot.
	 * @return string
	 */
	private function format_batch_runtime_for_report( array $snap ) {
		return ! empty( $snap['audit_batch_runtime_active'] )
			? __( 'Active', Constants::PLUGIN_TEXT_DOMAIN )
			: __( 'Inactive', Constants::PLUGIN_TEXT_DOMAIN );
	}
}
