<?php

class Brizy_Editor_Zip_Archiver implements Brizy_Editor_Zip_ArchiverInterface {
	use Brizy_Editor_Asset_AttachmentAware;
	use Brizy_Editor_Trait_Sanitize;

	const ARCHIVE_TYPE_LAYOUT = 'layout';
	const ARCHIVE_TYPE_BLOCK = 'block';
	const ARCHIVE_TYPE_POPUP = 'popup';

	/**
	 * @var Brizy_Editor_Project
	 */
	protected $project;
	/**
	 * @var Brizy_Admin_Fonts_Manager
	 */
	protected $fontManager;

	/**
	 * @var string
	 */
	protected $syncVersion;

	/**
	 * @var bool
	 */
	protected $isWhiteLabel;
	protected $classMap;

	public function __construct(
		Brizy_Editor_Project $project, Brizy_Admin_Fonts_Manager $fontManager, $syncVersion, $isWhiteLabel = false
	) {
		$this->project      = $project;
		$this->fontManager  = $fontManager;
		$this->syncVersion  = $syncVersion;
		$this->isWhiteLabel = $isWhiteLabel;

		$this->classMap     = [
			'Editor_Post'   => Brizy_Editor_Post::class,
			'Editor_Block'  => Brizy_Editor_Block::class,
			'Editor_Layout' => Brizy_Editor_Layout::class,
			'Editor_Popup'  => Brizy_Editor_Popup::class,
		];

		if ( ! class_exists( 'ZipArchive' ) ) {
			throw new InvalidArgumentException( __( 'Please install/enable php zip extension. Contact your hosting company for more info and help.' ) );
		}
	}

	public function getScreenshotType( $archiveType ) {
		switch ( $archiveType ) {
			case 'Editor_Layout':
			case Brizy_Editor_Layout::class:
				return Brizy_Editor_Screenshot_Manager::BLOCK_TYPE_LAYOUT;
            case 'Editor_Block':
			case Brizy_Editor_Block::class:
				return Brizy_Editor_Screenshot_Manager::BLOCK_TYPE_SAVED;
		}
	}

	/**
	 * @param Brizy_Editor_Zip_ArchiveItem[] $archiveItems
	 * @param null $outZipPath
	 *
	 * @return mixed|string|null
	 * @throws Exception
	 */
	public function createZip( $archiveItems, $fileName ) {

		wp_raise_memory_limit( 'image' );
		// create archive folder
		$outZipPath = $this->prepareArchiveFilepath( $fileName );
		$z          = new ZipArchive();
		$z->open( $outZipPath, ZipArchive::CREATE );
		foreach ( $archiveItems as $item ) {
			$this->addEntityToZip( $z, $item );
		}
		if ( ! $z->close() ) {
			throw new Exception( __( 'Failed to create archive.' ) );
		}
		unset( $z );

		return $outZipPath;
	}

	public function addEntityToZip( ZipArchive $z, Brizy_Editor_Zip_ArchiveItem $item ) {
		// get block data
		/**
		 * @var Brizy_Editor_Layout $block ;
		 */
		$block = $item->getPost();
		$data  = array(
			'class'         => $this->getExportClass(get_class( $block )),
			'title'         => $block->getTitle(),
			'meta'          => $block->getMeta(),
			'media'         => $block->getMedia(),
			'data'          => $block->getEditorData( true ),
			'editorVersion' => $this->syncVersion,
			'files'         => [
				'images'      => [],
				'uploads'     => [],
				'screenshots' => [],
				'fonts'       => [],
			],
			'hasPro'        => $item->isPro(),
		);
		if ( $block instanceof Brizy_Editor_Layout ) {
			$data['globalStyles'] = $block->getGlobalStyles();
		}
		if ( method_exists( $block, 'getTags' ) ) {
			$data['tags'] = $block->getTags();
		}
		$z->addEmptyDir( $block->getUid() );
		$media   = json_decode( $block->getMedia() );
		$meta    = json_decode( $block->getMeta() );
		$manager = new Brizy_Editor_Screenshot_Manager( new  Brizy_Editor_UrlBuilder( $this->project, $block ) );
		// create block data json file
		$data = $this->addImages( $z, $media, $data, $block->getUid() );
		$data = $this->addUploads( $z, $media, $data, $block->getUid() );
		$data = $this->addFonts( $z, $media, $data, $block->getUid() );
		$data = $this->addScreenshot( $z, $meta, $manager, $data, $block->getUid() );
		$z->addFromString( $block->getUid() . "/data.json", json_encode( $data ) );
	}

