<?php

namespace WPMailSMTP\Pro\Emails\Logs\Export;

use WP_Error;
use WPMailSMTP\Uploads;
use WPMailSMTP\Helpers\Helpers;
use WPMailSMTP\Vendor\XLSXWriter;

/**
 * Export file.
 *
 * @since 2.8.0
 */
class File {

	/**
	 * Write data to export file.
	 *
	 * @since 2.8.0
	 *
	 * @param Request $request Export request.
	 *
	 * @return true|WP_Error
	 */
	public function write( $request ) {

		// Include libraries.
		require_once wp_mail_smtp()->plugin_path . '/vendor/autoload.php';

		$type = $request->get_data( 'type' );

		if ( $type === 'xlsx' ) {
			// Write to the .xlsx file.
			return $this->write_xlsx( $request );
		} elseif ( $type === 'eml' ) {
			// Writing to the .eml or .zip file.
			return $this->write_eml( $request );
		} else {
			// Writing to the .csv file.
			return $this->write_csv( $request );
		}
	}

	/**
	 * Write data to .csv file.
	 *
	 * @since 2.8.0
	 *
	 * @param Request $request Export request.
	 *
	 * @return true|WP_Error
	 */
	public function write_csv( $request ) {

		$export_file = $this->get_tmp_filename( $request->get_request_id() );

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

		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fopen
		$f         = fopen( 'php://temp', 'wb+' );
		$separator = Export::get_config( 'export', 'csv_export_separator' );
		$enclosure = '"';

		$data = new TableData( $request );

		if ( $request->get_arg( 'step' ) === 1 ) {
			fputcsv( $f, $data->get_columns(), $separator, $enclosure );
		}

		foreach ( $data->get_row() as $row ) {
			fputcsv( $f, $row, $separator, $enclosure );
		}

		rewind( $f );

		$file_contents = stream_get_contents( $f );

		$result = $this->put_contents( $export_file, $file_contents );

		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fclose
		fclose( $f );

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

		return true;
	}

	/**
	 * Write data to .xlsx file.
	 *
	 * @since 2.8.0
	 *
	 * @param Request $request Export request.
	 *
	 * @return true|WP_Error
	 */
	public function write_xlsx( $request ) {

		$export_file = $this->get_tmp_filename( $request->get_request_id() );

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

		$writer     = new XLSXWriter();
		$sheet_name = 'WPMailSMTP';

		$data = new TableData( $request );

		if ( $request->get_arg( 'step' ) === 1 ) {
			$widths = array_map(
				function ( $header ) {
					return strlen( $header ) + 2;
				},
				$data->get_columns()
			);

			$writer->writeSheetHeader( $sheet_name, array_fill_keys( $data->get_columns(), 'string' ), [ 'widths' => $widths ] );
		}

		foreach ( $data->get_row() as $row ) {
			$writer->writeSheetRow( $sheet_name, $row, [ 'wrap_text' => true ] );
		}

		$writer->writeToFile( $export_file );

		return true;
	}

