<?php
namespace SabaiApps\Directories\Component\CSV\SystemTool;

use SabaiApps\Directories\Component\System\Tool\AbstractTool;
use SabaiApps\Directories\Component\Form\Form;
use SabaiApps\Directories\Context;
use SabaiApps\Directories\Exception;
use SabaiApps\Directories\Component\Field\IField;
use SabaiApps\Directories\Component\Entity\Model\Bundle;
use SabaiApps\Directories\Component\Field\Type\IQueryable;

class ExportSystemTool extends AbstractTool {

	protected function _systemToolInfo() {
		return array(
			'label'       => _x( 'Export content', 'system tool name', 'directories-pro' ),
			'description' => '',
			'weight'      => 60,
			'btn_label'   => _x( 'Export', 'directories-pro' ),
			'steps'       => array(
				'select_fields',
				'exporter_settings',
			),
			'hidden'      => true,
		);
	}

	protected function _getBundle( Context $context ) {
		return isset( $context->bundle ) && ( $bundle = $this->_application->Entity_Bundle( $context->bundle ) ) ? $bundle : false;
	}

	public function systemToolSettingsForm( $step, array $values, array $parents = array(), ?Context $context = null ) {
		if ( ! $bundle = $this->_getBundle( $context ) ) {
			return false;
		}

		switch ( $step ) {
			case 'select_fields':
				$bundle        = $this->_getBundle( $context );
				$fields        = $this->_application->Entity_Field( $bundle->name );
				$field_options = $custom_field_options = array();
				foreach ( $fields as $field_name => $field ) {
					if ( $field->isCustomField() ) {
						$_options =& $custom_field_options;
					} else {
						$_options =& $field_options;
					}
					$this->_loadFieldOptions( $field, $_options, $bundle );
				}
				ksort( $field_options );
				if ( ! empty( $custom_field_options ) ) {
					ksort( $custom_field_options );
					$field_options += $custom_field_options;
				}

				// Disable required fields
				$field_options_disabled = array();
				foreach ( $this->_getAllRequiredFields( $bundle ) as $field_name ) {
					$field_options_disabled[] = $this->_getFieldOptionValue( $field_name );
				}

				return array(
					'#header' => array(
						array(
							'level'   => 'info',
							'message' => __( 'Select the fields to export and configure CSV column headers.', 'directories-pro' ),
						),
					),
					'fields'  => array(
						'#type'             => 'tableselect',
						'#header'           => array(
							'field'         => __( 'Field name', 'directories-pro' ),
							'column_header' => __( 'Column header', 'directories-pro' ),
						),
						'#multiple'         => true,
						'#js_select'        => true,
						'#options'          => $field_options,
						'#options_disabled' => $field_options_disabled,
						'#default_value'    => array_keys( $field_options ),
						'#element_validate' => array( array( array( $this, '_validateSelectFields' ), array( $fields, $bundle ) ) ),
					),
				);

			case 'exporter_settings':
				$form = array(
					'#header'  => array(),
					'settings' => array(),
				);

				$selected_fields         = (array) ( $values['select_fields']['fields'] ?? array() );
				$exporters_by_field_type = $this->_application->CSV_Exporters( true );
				$bundle                  = $this->_getBundle( $context );
				$fields                  = $this->_application->Entity_Field( $bundle->name );
				$parents[]               = 'settings';
				foreach ( $selected_fields as $selected_field ) {
					if ( ! $_selected_field = explode( '__', $selected_field ) ) {
						continue;
					}

					$field_name = array_shift( $_selected_field );
					$column     = implode( '__', $_selected_field );

					if ( ! isset( $fields[ $field_name ] ) ) {
						continue;
					}
					$field = $fields[ $field_name ];

					if ( ! isset( $exporters_by_field_type[ $field->getFieldType() ] ) ) {
						continue;
					}
					$exporter_name = $exporters_by_field_type[ $field->getFieldType() ];
					if ( ! $exporter = $this->_application->CSV_Exporters_impl( $exporter_name, true ) ) {
						continue;
					}

					$info       = $exporter->csvExporterInfo();
					$columns    = $exporter->csvExporterColumns( $field );
					$_parents   = $parents;
					$_parents[] = $field_name;
					if ( strlen( $column ) ) {
						$_parents[] = $column;
					}
					if ( $column_settings_form = $exporter->csvExporterSettingsForm( $field, (array) ( $info['default_settings'] ?? array() ), $column, '"', $_parents ) ) {
						foreach ( array_keys( $column_settings_form ) as $key ) {
							if ( strpos( $key, '#' ) !== 0 ) {
								$column_settings_form[ $key ]['#horizontal'] = true;
							}
						}
						if ( strlen( $column ) ) {
							$form['settings'][ $field_name ][ $column ]                 = $column_settings_form;
							$form['settings'][ $field_name ][ $column ]['#title']       = $columns[ $column ];
							$form['settings'][ $field_name ][ $column ]['#collapsible'] = false;
						} else {
							$form['settings'][ $field_name ] = $column_settings_form;
						}
						$form['settings'][ $field_name ]['#collapsible']     = true;
						$form['settings'][ $field_name ]['#title_no_escape'] = true;
						if ( ! isset( $form['settings'][ $field_name ]['#title'] ) ) {
							$form['settings'][ $field_name ]['#title'] = $this->_getFieldLabel( $field );
						}
					}
				}
				if ( empty( $form['settings'] ) ) {
					return array();
				}

				$form['settings']['#tree'] = true;
				$form['#header'][]         = array(
					'level'   => 'info',
					'message' => __( 'Please configure additional options for each field.', 'directories-pro' ),
				);

				return $form;

			case 'run':
				$bundle = $this->_getBundle( $context );
				return array(
					'filename'      => array(
						'#title'         => __( 'File name', 'directories-pro' ),
						'#type'          => 'textfield',
						'#field_suffix'  => '.csv',
						'#default_value' => $bundle->name . '-' . date( 'Ymd', time() ),
						'#regex'         => '/^[a-zA-Z0-9-_]+$/',
						'#required'      => true,
						'#horizontal'    => true,
					),
					'query'         => array(
						'#title'            => __( 'Query by field', 'directories-pro' ),
						'#tree'             => true,
						'#element_validate' => array(
							function ( Form $form, &$value, $element ) {
								if ( empty( $value['fields'] ) ) {
									return;
								}

								$queries = array();
								foreach ( array_filter( $value['fields'] ) as $query ) {
									if ( ! strlen( $query['field'] )
										|| ! strlen( trim( $query['query'] ) )
									) {
										continue;
									}

									$queries[ $query['field'] ] = $query['query'];
								}
								$value['fields'] = $queries;
							},
						),
						'fields'            => array(
							array(
								'#type'           => 'field_query',
								'#fields'         => $this->_application->Entity_Field( $bundle ),
								'#default_value'  => null,
								'#query_in_admin' => true,
							),
						),
						'_add'              => array(
							'#type' => 'addmore',
						),
						'#horizontal'       => true,
					),
					'limit'         => array(
						'#type'          => 'number',
						'#title'         => __( 'Limit to X items (0 for all items)', 'directories-pro' ),
						'#default_value' => 0,
						'#min_value'     => 0,
						'#integer'       => true,
						'#horizontal'    => true,
					),
					'offset'        => array(
						'#type'          => 'number',
						'#title'         => __( 'Start from Xth item', 'directories-pro' ),
						'#default_value' => 1,
						'#min_value'     => 1,
						'#integer'       => true,
						'#horizontal'    => true,
					),
					'limit_request' => array(
						'#type'          => 'slider',
						'#title'         => __( 'Number of items to process per request', 'directories-pro' ),
						'#description'   => __( 'Adjust this setting if you are experiencing timeout errors.', 'directories-pro' ),
						'#default_value' => empty( $bundle->info['is_taxonomy'] ) ? 10 : 20,
						'#min_value'     => 1,
						'#max_value'     => 50,
						'#integer'       => true,
						'#horizontal'    => true,
					),
				);

			default:
		}
	}