	public function createFromZip( $zipPath ) {

		wp_raise_memory_limit( 'image' );
		global $wpdb;
		if ( ! file_exists( $zipPath ) ) {
			Brizy_Logger::instance()->error( 'Unable to find the archive path.', [ 'path' => $zipPath ] );
			throw new Exception( __( 'Unable to find the archive.' ) );
		}
		$z = new ZipArchive();
		$z->open( $zipPath );
		$instances = [];
		$failed    = [];
		// As long as statIndex() does not return false keep iterating
		try {
			$wpdb->query( "START TRANSACTION" );
			//find root folders
			$folders = [];
			for ( $i = 0; $i < $z->numFiles; $i ++ ) {
				$name  = $z->getNameIndex( $i );
				$parts = explode( '/', $name );
				if ( count( $parts ) ) {
					$folders[] = $parts[0];
				}
			}
			$folders = array_unique( $folders );
			foreach ( $folders as $folder ) {
				try {
					$instances[] = $this->createSingleFromZipPath( $z, $folder );
				} catch ( Exception $e ) {
					$failed[] = [ 'uid' => $folder, 'message' => $e->getMessage() ];
				}
			}
			$wpdb->query( "COMMIT" );
		} catch ( Exception $e ) {
			$wpdb->query( "ROLLBACK" );
			throw $e;
		}
		$z->close();

		return [ $instances, $failed ];
	}

	/**
	 * @throws Exception
	 */
	private function createSingleFromZipPath( ZipArchive $z, $dir ) {

		$data   = json_decode( $z->getFromName( $dir . '/data.json' ) );
		$hasPro = (bool) $data->hasPro;
		if ( $hasPro && ! Brizy_Compatibilities_BrizyProCompatibility::isPro() ) {
			throw new Exception( 'Attempt to import a PRO block in a non PRO environment.' );
		}
		$entityClass = $this->getImportClass($data->class);
		if ( ! class_exists( $entityClass ) ) {
			Brizy_Logger::instance()->error( "Unsupported object found in zip file", [ 'class' => $entityClass ] );
			throw new Exception( __( "Unsupported object found in zip file" ) );
		}
		if ( ! $this->isVersionSupported( $data->editorVersion ) ) {
			Brizy_Logger::instance()->error( "Unsupported zip file version", [ 'version' => $data->editorVersion ] );
			throw new Exception( __( "Unsupported zip file version" ) );
		}
		/**
		 * @var Brizy_Editor_Layout $block ;
		 */
		$block = $this->getManager( $entityClass )->createEntity( md5( random_bytes( 10 ) ), 'publish' );
		$block->set_needs_compile( true );
		$block->setEditorData( $this->sanitizeJson( $data->data ) );
		$block->setMeta( $data->meta );
		if ( isset( $data->title ) ) {
			$block->setTitle( $data->title );
		}
		if ( method_exists( $entityClass, 'setTags' ) && isset( $data->tags ) ) {
			$block->setTags( $data->tags );
		}
		$block->setMedia( $data->media );
		$block->set_editor_version( $data->editorVersion );
		if ( $block instanceof Brizy_Editor_Layout ) {
			$block->setGlobalStyles( $data->globalStyles );
		}
		$block->setDataVersion( 1 );
		$block->save();
		// store images
		$this->allowUploadsInCallback( function () use ( $data, $z, $block ) {
			$this->storeImages( $data, $z, $block );
			$this->storeUploads( $data, $z, $block );
		} );
		$this->storeScreenshot( $data, $z, $block );
		$this->storeFonts( $data, $z, $block );

		return $block;
	}

	public function getSyncVersion() {
		return $this->syncVersion;
	}

	public function isVersionSupported( $version ) {
		$version = preg_replace( "/-wp/i", "", $version );
		$version = preg_replace( "/-cloud/i", "", $version );

		return version_compare( $this->getSyncVersion(), $version, '>=' );
	}

	protected function prepareArchiveFilepath( $fileName ) {
		return sys_get_temp_dir() . DIRECTORY_SEPARATOR . $fileName;
	}

	protected function storeImages( $data, ZipArchive $z, Brizy_Editor_Post $block ) {
		$urlBuilder = new Brizy_Editor_UrlBuilder( $this->project, $block->getWpPostId() );
		foreach ( $data->files->images as $uid => $path ) {

			if ( $this->getAttachmentByMediaName( $uid ) ) {
				continue;
			}
			$basename            = basename( $path );
			$imageContent        = $z->getFromName( $path );
			$tempFile            = Brizy_Editor_Asset_StaticFileTrait::createSideLoadFile( $basename, $imageContent );
			$original_asset_path = $urlBuilder->page_upload_path( "/assets/images/" . $basename );
			Brizy_Editor_Asset_StaticFileTrait::createSideLoadMediaAttachment( $tempFile, $original_asset_path, $block->getWpPostId(), $uid );
		}
	}

