<?php

namespace GravityKit\GravityExport\Save\Feature;

use GF_Field_FileUpload;
use GFExcel\Addon\GravityExportAddon;
use GFExcel\GFExcel;
use GFExcel\GFExcelOutput;
use GFFormsModel;
use GravityKit\GravityExport\Save\Addon\SaveAddon;
use GravityKit\GravityExport\Save\Exception\SaveException;
use GravityKit\GravityExport\Save\Service\StorageService;
use GravityKit\GravityExport\Save\StorageType\CopyingStorageTypeInterface;
use GravityKit\GravityExport\Save\StorageType\Local;
use GravityKit\GravityExport\Save\StorageType\StorageTypeInterface;

/**
 * Feature that copies the file uploads of an entry with the entry to the off site storage type.
 * @since $ver$
 */
final class CopyFileUploads {
	/**
	 * Name of the feed setting
	 * @since $ver$
	 */
	public const SETTING_COPY_FILES = 'copy_files';

	/**
	 * The upload dir.
	 * @since $ver$
	 * @var string[]
	 */
	private $upload_dir;

	/**
	 * Register hooks.
	 * @since $ver$
	 */
	public function __construct() {
		$this->upload_dir = wp_get_upload_dir();

		add_filter( 'gk/gravityexport/save/exported/entry', $this, 10, 4 );
	}

	/**
	 * Trigger the feature.
	 *
	 * @param array                $entry        The entry object.
	 * @param array                $form         The form object.
	 * @param array                $feed         The feed object.
	 * @param StorageTypeInterface $storage_type The storage type
	 *
	 * @return void
	 */
	public function __invoke( array $entry, array $form, array $feed, StorageTypeInterface $storage_type ): void {
		if (
			! $this->isValidStorageType( $storage_type )
			|| ! $this->shouldCopyFiles( $feed )
		) {
			return;
		}

		// Get attachments from entry
		$file_uploads = $this->getFilePaths( $form, $feed, $entry );
		if ( ! $file_uploads ) {
			return;
		}

		$target = $this->getTargetDirectory( $storage_type, $feed, $entry );

		// Copy files
		foreach ( $file_uploads as $file ) {
			try {
				$storage_type->copy( $file, $target );
			} catch ( SaveException $e ) {
				// Catch and log exceptions, should not break.
				GravityExportAddon::get_instance()->log_error( $e->getMessage() );
			}
		}
	}


	/**
	 * Whether the provided storage is valid.
	 *
	 * @since $ver$
	 *
	 * @param StorageTypeInterface $storage_type The storage type.
	 *
	 * @return bool
	 */
	private function isValidStorageType( StorageTypeInterface $storage_type ): bool {
		return $storage_type instanceof CopyingStorageTypeInterface
		       && ! $storage_type instanceof Local;
	}

	/**
	 * Whether the file uploads should be copied.
	 * @since $ver$
	 *
	 * @param array $feed The feed object.
	 *
	 * @return bool Whether the file uploads should be copied.
	 */
	private function shouldCopyFiles( array $feed ): bool {
		$meta = rgar( $feed, 'meta', [] );

		return (bool) gf_apply_filters( [
			'gk/gravityexport/save/settings/copy-files',
			rgar( $feed, 'form_id', 0 ),
			rgar( $feed, 'id', 0 ),
		], rgar( $meta, self::SETTING_COPY_FILES, false ) );
	}

	/**
	 * Returns the local file paths to upload with the entry.
	 * @since $ver$
	 *
	 * @param array $form  The form object.
	 * @param array $entry The entry object.
	 *
	 * @return string[] The paths.
	 */
	private function getFilePaths( array $form, array $feed, array $entry ): array {
		$file_upload_fields = $this->getFileUploadFields( $form, $feed );
		if ( ! $file_upload_fields ) {
			return [];
		}

		$paths = [];

		foreach ( $file_upload_fields as $field ) {
			foreach ( explode( ',', $field->get_value_export( $entry ) ) as $file ) {
				$file = trim( $file );

				if ( empty( $file ) ) {
					continue;
				}

				$path = GFFormsModel::get_physical_file_path( $file, $entry['id'] ?? 0 );

				if ( $path === $file ) {
					// This is possibly a multi upload. In any case, it should replace the url with the local path.
					$path = str_replace( $this->upload_dir['url'], $this->upload_dir['path'], $file );
					if ( $path === $file ) {
						// It is still a URL, so we skip this file.
						continue;
					}
				}

				$paths[] = $path;
			}
		}

		return $paths;
	}

	/**
	 * Returns the target directory for the feed.
	 * @since $ver$
	 *
	 * @param array $feed  The feed object.
	 * @param array $entry The entry object.
	 *
	 * @return string The target path.
	 */
	private function getTargetDirectory( StorageTypeInterface $storage_type, array $feed, array $entry ): string {
		$meta = rgar( $feed, 'meta', [] );

		return SaveAddon::get_target_path( $meta, $feed, $entry );
	}

	/**
	 * Retrieves the field upload from the form.
	 * @since $ver$
	 *
	 * @param array $form The form object.
	 *
	 * @return GF_Field_FileUpload[] The file upload fields.
	 */
	private function getFileUploadFields( array $form, array $feed ): array {
		$source_feed_id = StorageService::getSourceFeedId( $feed );

		$form_id = rgar( $form, 'id', 0 );
		$output  = new GFExcelOutput( $form_id, GFExcel::getRenderer(), null, $source_feed_id );

		return array_filter( $output->getFields(), function ( $gf_field ) {
			return $gf_field instanceof GF_Field_FileUpload;
		} );
	}
}