	public function systemToolInit( array $settings, array &$storage, array &$logs, ?Context $context = null ) {
		try {
			$this->_application->System_Util_validateDirectory( $this->_application->getComponent( 'System' )->getTmpDir(), true );
		} catch ( \Exception $e ) {
			throw new Exception\RuntimeException( $e->getMessage() );
		}

		if ( $limit = (int) ( $settings['run']['limit'] ?? 0 ) ) {
			return array( 'default' => $limit );
		}

		$bundle       = $this->_getBundle( $context );
		$query_fields = empty( $settings['run']['query']['fields'] ) ? array() : $settings['run']['query']['fields'];
		return array( 'default' => $this->_getQuery( $bundle, $query_fields )->count() );
	}

	public function systemToolRunTask( $task, array $settings, $iteration, $total, array &$storage, array &$logs, ?Context $context = null ) {
		$is_first = $iteration === 1;
		$file     = rtrim( $this->_application->getComponent( 'System' )->getTmpDir(), '/' ) . '/' . $settings['run']['filename'] . '.csv';
		if ( false === ( $fp = fopen( $file, $is_first ? 'w+' : 'a+' ) ) ) {
			throw new Exception\RuntimeException( sprintf( 'Failed opening file %s with write permission', $file ) );
		}

		$selected_fields         = $settings['select_fields']['fields'];
		$exporter_settings       = (array) ( $settings['exporter_settings']['settings'] ?? array() );
		$exporters_by_field_type = $this->_application->CSV_Exporters( true );
		$bundle                  = $this->_getBundle( $context );
		$fields                  = $this->_application->Entity_Field( $bundle->name );
		$export_fields           = $columns = array();
		foreach ( $selected_fields as $selected_field ) {
			if ( ! $_selected_field = explode( '__', $selected_field ) ) {
				continue;
			}

			$field_name = array_shift( $_selected_field );
			$column     = implode( '__', $_selected_field );

			if ( ! $field = @$fields[ $field_name ] ) {
				continue;
			}

			if ( ! isset( $export_fields[ $field_name ] ) ) {
				$exporter_name = $exporters_by_field_type[ $field->getFieldType() ];
				if ( ! $exporter = $this->_application->CSV_Exporters_impl( $exporter_name, true ) ) {
					continue;
				}
				$export_fields[ $field_name ] = $exporter_name;
			}
			if ( strlen( $column ) ) {
				$columns[ $field_name ][ $column ] = $column;
			}
		}
		unset( $selected_fields );

		if ( $is_first ) {
			$headers = array();
			foreach ( array_keys( $export_fields ) as $field_name ) {
				if ( isset( $columns[ $field_name ] ) ) {
					foreach ( $columns[ $field_name ] as $column ) {
						$headers[] = $field_name . '__' . $column;
					}
				} else {
					$headers[] = $field_name;
				}
			}
			if ( false === fputcsv( $fp, $headers ) ) {
				throw new Exception\RuntimeException( sprintf( 'Failed writing CSV headers into file %s', $file ) );
			}
		}

		$query_fields = (array) ( $settings['run']['query']['fields'] ?? array() );
		if ( ! isset( $storage['exporter_storage'] ) ) {
			$storage['exporter_storage'] = array( 'files' => array( $file ) );
			$storage['rows_exported']    = 0;
		}
		$paginator = $this->_getQuery( $bundle, $query_fields )
			->sortById()
			->paginate( (int) $settings['run']['limit_request'] )
			->setCurrentPage( $iteration );
		$entities  = $paginator->getElements();

		// Notify
		$this->_application->Action( 'csv_export_entities', array( $bundle, $entities, $export_fields, $exporter_settings ) );

		foreach ( $entities as $entity ) {
			$row          = array();
			$field_values = $entity->getFieldValues( true );
			foreach ( $export_fields as $field_name => $exporter_name ) {
				if ( ! isset( $field_values[ $field_name ] )
					|| $field_values[ $field_name ] === false // can be false if conditions did not meet
				) {
					// No field value, so populate columns with empty values
					if ( isset( $columns[ $field_name ] ) ) {
						foreach ( $columns[ $field_name ] as $column ) {
							$row[ $field_name . '__' . $column ] = '';
						}
					} else {
						$row[ $field_name ] = '';
					}
					continue;
				}

				$settings = $exporter_settings[ $field_name ] ?? array();
				$exported = $this->_application->CSV_Exporters_impl( $exporter_name )->csvExporterDoExport(
					$fields[ $field_name ],
					$settings,
					$field_values[ $field_name ],
					$columns[ $field_name ] ?? array(),
					$storage['exporter_storage'],
					$logs
				);
				if ( isset( $columns[ $field_name ] ) ) {
					foreach ( $columns[ $field_name ] as $column ) {
						$row[ $field_name . '__' . $column ] = $exported[ $column ] ?? '';
					}
				} elseif ( is_array( $exported ) ) {
					foreach ( array_keys( $exported ) as $key ) {
						$row[ $field_name . '__' . $key ] = $exported[ $key ];
					}
				} else {
					$row[ $field_name ] = $exported;
				}
			}
			if ( false === fputcsv( $fp, $this->_application->Filter( 'csv_export_entity', $row, array( $bundle, $entity ) ) ) ) {
				$logs['error'][] = sprintf(
					__( 'Failed exporting: %1$s (ID: %2$d)', 'directories-pro' ),
					$this->_application->Entity_Title( $entity, true ),
					$entity->getId()
				);
			} else {
				$logs['success'][] = sprintf(
					__( 'Exported: %1$s (ID: %2$d)', 'directories-pro' ),
					$this->_application->Entity_Title( $entity, true ),
					$entity->getId()
				);
				++$storage['rows_exported'];
			}
		}
		return $paginator->getElementLimit();
	}