	protected function storeScreenshot( $data, ZipArchive $z, Brizy_Editor_Post $block ) {

		$manager = new Brizy_Editor_Screenshot_Manager( new Brizy_Editor_UrlBuilder( $block ) );
		$screens = (array) $data->files->screenshots;
		foreach ( $screens as $uid => $filePath ) {
			$manager->saveScreenshot( $uid, $this->getScreenshotType( $data->class ), $z->getFromName( $filePath ), $block->getWpPostId() );
		}

	}

	protected function storeFonts( $data, ZipArchive $z, Brizy_Editor_Post $block ) {
		foreach ( $data->files->fonts as $fontUid => $font ) {
			$family     = $font->family;
			$weights    = $font->weights;
			$newWeights = array();
			foreach ( (array) $weights as $weight => $weightType ) {
				foreach ( (array) $weightType as $type => $filePath ) {
					$newWeights[ $weight ][ $type ] = Brizy_Editor_Asset_StaticFileTrait::createSideLoadFile( basename( $filePath ), $z->getFromName( $filePath ) );
				}
			}
			try {
				$this->fontManager->createFont( $fontUid, $family, $newWeights, 'uploaded' );
			} catch ( Brizy_Admin_Fonts_Exception_DuplicateFont $e ) {
				continue;
			} catch ( Exception $e ) {
				Brizy_Logger::instance()->error( $e );
				//throw new Exception( __( 'Failed to create font [' . $fontUid . '] from archive.' ) );
				continue;
			}
		}
	}

	protected function storeUploads( $data, ZipArchive $z, Brizy_Editor_Post $block ) {

		$urlBuilder = new Brizy_Editor_UrlBuilder( $this->project, $block->getWpPostId() );
		foreach ( $data->files->uploads as $uidKey => $path ) {
			list( $uid, $uploadName ) = explode( '|||', $uidKey );
			if ( $this->getAttachmentByMediaName( $uid ) ) {
				continue;
			}
			$original_asset_path          = $urlBuilder->brizy_upload_path( "/custom_files/" . $uploadName );
			$original_asset_path_relative = $urlBuilder->brizy_upload_relative_path( "/custom_files/" . $uploadName );
			$tempFile                     = Brizy_Editor_Asset_StaticFileTrait::createSideLoadFile( $uploadName, $z->getFromName( $path ) );
			$filePath                     = Brizy_Editor_Asset_StaticFileTrait::createSideLoadMedia( $tempFile, $original_asset_path );
			if ( $filePath instanceof WP_Error ) {
				throw new Exception( $filePath->get_error_message() . " File Path: " . htmlentities( $original_asset_path_relative ) );
			}
			Brizy_Editor_Asset_StaticFileTrait::createMediaAttachment( $filePath, $original_asset_path_relative, $block->getWpPostId(), $uid );
		}
	}

	protected function getManager( $class ) {
		static $managers = [];
		if ( isset( $managers[ Brizy_Editor_Block::class ] ) ) {
			return $managers[ Brizy_Editor_Block::class ];
		}
		switch ( $class ) {
			case Brizy_Editor_Block::class:
				return $managers[ Brizy_Editor_Block::class ] = new Brizy_Admin_Blocks_Manager( Brizy_Admin_Blocks_Main::CP_SAVED );
			case Brizy_Editor_Layout::class:
				return $managers[ Brizy_Editor_Layout::class ] = new Brizy_Admin_Layouts_Manager();
		}
	}

	/**
	 * @param ZipArchive $z
	 * @param $block
	 * @param $filesImagesPath
	 * @param array $data
	 *
	 * @return array
	 * @throws Exception
	 */
	protected function addImages( ZipArchive $z, $media, array $data, $dir ) {
		$z->addEmptyDir( $filesImagesPath = $dir . '/files/images' );
		foreach ( $media->images as $mediaUid ) {
			$mediaId = (int) $this->getAttachmentByMediaName( $mediaUid );
			if ( ! $mediaId ) {
				continue;
			}
			$imagePath = get_attached_file( $mediaId );
			$imageName = basename( $imagePath );
			if ( file_exists( $imagePath ) ) {
				$path = $filesImagesPath . "/" . $imageName;
				$z->addFile( $imagePath, $path );
				$data['files']['images'][ $mediaUid ] = $path;
			} else {
				Brizy_Logger::instance()->error( 'Archive object failed. The file ' . $imagePath . ' does not exist', [] );
				//throw new Exception( __( 'Archive object failed. The file ' . $imagePath . ' does not exist' ) );
			}
		}

		return $data;
	}

