<?php
namespace BulkPageCreator;

use WP_PowerSuite\Core\AbstractModule;
use WP_PowerSuite\Core\NetworkScope;

class Module extends AbstractModule {

	private const MODULE_ID = 'bulk-page-creator';

	private const MODULE_VERSION = '1.3.0';

	private const NONCE_ACTION = 'wpps_bulk_page_creator';

	private const THROTTLE_MAX_PER_MINUTE = 240;

	private const CSV_MAX_ROWS = 500;

	private const CSV_MAX_BYTES = 1048576;

	private const ROW_DATA_MAX_BYTES = 65536;

	private const ROW_DATA_MAX_KEYS = 100;

	private const TAXONOMY_TERMS_MAX = 50;

	private const TERMS_PER_TAX_MAX = 500;

	/** @var array<string, mixed>|null */
	private ?array $settings_cache = null;

	public function getId(): string {
		return self::MODULE_ID;
	}

	public function getName(): string {
		return __( 'Bulk Page Creator', 'wp-powersuite' );
	}

	public function getVersion(): string {
		return self::MODULE_VERSION;
	}

	public function getDescription(): string {
		return __( 'Generate many similar pages from a CSV template—great for local landing pages and repeatable layouts.', 'wp-powersuite' );
	}

	public function getContext(): string|array {
		return [ 'admin', 'ajax' ];
	}

	public function getDependencies(): array {
		return [];
	}

	private function isLicenseActive(): bool {
		return function_exists( 'wp_powersuite_is_license_valid' ) && wp_powersuite_is_license_valid();
	}

	protected function registerHooks(): void {
		if ( ! $this->isLicenseActive() ) {
			return;
		}

		add_action( 'wp_ajax_wpps_bpc_create_page', [ $this, 'ajaxCreatePage' ] );
		add_action( 'wp_ajax_wpps_bpc_parse_file', [ $this, 'ajaxParseFile' ] );
		add_action( 'wp_ajax_wpps_bpc_get_taxonomies', [ $this, 'ajaxGetTaxonomies' ] );
	}

	public function ajaxParseFile(): void {
		$this->verifyNonce();
		$post_type = isset( $_POST['post_type'] ) ? sanitize_key( wp_unslash( $_POST['post_type'] ) ) : $this->getTargetPostType();
		if ( ! in_array( $post_type, [ 'page', 'post' ], true ) ) {
			$post_type = $this->getTargetPostType();
		}
		$this->assertCanCreateType( $post_type );

		if ( empty( $_FILES['file'] ) || UPLOAD_ERR_OK !== (int) $_FILES['file']['error'] ) {
			wp_send_json_error( [ 'message' => __( 'No file uploaded or upload error.', 'wp-powersuite' ) ] );
		}

		$file = $_FILES['file'];
		if ( empty( $file['tmp_name'] ) || ! is_uploaded_file( $file['tmp_name'] ) ) {
			wp_send_json_error( [ 'message' => __( 'Invalid upload.', 'wp-powersuite' ) ] );
		}
		if ( isset( $file['size'] ) && (int) $file['size'] > self::CSV_MAX_BYTES ) {
			wp_send_json_error(
				[
					'message' => sprintf(
						/* translators: %s: max file size in megabytes */
						__( 'CSV file is too large. Maximum size is %s MB.', 'wp-powersuite' ),
						(string) ( self::CSV_MAX_BYTES / 1048576 )
					),
				]
			);
		}

		$checked = wp_check_filetype_and_ext( $file['tmp_name'], $file['name'], [ 'csv' => 'text/csv' ] );
		$ext     = strtolower( (string) ( $checked['ext'] ?? pathinfo( $file['name'], PATHINFO_EXTENSION ) ) );
		if ( 'csv' !== $ext ) {
			wp_send_json_error( [ 'message' => __( 'Unsupported file type. Use CSV. For XLSX, use the Upload XLSX tab (parsed in browser).', 'wp-powersuite' ) ] );
		}

		$delim_key = isset( $_POST['delimiter'] ) ? sanitize_text_field( wp_unslash( $_POST['delimiter'] ) ) : 'comma';
		$delims    = [ 'comma' => ',', 'semicolon' => ';', 'tab' => "\t" ];
		$delimiter = $delims[ $delim_key ] ?? ',';

		$handle = fopen( $file['tmp_name'], 'r' );
		if ( ! $handle ) {
			wp_send_json_error( [ 'message' => __( 'Could not read file.', 'wp-powersuite' ) ] );
		}
		$headers = fgetcsv( $handle, 0, $delimiter );
		if ( ! $headers ) {
			fclose( $handle );
			wp_send_json_error( [ 'message' => __( 'Invalid or empty CSV.', 'wp-powersuite' ) ] );
		}
		$headers = array_map( 'trim', $headers );
		$rows    = [];
		$count   = 0;
		while ( ( $row = fgetcsv( $handle, 0, $delimiter ) ) !== false ) {
			if ( $count >= self::CSV_MAX_ROWS ) {
				break;
			}
			$obj = [];
			foreach ( $headers as $i => $h ) {
				$obj[ $h ] = isset( $row[ $i ] ) ? trim( (string) $row[ $i ] ) : '';
			}
			$rows[] = $obj;
			++$count;
		}
		fclose( $handle );

		wp_send_json_success( [ 'headers' => $headers, 'rows' => $rows ] );
	}

