<?php

namespace WP_Table_Builder\Inc\Admin;

use DOMDocument;
use DOMXPath;
use WP_Table_Builder\Inc\Admin\Managers\Settings_Manager;
use WP_Table_Builder\Inc\Common\Traits\Ajax_Response;
use WP_Table_Builder as NS;
use ZipArchive;
use function add_action;
use function current_user_can;
use function get_bloginfo;
use function get_plugin_data;
use function get_post;
use function get_post_meta;
use function get_the_title;
use function mb_convert_encoding;
use function request_filesystem_credentials;
use function wp_create_nonce;
use function wp_kses_stripslashes;
use function wp_reset_postdata;

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

/**
 * Class Export
 *
 * Class for maintaining export functionality
 *
 * @package WP_Table_Builder\Inc\Admin
 */
class Export
{

	// ajax response trait
	use Ajax_Response;

	/**
	 * singleton instance of the class
	 *
	 * @var null
	 */
	protected static $instance;

	/**
	 * table fetch ajax constant
	 */
	const EXPORT_FETCH_TABLES = 'wptb_table_export_fetch_tables';

	/**
	 * table export ajax constant
	 */
	const EXPORT_TABLES = 'wptb_table_export_main_export';

	/**
	 * nonce types
	 */
	const NONCE_TYPES = ['fetch' => self::EXPORT_FETCH_TABLES, 'export' => self::EXPORT_TABLES];

	/**
	 * supported export types
	 *
	 * @var string[]
	 */
	protected $supported_export_types = ['XML' => 'xml', 'CSV' => 'csv'];

	/**
	 * plugin text domain
	 * @var string
	 */
	private $text_domain = NS\PLUGIN_TEXT_DOMAIN;

	/**
	 * Export constructor.
	 *
	 * Private constructor for singleton nature of the class
	 *
	 */
	private function __construct()
	{
		add_action('wp_ajax_' . self::EXPORT_FETCH_TABLES, [$this, 'wptb_export_fetch_tables']);
		add_action('wp_ajax_' . self::EXPORT_TABLES, [$this, 'wptb_export_main_export']);
	}

	/**
	 * Get instanced singleton version of the export class
	 *
	 * @return Export instance of class
	 */
	public static function get_instance()
	{
		if (!isset(static::$instance)) {
			static::$instance = new Export();
		}

		return static::$instance;
	}

	/**
	 * Fetch user tables
	 */
	public function wptb_export_fetch_tables()
	{
		if (current_user_can(Settings_Manager::ALLOWED_ROLE_META_CAP) && check_ajax_referer(self::EXPORT_FETCH_TABLES, 'nonce', false)) {
			$this->set_message(esc_html__('success', 'wp-table-builder'));

			$tables = $this->get_wptb_tables('ID', 'post_title', 'post_date');

			$this->append_response_data($tables, 'userTables');

		} else {
			$this->set_error(esc_html__('you are not authorized to access this ajax endpoint', 'wp-table-builder'));
		}

		$this->send_json();
	}

	/**
	 * Convert specific date fields in a post table to ISO8601 format
	 *
	 * @param array $post_object WordPress post table array
	 */
	private function date_to_iso8601(&$post_object)
	{
		$date_fields = ['post_date', 'post_modified', 'post_date_gmt'];

		foreach ($date_fields as $field) {
			if (isset($post_object[$field])) {
				$post_object[$field] = date('c', strtotime($post_object[$field]));
			}
		}
	}