	public function systemToolComplete( array &$storage, array &$logs, &$downloadFile, ?Context $context = null ) {
		if ( empty( $storage['rows_exported'] )
			|| empty( $storage['exporter_storage']['files'][0] )
		) {
			return;
		}

		$downloadFile = basename( $storage['exporter_storage']['files'][0] );
		if ( count( $storage['exporter_storage']['files'] ) > 1
			&& class_exists( '\ZipArchive', false )
		) {
			$zip      = new \ZipArchive();
			$zip_file = basename( $storage['exporter_storage']['files'][0], '.csv' ) . '.zip';
			if ( true !== $result = $zip->open( rtrim( dirname( $storage['exporter_storage']['files'][0] ), '/' ) . '/' . $zip_file, \ZipArchive::CREATE ) ) {
				$logs['error'][] = 'Failed creating zip archive (Error: ' . $this->H( $result ) . ')';
			} else {
				foreach ( $storage['exporter_storage']['files'] as $file ) {
					$zip->addFile( $file, basename( $file ) );
				}
				$zip->close();
				$downloadFile = $zip_file; // let user download zip file
			}
		}
	}

	protected function _loadFieldOptions( IField $field, array &$options, ?Bundle $bundle = null ) {
		$exporters_by_field_type = $this->_application->CSV_Exporters( true );
		$field_type              = $field->getFieldType();
		$exporter_name           = $exporters_by_field_type[ $field_type ] ?? null;
		if ( ! $exporter_name
			|| ( ! $exporter = $this->_application->CSV_Exporters_impl( $exporter_name, true ) )
			|| ! $exporter->csvExporterSupports( $bundle ?? $field->Bundle, $field )
		) {
			return;
		}

		$columns = $exporter->csvExporterColumns( $field );
		if ( ! empty( $columns ) ) {
			foreach ( $columns as $column => $column_label ) {
				$options[ $this->_getFieldOptionValue( $field->getFieldName(), $column ) ] = array(
					'field'         => $this->_getFieldLabel( $field, $column_label ),
					'column_header' => $field->getFieldName() . '__' . $column,
				);
			}
		} else {
			$options[ $this->_getFieldOptionValue( $field->getFieldName(), '' ) ] = array(
				'field'         => $this->_getFieldLabel( $field, '' ),
				'column_header' => $field->getFieldName(),
			);
		}
	}