	/**
	 * @param ZipArchive $z
	 * @param $media
	 * @param array $data
	 *
	 * @return array
	 */
	protected function addUploads( ZipArchive $z, $media, array $data, $dir ) {

		$z->addEmptyDir( $filesUploadsPath = $dir . '/files/uploads' );
		foreach ( $media->uploads as $mediaUpload ) {
			list( $uploadUid, $uploadName ) = explode( '|||', $mediaUpload );
			$mediaId  = (int) $this->getAttachmentByMediaName( $uploadUid );
			$filePath = get_attached_file( $mediaId );
			$fileName = basename( $filePath );
			if ( file_exists( $filePath ) ) {
				$path = $filesUploadsPath . "/" . $fileName;
				$z->addFile( $filePath, $path );
				$data['files']['uploads'][ $mediaUpload ] = $path;
			}
		}

		return $data;
	}

	/**
	 * @param $media
	 * @param ZipArchive $z
	 * @param $filesFontsPath
	 * @param array $data
	 *
	 * @return array
	 * @throws Exception
	 */
	protected function addFonts( ZipArchive $z, $media, array $data, $dir ) {
		$z->addEmptyDir( $filesFontsPath = $dir . '/files/fonts' );
		foreach ( $media->fonts as $fontUid ) {
			$fontData = $this->fontManager->getFontForExport( $fontUid );
			if ( ! $fontData ) {
				continue;
			}
			// prepare fontData data
			foreach ( $fontData['weights'] as $weigth => $files ) {
				foreach ( $files as $type => $file ) {
					$fontName = basename( $file );
					if ( file_exists( $file ) ) {
						$path = $filesFontsPath . "/" . $fontName;
						$z->addFile( $file, $path );
						$fontData['weights'][ $weigth ][ $type ] = $path;
						$data['files']['fonts'][ $fontUid ]      = $fontData;
					} else {
						Brizy_Logger::instance()->error( 'Archive object failed. The file ' . $file . ' does not exist', [] );
						//throw new Exception( __('Archive object failed. The file ' . $file . ' does not exist') );
						continue;
					}
				}
			}
		}

		return $data;
	}

	/**
	 * @param ZipArchive $z
	 * @param $meta
	 * @param $block
	 * @param array $data
	 *
	 * @return array
	 * @throws Exception
	 */
	protected function addScreenshot(
		ZipArchive $z, $meta, Brizy_Editor_Screenshot_Manager $manager, array $data, $dir
	) {
		$screenUid = $meta->_thumbnailSrc;
		if ( $screenUid ) {

			$screenPath = $manager->getScreenshot( $screenUid );
			if ( ! file_exists( $screenPath ) ) {
				Brizy_Logger::instance()->error( 'Archive object failed. The file ' . $screenPath . ' does not exist', [] );

				//throw new Exception( __('Archive object failed. The file ' . $screenPath . ' does not exist') );
				return;
			}
			$zipScreenPath = $dir . "/files/screenshots/" . basename( $screenPath );
			$z->addFile( $screenPath, $zipScreenPath );
			$data['files']['screenshots'][ $screenUid ] = $zipScreenPath;
		}

		return $data;
	}

	protected function allowUploadsInCallback( $callback ) {
		// enable svg upload
		$svnUpload         = new Brizy_Admin_Svg_Main();
		$jsonUpload        = new Brizy_Admin_Json_Main();
		$svnUploadEnabled  = Brizy_Admin_Svg_Main::isSvgEnabled();
		$jsonUploadEnabled = Brizy_Admin_Json_Main::isJsonEnabled();
		if ( ! $svnUploadEnabled ) {
			$svnUpload->enableSvgUpload();
		}
		if ( ! $jsonUploadEnabled ) {
			$jsonUpload->enableJsonUpload();
		}
		$callback();
		// disabled it if was disabled before
		if ( ! $svnUploadEnabled ) {
			$svnUpload->disableSvgUpload();
		}
		if ( ! $jsonUploadEnabled ) {
			$jsonUpload->disableJsonUpload();
		}
	}

	private function getExportClass( $class ) {

		$map = array_flip($this->classMap);

		if ( isset( $map[ $class ] ) ) {
			return $map[ $class ];
		}

		return $class;
	}
	private function getImportClass( $class ) {

		if ( isset( $this->classMap[ $class ] ) ) {
			return $this->classMap[ $class ];
		}

		return $class;
	}
}