	public function ajaxGetTaxonomies(): void {
		$this->verifyNonce();
		$post_type = isset( $_POST['post_type'] ) ? sanitize_key( wp_unslash( $_POST['post_type'] ) ) : 'page';
		if ( ! in_array( $post_type, [ 'page', 'post' ], true ) ) {
			$post_type = 'page';
		}
		$this->assertCanEditType( $post_type );

		$result = [];
		foreach ( get_object_taxonomies( $post_type, 'objects' ) as $slug => $tax ) {
			if ( ! current_user_can( $tax->cap->assign_terms ?? 'assign_terms' ) ) {
				continue;
			}
			$terms = get_terms(
				[
					'taxonomy'   => $slug,
					'hide_empty' => false,
					'number'     => self::TERMS_PER_TAX_MAX,
					'orderby'    => 'name',
					'order'      => 'ASC',
					'fields'     => 'id=>name',
				]
			);
			if ( is_wp_error( $terms ) || ! is_array( $terms ) || $terms === [] ) {
				continue;
			}
			$mapped = [];
			foreach ( $terms as $term_id => $term_name ) {
				$mapped[] = [
					'id'   => (int) $term_id,
					'name' => (string) $term_name,
				];
			}
			$result[ $slug ] = [
				'label' => $tax->labels->name ?? $slug,
				'terms' => $mapped,
			];
		}
		wp_send_json_success( [ 'taxonomies' => $result ] );
	}