	protected function _getAllRequiredFields( Bundle $bundle ) {
		$required_fields = array();

		if ( ! empty( $bundle->info['parent'] ) ) {
			$required_fields[] = $bundle->entitytype_name . '_parent';
		}
		if ( ! empty( $bundle->info['parent_field'] ) ) {
			$required_fields[] = $bundle->info['parent_field'];
		}

		return $required_fields;
	}

	protected function _getFieldOptionValue( $fieldName, $column = '' ) {
		$value = $fieldName;
		if ( strlen( $column ) ) {
			$value .= '__' . $column;
		}
		return $value;
	}

	protected function _getFieldLabel( IField $field, $columnLabel = '' ) {
		if ( ( $field_type = $this->_application->Field_Type( $field->getFieldType() ) )
			&& $field_type->fieldTypeInfo( 'admin_only' )
		) {
			$label = $field_type->fieldTypeInfo( 'label' );
		} else {
			$label = $field->getFieldLabel();
		}
		$label = $this->_application->H( $label ) . ' (' . $field->getFieldName() . ')';
		if ( $field->isCustomField() ) {
			$label = '<span class="' . DRTS_BS_PREFIX . 'badge ' . DRTS_BS_PREFIX . 'bg-secondary">'
				. $this->_application->H( __( 'Custom field', 'directories-pro' ) ) . '</span> ' . $label;
		}
		if ( strlen( $columnLabel ) ) {
			$label .= ' - ' . $this->_application->H( $columnLabel );
		}
		return $label;
	}

	public function _validateSelectFields( $form, &$value, $element, $fields, Bundle $bundle ) {
		$value = is_array( $value ) ? array_filter( $value ) : array();

		// Make sure required fields are going to be imported
		foreach ( $this->_getAllRequiredFields( $bundle ) as $field_name ) {
			if ( isset( $fields[ $field_name ] )
				&& ! in_array( $option_value = $this->_getFieldOptionValue( $field_name ), $value )
			) {
				$value[] = $option_value;
			}
		}
	}

	protected function _getQuery( Bundle $bundle, array $fields ) {
		$query = $this->_application->Entity_Query( $bundle->entitytype_name, $bundle->name, $fields );
		return $this->_application->Filter( 'csv_export_query', $query, array( $bundle ) );
	}
}