	/**
	 * Export tables to frontend with a ajax response
	 */
	public function wptb_export_main_export()
	{
		if (current_user_can(Settings_Manager::ALLOWED_ROLE_META_CAP) && check_ajax_referer(self::EXPORT_TABLES, 'nonce', false) && isset($_POST['ids']) && isset($_POST['export_type'])) {
			$export_type = $_POST['export_type'];

			if (!in_array($export_type, array_keys($this->supported_export_types))) {
				$this->set_error(__('invalid export type', 'wp-table-builder'));

				return;
			}

			$table_ids = json_decode(wp_kses_stripslashes($_POST['ids']));

			// table id check to determine whether a zip or a single file will be sent
			if (sizeof($table_ids) > 1) {
				$this->zip_archive_creation($table_ids, $export_type);
			} else {
				$this->single_file_serve($table_ids[0], $export_type);
			}

			// exit ajax output
			die();
		} else {
			$this->set_error(esc_html__('you are not authorized to access this ajax endpoint', 'wp-table-builder'));
		}

		$this->send_json();
	}

	/**
	 * Create and send a single file
	 *
	 * @param int $table_id table id
	 * @param string $export_type file type
	 */
	protected function single_file_serve($table_id, $export_type)
	{
		$file_extension = $this->supported_export_types[$export_type];

		$meta_value = call_user_func([
			$this,
			"prepare_{$file_extension}_table"
		], $table_id);

		$filename = $this->prepare_file_name($file_extension, $table_id);

		$this->shared_headers($filename);

		echo $meta_value;
	}

	/**
	 * Create and send a zip archive with requested tables
	 *
	 * @param int[] $table_ids an array of table ids
	 * @param string $export_type file type for zipped files inside archive
	 */
	protected function zip_archive_creation($table_ids, $export_type)
	{

		// @deprecated
		// get WordPress filesystem credentials
		// using WordPress related filesystem methods to minimize the bugs I/O operations can cause on various server/hosting setups
//		$creds = request_filesystem_credentials( site_url() . '/wp-admin/', '', false, false, null );
//		if ( ! WP_Filesystem( $creds ) ) {
//			$this->set_error( esc_html__( 'you do not have write access to filesystem', 'wp-table-builder' ) );
//
//			return;
//		}

		WP_Filesystem(true);

		global $wp_filesystem;


		$upload_dir = NS\WP_TABLE_BUILDER_DIR . 'uploads';

		// create upload directory if not available
		if (!$wp_filesystem->is_dir($upload_dir)) {
			$dir_create_status = $wp_filesystem->mkdir($upload_dir);
			if (!$dir_create_status) {
				$this->set_error(esc_html__('failed in creating temp directory for export zip file', 'wp-table-builder'));

				return;
			}
		}

		$temp_file_name = tempnam($upload_dir, 'zip');
		if (!class_exists('ZipArchive')) {
			$this->set_error(esc_html__('your server do not support zip archives', 'wp-table-builder'));

			return;
		}

		// zip archive creation
		// creating the archive in memory may cause performance issues at some server/hosting setups, creating/deleting as a temp file seems like the best option
		$zip = new ZipArchive();
		$zip->open($temp_file_name, ZipArchive::OVERWRITE);

		$file_extension = $this->supported_export_types[$export_type];
		foreach ((array) $table_ids as $id) {
			// calling this class's related function according to requested file type defined in the POST request
			$meta_value = call_user_func([
				$this,
				"prepare_{$file_extension}_table"
			], $id);
			$zip->addFromString($this->prepare_file_name($file_extension, $id), $meta_value);
		}

		$zip->close();

		$zip_content = $wp_filesystem->get_contents($temp_file_name);

		// remove temp file
		$wp_filesystem->delete($temp_file_name);

		$file_name = $this->prepare_file_name('zip');

		// this header will be used in front-end to differentiate to parse the data as a glob or json
		$this->shared_headers($file_name);

		echo $zip_content;
	}

	/**
	 * Shared html headers for octet-stream ajax response
	 *
	 * @param string $file_name filename to be added to the content-disposition header
	 */
	protected function shared_headers($file_name)
	{
		header('Content-Type: application/octet-stream');
		header("Content-Disposition: attachment; filename=\"{$file_name}\"");
	}

