<?php
/**
 * CSV import/export — free, working, no row caps (all three are paywalled
 * or broken in the incumbents).
 *
 * Export columns: source,target,type,active,hits
 * Import accepts our export plus common variants ("url,redirect_to",
 * "from,to", with or without a header row).
 *
 * @package RedirectZen
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class RDZ_Csv {

	/**
	 * Hooks.
	 */
	public static function init() {
		add_action( 'admin_post_rdz_export_csv', array( __CLASS__, 'handle_export' ) );
		add_action( 'admin_post_rdz_import_csv', array( __CLASS__, 'handle_import' ) );
	}

	/**
	 * Streams all redirects as a CSV download.
	 */
	public static function handle_export() {
		if ( ! current_user_can( RDZ_Admin::CAPABILITY ) ) {
			wp_die( esc_html__( 'You are not allowed to do that.', 'redirectzen-301-redirect-manager' ) );
		}
		check_admin_referer( 'rdz_export_csv' );

		global $wpdb;
		$rows = $wpdb->get_results( 'SELECT source, target, redirect_type, is_active, hits, is_regex FROM ' . RDZ_Redirects::table() . ' ORDER BY id ASC', ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching

		nocache_headers();
		header( 'Content-Type: text/csv; charset=utf-8' );
		header( 'Content-Disposition: attachment; filename=redirectzen-export-' . gmdate( 'Y-m-d' ) . '.csv' );

		$out = fopen( 'php://output', 'w' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen
		fputcsv( $out, array( 'source', 'target', 'type', 'active', 'hits', 'regex' ) );
		foreach ( (array) $rows as $row ) {
			fputcsv( $out, array( $row['source'], $row['target'], $row['redirect_type'], $row['is_active'], $row['hits'], $row['is_regex'] ) );
		}
		fclose( $out ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
		exit;
	}

	/**
	 * Handles a CSV upload.
	 */
	public static function handle_import() {
		if ( ! current_user_can( RDZ_Admin::CAPABILITY ) ) {
			wp_die( esc_html__( 'You are not allowed to do that.', 'redirectzen-301-redirect-manager' ) );
		}
		check_admin_referer( 'rdz_import_csv' );

		// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- tmp_name is a server path, validated below.
		$file = isset( $_FILES['rdz_csv']['tmp_name'] ) ? $_FILES['rdz_csv']['tmp_name'] : '';
		if ( ! $file || ! is_uploaded_file( $file ) ) {
			self::finish( array( 'imported' => 0, 'skipped' => 0, 'error' => 'nofile' ) );
		}

		$handle = fopen( $file, 'r' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen
		if ( ! $handle ) {
			self::finish( array( 'imported' => 0, 'skipped' => 0, 'error' => 'unreadable' ) );
		}

		$rows = array();
		while ( false !== ( $line = fgetcsv( $handle ) ) ) { // phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition
			$rows[] = $line;
		}
		fclose( $handle ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose

		$parsed = self::parse_rows( $rows );
		if ( empty( $parsed ) ) {
			self::finish( array( 'imported' => 0, 'skipped' => 0, 'error' => 'empty' ) );
		}

		RDZ_Backups::snapshot( __( 'Before CSV import', 'redirectzen-301-redirect-manager' ) );

		$imported = 0;
		$skipped  = 0;
		foreach ( $parsed as $entry ) {
			$result = RDZ_Redirects::add_rule( $entry['source'], $entry['target'], $entry['type'], array( 'queue_verify' => true ) );
			if ( is_wp_error( $result ) ) {
				$skipped++;
			} else {
				$imported++;
				if ( ! $entry['active'] ) {
					RDZ_Redirects::update_rule( (int) $result['id'], array( 'is_active' => 0 ) );
				}
			}
		}

		self::finish( array( 'imported' => $imported, 'skipped' => $skipped ) );
	}

	/**
	 * Turns raw CSV rows into import entries. Pure logic, unit tested.
	 *
	 * Column detection: a header row naming any of source/url/from +
	 * target/redirect_to/to/destination is used when present; otherwise
	 * columns 0/1 are taken as source/target (+2 type, +3 active).
	 *
	 * @param array[] $rows Raw fgetcsv rows.
	 * @return array[] { source, target, type, active }
	 */
	public static function parse_rows( $rows ) {
		if ( empty( $rows ) ) {
			return array();
		}

		$source_names = array( 'source', 'url', 'from', 'request', 'old url', 'old' );
		$target_names = array( 'target', 'redirect_to', 'to', 'destination', 'new url', 'new' );

		$map    = array( 'source' => 0, 'target' => 1, 'type' => 2, 'active' => 3 );
		$header = array_map( static function ( $cell ) {
			return strtolower( trim( (string) $cell ) );
		}, (array) $rows[0] );

		$has_header = false;
		foreach ( $header as $index => $cell ) {
			if ( in_array( $cell, $source_names, true ) ) {
				$map['source'] = $index;
				$has_header    = true;
			} elseif ( in_array( $cell, $target_names, true ) ) {
				$map['target'] = $index;
				$has_header    = true;
			} elseif ( in_array( $cell, array( 'type', 'code', 'status' ), true ) ) {
				$map['type'] = $index;
			} elseif ( in_array( $cell, array( 'active', 'enabled' ), true ) ) {
				$map['active'] = $index;
			}
		}

		if ( $has_header ) {
			array_shift( $rows );
		}

		$entries = array();
		foreach ( $rows as $row ) {
			$row    = (array) $row;
			$source = isset( $row[ $map['source'] ] ) ? trim( (string) $row[ $map['source'] ] ) : '';
			$target = isset( $row[ $map['target'] ] ) ? trim( (string) $row[ $map['target'] ] ) : '';
			if ( '' === $source || '' === $target ) {
				continue;
			}

			$type = isset( $row[ $map['type'] ] ) ? (int) $row[ $map['type'] ] : 301;
			if ( ! in_array( $type, array( 301, 302, 307 ), true ) ) {
				$type = 301;
			}

			$active = true;
			if ( isset( $row[ $map['active'] ] ) && '' !== trim( (string) $row[ $map['active'] ] ) ) {
				$active = in_array( strtolower( trim( (string) $row[ $map['active'] ] ) ), array( '1', 'yes', 'true', 'enabled', 'active' ), true );
			}

			$entries[] = array(
				'source' => $source,
				'target' => $target,
				'type'   => $type,
				'active' => $active,
			);
		}

		return $entries;
	}

	/**
	 * Redirects back to the import screen with result args.
	 *
	 * @param array $args Query args.
	 */
	private static function finish( $args ) {
		wp_safe_redirect(
			add_query_arg(
				array_merge( array( 'page' => 'redirectzen-import', 'rdz_notice' => 'csv_done' ), $args ),
				admin_url( 'admin.php' )
			)
		);
		exit;
	}
}