	/**
	 * Write data to .eml or .zip file.
	 *
	 * @since 2.9.0
	 *
	 * @param Request $request Export request.
	 *
	 * @return true|WP_Error
	 */
	public function write_eml( $request ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh

		$export_file = $this->get_tmp_filename( $request->get_request_id() );

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

		$data = new EMLData( $request );

		$db_args = $request->get_data( 'db_args' );

		if ( ! empty( $db_args['id'] ) ) {
			foreach ( $data->get_content() as $value ) {
				list( , $content ) = $value;
				file_put_contents( $export_file, $content ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_file_put_contents
			}
		} else {
			$zip      = new \ZipArchive();

			/*
			 * Use `\ZipArchive::OVERWRITE` instead of `\ZipArchive::CREATE` for compatibility with PHP 8.1.
			 * In PHP 8.1 with `\ZipArchive::CREATE` mode using empty file as ZipArchive is deprecated.
			 * Since we create file in `File::get_tmp_filename` method, we need to use `\ZipArchive::OVERWRITE` mode.
			 */
			$zip_open = $zip->open( $export_file, \ZipArchive::OVERWRITE );

			if ( $zip_open !== true ) {
				return new WP_Error(
					'zip_creating',
					sprintf( /* translators: %s - Error code. */
						esc_html__( 'An error occurred when creating a zip file. Error code: %s.', 'wp-mail-smtp-pro' ),
						$zip_open
					)
				);
			}

			// Include polyfill if mbstring PHP extension is not enabled.
			if ( ! function_exists( 'mb_substr' ) ) {
				Helpers::include_mbstring_polyfill();
			}

			foreach ( $data->get_content() as $value ) {
				list( $email, $content ) = $value;

				$filename = $email->get_id() . '-' . sanitize_title( mb_substr( $email->get_subject(), 0, 30 ) ) . '.eml';
				$zip->addFromString( $filename, $content );
			}

			$zip->close();
		}

		return true;
	}

	/**
	 * Output the file.
	 *
	 * @since 2.8.0
	 *
	 * @param Request $request Export request.
	 *
	 * @throws \Exception In case of file error.
	 */
	public function output_file( $request ) {

		if ( empty( $request->get_request_id() ) ) {
			throw new \Exception( Export::get_config( 'errors', 'unknown_request' ) );
		}

		$export_file = $this->get_tmp_filename( $request->get_request_id() );

		if ( is_wp_error( $export_file ) ) {
			throw new \Exception( $export_file->get_error_message() );
		}

		clearstatcache( true, $export_file );

		if ( ! is_readable( $export_file ) || is_dir( $export_file ) ) {
			throw new \Exception( Export::get_config( 'errors', 'file_not_readable' ) );
		}

		if ( @filesize( $export_file ) === 0 ) { //phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
			throw new \Exception( Export::get_config( 'errors', 'file_empty' ) );
		}

		$db_args = $request->get_data( 'db_args' );

		if ( ! empty( $db_args['id'] ) ) {
			$file_name = 'wp-mail-smtp-email-log-' . $db_args['id'];
		} else {
			$file_name = 'wp-mail-smtp-email-logs';
		}

		$file_ext = $request->get_data( 'type' );

		if ( $file_ext === 'eml' ) {
			$file_ext = 'zip';

			if ( ! empty( $db_args['id'] ) ) {
				$file_ext = 'eml';
			}
		}

		$file_name .= '-' . current_time( 'Y-m-d-H-i-s' ) . '.' . $file_ext;

		/**
		 * Filters export filename.
		 *
		 * @since 2.8.0
		 *
		 * @param string  $file_name Filename.
		 * @param Request $request   Export request.
		 */
		$file_name = apply_filters( 'wp_mail_smtp_pro_emails_logs_export_file_output_file', $file_name, $request );

		$this->http_headers( sanitize_file_name( $file_name ), $request->get_data( 'type' ) );

		readfile( $export_file ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_readfile

		exit;
	}

	/**
	 * Temporary files directory.
	 *
	 * @since 2.8.0
	 *
	 * @return string|WP_Error
	 */
	public function get_tmpdir() {

		$upload_dir = Uploads::upload_dir();

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

		$upload_path = $upload_dir['path'];

		$export_path = trailingslashit( $upload_path ) . 'export';
		if ( ! file_exists( $export_path ) ) {
			wp_mkdir_p( $export_path );
		}

		// Check if the .htaccess exists in the upload directory, if not - create it.
		Uploads::create_upload_dir_htaccess_file();

		// Check if the index.html exists in the directories, if not - create it.
		Uploads::create_index_html_file( $upload_path );
		Uploads::create_index_html_file( $export_path );

		// Normalize slashes for Windows.
		$export_path = wp_normalize_path( $export_path );

		return $export_path;
	}

	/**
	 * Full pathname to the Temporary file.
	 *
	 * @since 2.8.0
	 *
	 * @param string $request_id Request id.
	 *
	 * @return string|WP_Error
	 */
	public function get_tmp_filename( $request_id ) {

		$export_dir = $this->get_tmpdir();

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

		$export_file = $export_dir . '/' . sanitize_key( $request_id ) . '.tmp';

		touch( $export_file );

		return $export_file;
	}

	/**
	 * Send HTTP headers for file download.
	 *
	 * @since 2.8.0
	 *
	 * @param string $file_name File name.
	 * @param string $type      File type.
	 */
	public function http_headers( $file_name, $type ) {

		nocache_headers();
		header( 'Content-Description: File Transfer' );
		header( 'Content-Type: text/' . $type );
		header( 'Content-Disposition: attachment; filename=' . $file_name );
		header( 'Content-Transfer-Encoding: binary' );
	}

	/**
	 * Put file contents using WP Filesystem.
	 *
	 * @since 3.3.0
	 *
	 * @param string $export_file   Export filename.
	 * @param string $file_contents File contents.
	 *
	 * @return true|WP_Error
	 */
	private function put_contents( $export_file, $file_contents ) {

		global $wp_filesystem;

		if ( ! class_exists( 'WP_Filesystem_Direct' ) ) {
			require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php';
			require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-direct.php';
		}

		if ( ! $wp_filesystem instanceof \WP_Filesystem_Direct ) {
			if ( get_filesystem_method() !== 'direct' ) {
				return new WP_Error(
					'wrong-wp-filesystem-access',
					esc_html__( 'Your WP site is not using the direct file system method, which is needed for this export.', 'wp-mail-smtp-pro' )
				);
			}

			// Instantiate $wp_filesystem global with direct access method.
			WP_Filesystem( request_filesystem_credentials( site_url() ) );
		}

		// Borrowed from `WP_Filesystem_Direct::put_contents()`.
		$fp = @fopen( $export_file, 'ab' ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged,WordPress.WP.AlternativeFunctions.file_system_read_fopen

		if ( ! $fp ) {
			return new WP_Error(
				'export-file-access-error',
				esc_html__( 'Something went wrong while opening the export file for writing.', 'wp-mail-smtp-pro' )
			);
		}

		mbstring_binary_safe_encoding();

		$data_length   = strlen( $file_contents );
		$bytes_written = fwrite( $fp, $file_contents ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fwrite

		reset_mbstring_encoding();
		fclose( $fp ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fclose

		if ( $data_length !== $bytes_written ) {
			return new WP_Error(
				'export-file-write-error',
				esc_html__( 'Something went wrong while writing to the export file.', 'wp-mail-smtp-pro' )
			);
		}

		$wp_filesystem->chmod( $export_file );

		return true;
	}
}