	/**
	 * Prepare filename with its extension
	 *
	 * @param string $extension file extension
	 *
	 * @param int $id post id to get title of the post, if a negative integer is supplied, a unix timestamp value will be used
	 *
	 * @return string filename
	 */
	public function prepare_file_name($extension, $id = -1)
	{
		$file_base = '';
		if ($id < 0) {
			$file_base = time();
		} else {
			$title = get_the_title($id);
			$file_base = $title === "" ? "Table{$id}" : $title;
		}

		return "{$file_base}.{$extension}";
	}

	/**
	 * Prepare individual table data as xml format.
	 *
	 * @param int $id post id
	 *
	 * @return string table content
	 */
	private function prepare_xml_table($id)
	{
		$content = get_post_meta($id, '_wptb_content_', true);

		$content_to_return = '';

		$dom_handler = new DOMDocument();
		$status = @$dom_handler->loadHTML(mb_convert_encoding($content, 'HTML-ENTITIES', get_bloginfo('charset')), LIBXML_NOERROR | LIBXML_NOWARNING | LIBXML_HTML_NODEFDTD | LIBXML_HTML_NOIMPLIED);

		if ($status) {
			$table = $dom_handler->documentElement;
			if ($table->tagName === 'table') {
				$table->setAttribute('data-wptb-table-title', get_post($id)->post_title);
				$table->setAttribute('data-wptb-export-version', get_plugin_data(NS\PLUGIN__FILE__)['Version']);
				$content_to_return = $dom_handler->saveHTML();
			}
		}

		return $content_to_return;
	}

	/**
	 * Prepare individual table data as csv format
	 *
	 * @param int $id post id
	 *
	 * @return mixed table data
	 */
	private function prepare_csv_table($id)
	{
		$table_html_content = get_post_meta($id, '_wptb_content_', true);

		if (function_exists('mb_convert_encoding')) {
			$charset = get_bloginfo('charset');

			$table_html_content = mb_convert_encoding($table_html_content, 'HTML-ENTITIES', $charset);
		}

		$dom_table = new DOMDocument('1.0', 'UTF-8');
		@$dom_table->loadHTML($table_html_content);

		$table_body = $dom_table->getElementsByTagName('tbody')[0];
		$top_level_rows = $table_body->getElementsByTagName('tr');

		$row_text_content = [];

		for ($i = 0; $i < sizeof($top_level_rows); $i++) {
			$data_nodes = $top_level_rows[$i]->getElementsByTagName('td');

			foreach ($data_nodes as $node) {
				$row_text_content[$i][] = $node->nodeValue;
			}
		}

		ob_start();

		$file = fopen('php://output', 'w');

		try {
			foreach ($row_text_content as $row) {
				if (fputcsv($file, $row) === false) {
					throw new \Exception("Error generating CSV data.");
				}
			}
		} finally {
			fclose($file);
		}
		$csv = ob_get_clean();

		if ($csv === false) {
			throw new \Exception("Error capturing CSV output.");
		}

		return $csv;
	}

	/**
	 * Get tables from database
	 *
	 * @param array $fields an array of post fields to be returned
	 *
	 * @return array post array
	 */
	private function get_wptb_tables(...$fields)
	{
		global $wpdb;

		$parsed_fields = implode(',', $fields);

		$query = $wpdb->prepare("SELECT {$parsed_fields} FROM {$wpdb->posts} WHERE post_type = %s", 'wptb-tables');

		$tables = $wpdb->get_results($query, ARRAY_A);

		array_walk($tables, [$this, 'date_to_iso8601']);

		return $tables;
	}

	/**
	 * Generate nonce for the ajax endpoint
	 *
	 * @param string $type nonce type, use this classes's available nonce enum property to avoid errors for invalid type arguments
	 *
	 * @return string nonce
	 */
	public function generate_nonce($type)
	{
		$nonce_type = self::NONCE_TYPES[$type];

		return wp_create_nonce($nonce_type);
	}
}