	public function ajaxCreatePage(): void {
		$this->verifyNonce();
		if ( ! $this->consumeCreateThrottle() ) {
			wp_send_json_error( [ 'message' => __( 'Too many creates per minute. Wait a moment and try again.', 'wp-powersuite' ) ] );
		}

		$post_type = isset( $_POST['post_type'] ) ? sanitize_key( wp_unslash( $_POST['post_type'] ) ) : '';
		if ( ! in_array( $post_type, [ 'page', 'post' ], true ) ) {
			$post_type = $this->getTargetPostType();
		}
		$this->assertCanCreateType( $post_type );
		$this->assertCanPublishType( $post_type );

		$template_id     = isset( $_POST['template_id'] ) ? (int) $_POST['template_id'] : 0;
		$row_raw         = isset( $_POST['row_data'] ) ? (string) wp_unslash( $_POST['row_data'] ) : '';
		if ( strlen( $row_raw ) > self::ROW_DATA_MAX_BYTES ) {
			wp_send_json_error( [ 'message' => __( 'Row data is too large.', 'wp-powersuite' ) ] );
		}
		$slug_column     = $this->sanitizeColumnName( isset( $_POST['slug_column'] ) ? wp_unslash( $_POST['slug_column'] ) : '' );
		$title_column    = $this->sanitizeColumnName( isset( $_POST['title_column'] ) ? wp_unslash( $_POST['title_column'] ) : '' );
		$parent_column   = $this->sanitizeColumnName( isset( $_POST['parent_column'] ) ? wp_unslash( $_POST['parent_column'] ) : '' );
		$existing_action = isset( $_POST['existing_action'] ) ? sanitize_key( wp_unslash( $_POST['existing_action'] ) ) : 'create';
		if ( ! in_array( $existing_action, [ 'create', 'update', 'skip' ], true ) ) {
			$existing_action = 'create';
		}
		$taxonomy_terms = [];
		if ( isset( $_POST['taxonomy_terms'] ) && is_array( $_POST['taxonomy_terms'] ) ) {
			$taxonomy_terms = array_values(
				array_unique(
					array_filter( array_map( 'absint', wp_unslash( $_POST['taxonomy_terms'] ) ) )
				)
			);
			if ( count( $taxonomy_terms ) > self::TAXONOMY_TERMS_MAX ) {
				wp_send_json_error( [ 'message' => __( 'Too many taxonomy terms selected.', 'wp-powersuite' ) ] );
			}
		}

		if ( $template_id <= 0 ) {
			wp_send_json_error( [ 'message' => __( 'Template ID is required.', 'wp-powersuite' ) ] );
		}
		$this->assertCanUseTemplate( $template_id );

		$row_data = json_decode( $row_raw, true );
		if ( ! is_array( $row_data ) || count( $row_data ) > self::ROW_DATA_MAX_KEYS ) {
			wp_send_json_error( [ 'message' => __( 'Invalid row data.', 'wp-powersuite' ) ] );
		}

		if ( ! empty( $row_data['wpps_ignore'] ) ) {
			wp_send_json_success( [ 'skipped' => true, 'reason' => 'ignore' ] );
		}

		$template = get_post( $template_id );
		if ( ! $template || ! in_array( $template->post_type, [ 'page', 'post' ], true ) ) {
			wp_send_json_error( [ 'message' => __( 'Template not found.', 'wp-powersuite' ) ] );
		}
		if ( $template->post_type !== $post_type ) {
			wp_send_json_error( [ 'message' => __( 'Template type must match the selected post type.', 'wp-powersuite' ) ] );
		}

		$settings = $this->getSettings();
		$parent   = (int) ( $settings['parent_page'] ?? 0 );
		$author   = $this->resolvePostAuthor( (int) ( $settings['author_id'] ?? 0 ), (int) $template->post_author, $post_type );
		$status   = $settings['post_status'] ?? 'publish';

		if ( $parent_column && isset( $row_data[ $parent_column ] ) && is_numeric( $row_data[ $parent_column ] ) ) {
			$parent = (int) $row_data[ $parent_column ];
		}

		$parent = $this->normalizeParentId( $parent, $post_type );

		$content = $this->substitute( $template->post_content, $row_data );
		$title   = $this->substitute( $template->post_title, $row_data );
		$excerpt = $this->substitute( (string) $template->post_excerpt, $row_data );

		$slug_raw = '';
		if ( $slug_column && isset( $row_data[ $slug_column ] ) ) {
			$slug_raw = (string) $row_data[ $slug_column ];
		} elseif ( $title_column && isset( $row_data[ $title_column ] ) ) {
			$slug_raw = (string) $row_data[ $title_column ];
		} else {
			$first = reset( $row_data );
			$slug_raw = is_string( $first ) ? $first : $title;
		}
		$slug = sanitize_title( $slug_raw );

		if ( '' === $slug ) {
			$slug = sanitize_title( $title );
		}
		if ( '' === $slug ) {
			$slug = 'page-' . wp_rand( 1000, 9999 );
		}

		$existing = get_page_by_path( $slug, OBJECT, $post_type );
		if ( $existing ) {
			if ( 'skip' === $existing_action ) {
				wp_send_json_success( [ 'skipped' => true, 'reason' => 'duplicate', 'slug' => $slug ] );
			}
			if ( 'update' === $existing_action ) {
				if ( ! current_user_can( 'edit_post', $existing->ID ) ) {
					wp_send_json_error( [ 'message' => __( 'You cannot update an existing post with this slug.', 'wp-powersuite' ) ] );
				}
				$new_post = [
					'ID'           => $existing->ID,
					'post_title'   => $title ?: __( 'Untitled', 'wp-powersuite' ),
					'post_content' => $content,
					'post_excerpt' => $excerpt,
					'post_status'  => $status,
					'post_author'  => $author,
					'post_parent'  => $parent,
				];
				$post_id = wp_update_post( wp_slash( $new_post ), true );
				if ( is_wp_error( $post_id ) ) {
					wp_send_json_error( [ 'message' => $post_id->get_error_message(), 'slug' => $slug ] );
				}
				$this->copyPostMeta( $template_id, (int) $post_id, $row_data );
				$this->setTaxonomyTerms( (int) $post_id, $taxonomy_terms, $post_type );
				wp_send_json_success(
					[
						'post_id' => (int) $post_id,
						'title'   => $new_post['post_title'],
						'slug'    => $slug,
						'updated' => true,
					]
				);
			}
			$slug .= '-' . wp_rand( 100, 999 );
		}

		$new_post = [
			'post_type'      => $post_type,
			'post_title'     => $title ?: __( 'Untitled', 'wp-powersuite' ),
			'post_content'   => $content,
			'post_excerpt'   => $excerpt,
			'post_name'      => $slug,
			'post_status'    => $status,
			'post_author'    => $author,
			'post_parent'    => $parent,
			'post_date'      => current_time( 'mysql' ),
			'post_date_gmt'  => current_time( 'mysql', true ),
			'comment_status' => $template->comment_status,
			'ping_status'    => $template->ping_status,
		];

		$post_id = wp_insert_post( wp_slash( $new_post ), true );

		if ( is_wp_error( $post_id ) ) {
			wp_send_json_error( [ 'message' => $post_id->get_error_message(), 'slug' => $slug ] );
		}

		$this->copyPostMeta( $template_id, (int) $post_id, $row_data );
		$this->setTaxonomyTerms( (int) $post_id, $taxonomy_terms, $post_type );

		wp_send_json_success(
			[
				'post_id' => (int) $post_id,
				'title'   => $new_post['post_title'],
				'slug'    => $slug,
			]
		);
	}

	private function sanitizeColumnName( mixed $name ): string {
		if ( ! is_string( $name ) && ! is_numeric( $name ) ) {
			return '';
		}

		return sanitize_key( (string) $name );
	}

	private function normalizeParentId( int $parent, string $post_type ): int {
		if ( $parent <= 0 ) {
			return 0;
		}
		if ( ! is_post_type_hierarchical( $post_type ) ) {
			return 0;
		}
		$p = get_post( $parent );
		if ( ! $p instanceof \WP_Post || $p->post_type !== $post_type ) {
			return 0;
		}
		if ( ! current_user_can( 'edit_post', $parent ) ) {
			return 0;
		}

		return $parent;
	}

	private function consumeCreateThrottle(): bool {
		$uid = get_current_user_id();
		if ( $uid <= 0 ) {
			return false;
		}
		$key = 'wpps_bpc_rl_' . $uid;
		$n   = (int) get_transient( $key );
		if ( $n >= self::THROTTLE_MAX_PER_MINUTE ) {
			return false;
		}
		set_transient( $key, $n + 1, MINUTE_IN_SECONDS );

		return true;
	}

	private function verifyNonce(): void {
		if ( ! check_ajax_referer( self::NONCE_ACTION, 'nonce', false ) ) {
			wp_send_json_error( [ 'message' => __( 'Invalid security token.', 'wp-powersuite' ) ] );
		}
		if ( ! NetworkScope::canManagePlugin() ) {
			wp_send_json_error( [ 'message' => __( 'Permission denied.', 'wp-powersuite' ) ] );
		}
	}

	private function getTargetPostType(): string {
		$pt = (string) ( $this->getSettings()['post_type'] ?? 'page' );

		return in_array( $pt, [ 'page', 'post' ], true ) ? $pt : 'page';
	}

	private function assertCanEditType( string $post_type ): void {
		$pto = get_post_type_object( $post_type );
		if ( ! $pto instanceof \WP_Post_Type || ! current_user_can( $pto->cap->edit_posts ) ) {
			wp_send_json_error( [ 'message' => __( 'Permission denied.', 'wp-powersuite' ) ] );
		}
	}

	private function assertCanCreateType( string $post_type ): void {
		$pto = get_post_type_object( $post_type );
		if ( ! $pto instanceof \WP_Post_Type || ! current_user_can( $pto->cap->create_posts ) ) {
			wp_send_json_error( [ 'message' => __( 'You cannot create this post type.', 'wp-powersuite' ) ] );
		}
	}

	/**
	 * Block creation as published/private/future unless the user can publish this type.
	 */
	private function assertCanPublishType( string $post_type ): void {
		$status = (string) ( $this->getSettings()['post_status'] ?? 'publish' );
		if ( ! in_array( $status, [ 'publish', 'private', 'future' ], true ) ) {
			return;
		}
		$pto = get_post_type_object( $post_type );
		if ( ! $pto instanceof \WP_Post_Type || ! current_user_can( $pto->cap->publish_posts ) ) {
			wp_send_json_error(
				[ 'message' => __( 'You do not have permission to publish this post type. Set the status to Draft in the module settings.', 'wp-powersuite' ) ]
			);
		}
	}

	/**
	 * Resolve a safe author: only honor a different author when the user can edit others' posts.
	 */
	private function resolvePostAuthor( int $configured, int $fallback, string $post_type ): int {
		$current = get_current_user_id();
		$author  = $configured > 0 ? $configured : $fallback;

		if ( $author <= 0 ) {
			return $current;
		}
		if ( $author !== $current ) {
			$pto = get_post_type_object( $post_type );
			$cap = $pto instanceof \WP_Post_Type ? $pto->cap->edit_others_posts : 'edit_others_posts';
			if ( ! current_user_can( $cap ) ) {
				return $current;
			}
		}
		if ( ! get_userdata( $author ) ) {
			return $current;
		}

		return $author;
	}

	private function assertCanUseTemplate( int $template_id ): void {
		if ( ! current_user_can( 'edit_post', $template_id ) ) {
			wp_send_json_error( [ 'message' => __( 'You cannot use this template.', 'wp-powersuite' ) ] );
		}
	}

	private function copyPostMeta( int $template_id, int $post_id, array $row_data ): void {
		require_once __DIR__ . '/includes/class-page-builder-copy.php';

		$copier = new PageBuilderCopy(
			fn( string $content, array $row ): string => $this->substitute( $content, $row ),
			fn( array $arr, array $row ): array => $this->substituteArray( $arr, $row )
		);
		$copier->copyFromTemplate( $template_id, $post_id, $row_data );
	}

	private function substituteArray( array $arr, array $row_data ): array {
		foreach ( $arr as $k => $v ) {
			if ( is_string( $v ) ) {
				$arr[ $k ] = $this->substitute( $v, $row_data );
			} elseif ( is_array( $v ) ) {
				$arr[ $k ] = $this->substituteArray( $v, $row_data );
			}
		}

		return $arr;
	}

	private function setTaxonomyTerms( int $post_id, array $term_ids, string $post_type ): void {
		if ( $term_ids === [] ) {
			return;
		}

		$allowed_taxonomies = get_object_taxonomies( $post_type );
		if ( $allowed_taxonomies === [] ) {
			return;
		}

		$by_tax = [];
		foreach ( $term_ids as $term_id ) {
			$term = get_term( (int) $term_id );
			if ( ! $term instanceof \WP_Term || is_wp_error( $term ) ) {
				continue;
			}
			if ( ! in_array( $term->taxonomy, $allowed_taxonomies, true ) ) {
				continue;
			}
			$tax_obj = get_taxonomy( $term->taxonomy );
			if ( ! $tax_obj instanceof \WP_Taxonomy || ! current_user_can( $tax_obj->cap->assign_terms ) ) {
				continue;
			}
			$by_tax[ $term->taxonomy ]   = $by_tax[ $term->taxonomy ] ?? [];
			$by_tax[ $term->taxonomy ][] = (int) $term_id;
		}
		foreach ( $by_tax as $taxonomy => $ids ) {
			wp_set_object_terms( $post_id, $ids, $taxonomy, false );
		}
	}

	private function substitute( string $content, array $row ): string {
		foreach ( $row as $key => $value ) {
			$content = str_replace( '{' . $key . '}', (string) $value, $content );
		}

		return $content;
	}

	private function getSettings(): array {
		if ( $this->settings_cache !== null ) {
			return $this->settings_cache;
		}

		$raw = $this->moduleSettings()->all( [] );
		if ( ! is_array( $raw ) ) {
			$raw = [];
		}
		$pt = isset( $raw['post_type'] ) ? sanitize_key( (string) $raw['post_type'] ) : 'page';
		if ( ! in_array( $pt, [ 'page', 'post' ], true ) ) {
			$pt = 'page';
		}
		$st = isset( $raw['post_status'] ) ? sanitize_key( (string) $raw['post_status'] ) : 'publish';
		if ( ! in_array( $st, [ 'publish', 'draft', 'private' ], true ) ) {
			$st = 'publish';
		}

		$parent = max( 0, (int) ( $raw['parent_page'] ?? 0 ) );
		if ( $parent > 0 ) {
			$parent = $this->normalizeParentId( $parent, $pt );
		}

		$author = max( 0, (int) ( $raw['author_id'] ?? 0 ) );
		if ( $author > 0 && ! get_userdata( $author ) ) {
			$author = 0;
		}

		$this->settings_cache = [
			'post_type'   => $pt,
			'parent_page' => $parent,
			'author_id'   => $author,
			'post_status' => $st,
		];

		return $this->settings_cache;
	}

	/**
	 * @return list<string>
	 */
	public static function getAllowedPostTypes(): array {
		return [ 'page', 'post' ];
	}

	/**
	 * @return list<string>
	 */
	public static function getAllowedPostStatuses(): array {
		return [ 'publish', 'draft', 'private' ];
	}
}
