<?php
/** HTTP-Handler für direkte, größenbegrenzte Medien-Uploads. */

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

/** @return never */
function wpagent_companion_direct_media_upload_error( WP_Error $error ): void {
	$data   = $error->get_error_data();
	$status = is_array( $data ) ? (int) ( $data['status'] ?? 500 ) : 500;
	if ( $status < 400 || $status > 599 ) {
		$status = 500;
	}
	nocache_headers();
	header( 'Referrer-Policy: no-referrer' );
	header( 'X-Content-Type-Options: nosniff' );
	wp_send_json_error(
		array(
			'code'    => $error->get_error_code(),
			'message' => $error->get_error_message(),
		),
		$status
	);
}

/** @return array<string,mixed>|WP_Error */
function wpagent_companion_stream_direct_media_upload( array $record ) {
	require_once ABSPATH . 'wp-admin/includes/file.php';
	require_once ABSPATH . 'wp-admin/includes/image.php';
	require_once ABSPATH . 'wp-admin/includes/media.php';

	$source = fopen( 'php://input', 'rb' );
	if ( false === $source ) {
		return new WP_Error( 'wpagent_direct_upload_stream', __( 'Der Upload-Datenstrom konnte nicht geöffnet werden.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$temp = wp_tempnam( (string) $record['filename'] );
	if ( ! is_string( $temp ) || '' === $temp ) {
		fclose( $source );
		return new WP_Error( 'wpagent_direct_upload_temp', __( 'Für den direkten Upload konnte keine temporäre Datei angelegt werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
	}
	$target = fopen( $temp, 'wb' );
	if ( false === $target ) {
		fclose( $source );
		wp_delete_file( $temp );
		return new WP_Error( 'wpagent_direct_upload_temp', __( 'Für den direkten Upload konnte keine temporäre Datei geöffnet werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
	}

	$bytes  = 0;
	$result = null;
	try {
		while ( ! feof( $source ) ) {
			$chunk = fread( $source, 65536 );
			if ( false === $chunk ) {
				$result = new WP_Error( 'wpagent_direct_upload_read', __( 'Der direkte Upload konnte nicht vollständig gelesen werden.', 'wp-agent-companion' ), array( 'status' => 400 ) );
				break;
			}
			if ( '' === $chunk ) {
				if ( feof( $source ) ) {
					break;
				}
				$result = new WP_Error( 'wpagent_direct_upload_read', __( 'Der direkte Upload konnte nicht vollständig gelesen werden.', 'wp-agent-companion' ), array( 'status' => 400 ) );
				break;
			}
			$chunk_bytes = strlen( $chunk );
			if ( $bytes + $chunk_bytes > (int) $record['max_bytes'] ) {
				$result = new WP_Error( 'wpagent_direct_upload_too_large', __( 'Die Datei überschreitet die freigegebene Upload-Größe.', 'wp-agent-companion' ), array( 'status' => 413 ) );
				break;
			}
			$offset = 0;
			while ( $offset < $chunk_bytes ) {
				$written = fwrite( $target, substr( $chunk, $offset ) );
				if ( false === $written || 0 === $written ) {
					$result = new WP_Error( 'wpagent_direct_upload_write', __( 'Der direkte Upload konnte nicht vollständig gespeichert werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
					break 2;
				}
				$offset += $written;
			}
			$bytes += $chunk_bytes;
		}
	} catch ( Throwable $error ) {
		$result = new WP_Error( 'wpagent_direct_upload_stream', __( 'Der direkte Upload ist beim Lesen des Datenstroms fehlgeschlagen.', 'wp-agent-companion' ), array( 'status' => 500 ) );
	} finally {
		fclose( $source );
		fclose( $target );
	}
	if ( $result instanceof WP_Error ) {
		wp_delete_file( $temp );
		return $result;
	}
	if ( $bytes < 1 ) {
		wp_delete_file( $temp );
		return new WP_Error( 'wpagent_direct_upload_empty', __( 'Der direkte Upload enthält keine Datei.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}

	$attachment_id = 0;
	try {
		$allowed = get_allowed_mime_types( (int) $record['user_id'] );
		$checked = wp_check_filetype_and_ext( $temp, (string) $record['filename'], $allowed );
		if ( empty( $checked['ext'] ) || empty( $checked['type'] ) || ! hash_equals( (string) $record['expected_mime'], (string) $checked['type'] ) ) {
			return new WP_Error( 'wpagent_direct_upload_mime', __( 'Der Dateiinhalt stimmt nicht mit dem freigegebenen Dateityp überein.', 'wp-agent-companion' ), array( 'status' => 415 ) );
		}

		$attachment_id = media_handle_sideload(
			array( 'name' => (string) $record['filename'], 'tmp_name' => $temp, 'error' => 0, 'size' => $bytes ),
			(int) $record['post_id'],
			null,
			array( 'post_author' => (int) $record['user_id'] )
		);
		if ( is_wp_error( $attachment_id ) ) {
			return new WP_Error( 'wpagent_direct_upload_media', __( 'Der direkte Upload ist bei der Medienverarbeitung fehlgeschlagen.', 'wp-agent-companion' ), array( 'status' => 415 ) );
		}
		$attachment_id = (int) $attachment_id;

		if ( str_starts_with( (string) $record['expected_mime'], 'image/' ) ) {
			update_post_meta( $attachment_id, '_wp_attachment_image_alt', (string) $record['alt'] );
		}
		$attached_file = get_attached_file( $attachment_id );
		$mime_type     = (string) get_post_mime_type( $attachment_id );
		$stored_alt    = (string) get_post_meta( $attachment_id, '_wp_attachment_image_alt', true );
		$url           = (string) wp_get_attachment_url( $attachment_id );
		$verified      = is_string( $attached_file )
			&& is_file( $attached_file )
			&& $bytes === (int) filesize( $attached_file )
			&& '' !== $url
			&& hash_equals( (string) $record['expected_mime'], $mime_type )
			&& (int) $record['post_id'] === (int) get_post_field( 'post_parent', $attachment_id )
			&& (int) $record['user_id'] === (int) get_post_field( 'post_author', $attachment_id )
			&& ( ! str_starts_with( $mime_type, 'image/' ) || hash_equals( (string) $record['alt'], $stored_alt ) );
		if ( ! $verified ) {
			wp_delete_attachment( $attachment_id, true );
			$attachment_id = 0;
			return new WP_Error( 'wpagent_direct_upload_verify', __( 'Der direkte Upload konnte nach dem Speichern nicht vollständig verifiziert werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
		}

		return array(
			'attachment_id' => $attachment_id,
			'post_id'       => (int) $record['post_id'],
			'filename'      => basename( $attached_file ),
			'mime_type'     => $mime_type,
			'bytes'         => $bytes,
			'alt'           => str_starts_with( $mime_type, 'image/' ) ? $stored_alt : '',
			'url'           => $url,
			'verified'      => true,
		);
	} catch ( Throwable $error ) {
		if ( $attachment_id > 0 ) {
			wp_delete_attachment( $attachment_id, true );
		}
		return new WP_Error( 'wpagent_direct_upload_processing', __( 'Der direkte Upload ist bei der Medienverarbeitung fehlgeschlagen.', 'wp-agent-companion' ), array( 'status' => 500 ) );
	} finally {
		if ( is_file( $temp ) ) {
			wp_delete_file( $temp );
		}
	}
}

function wpagent_companion_handle_direct_media_upload(): void {
	if ( 'POST' !== strtoupper( (string) ( $_SERVER['REQUEST_METHOD'] ?? '' ) ) ) {
		header( 'Allow: POST' );
		wpagent_companion_direct_media_upload_error( new WP_Error( 'wpagent_direct_upload_method', __( 'Der direkte Upload akzeptiert ausschließlich POST-Anfragen.', 'wp-agent-companion' ), array( 'status' => 405 ) ) );
	}
	$content_type = strtolower( trim( explode( ';', (string) ( $_SERVER['CONTENT_TYPE'] ?? '' ), 2 )[0] ) );
	$encoding     = strtolower( trim( (string) ( $_SERVER['HTTP_CONTENT_ENCODING'] ?? $_SERVER['CONTENT_ENCODING'] ?? '' ) ) );
	if ( 'application/octet-stream' !== $content_type || ( '' !== $encoding && 'identity' !== $encoding ) ) {
		wpagent_companion_direct_media_upload_error( new WP_Error( 'wpagent_direct_upload_encoding', __( 'Der direkte Upload erwartet einen unveränderten application/octet-stream-Datenstrom.', 'wp-agent-companion' ), array( 'status' => 415 ) ) );
	}
	$user_raw = $_GET['user'] ?? ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- der Einmal-Header ist der Autorisierungsnachweis.
	$user_id  = is_scalar( $user_raw ) ? absint( $user_raw ) : 0;
	$token    = wpagent_companion_direct_media_upload_request_token();
	$record   = wpagent_companion_peek_direct_media_upload( $user_id, $token );
	if ( is_wp_error( $record ) ) {
		wpagent_companion_direct_media_upload_error( $record );
	}
	$length_raw = $_SERVER['CONTENT_LENGTH'] ?? null;
	if ( null !== $length_raw ) {
		if ( ! is_scalar( $length_raw ) || ! ctype_digit( (string) $length_raw ) ) {
			wpagent_companion_direct_media_upload_error( new WP_Error( 'wpagent_direct_upload_length', __( 'Die angegebene Upload-Größe ist ungültig.', 'wp-agent-companion' ), array( 'status' => 400 ) ) );
		}
		$length = (int) $length_raw;
		if ( $length < 1 ) {
			wpagent_companion_direct_media_upload_error( new WP_Error( 'wpagent_direct_upload_empty', __( 'Der direkte Upload enthält keine Datei.', 'wp-agent-companion' ), array( 'status' => 400 ) ) );
		}
		if ( $length > (int) $record['max_bytes'] ) {
			wpagent_companion_direct_media_upload_error( new WP_Error( 'wpagent_direct_upload_too_large', __( 'Die Datei überschreitet die freigegebene Upload-Größe.', 'wp-agent-companion' ), array( 'status' => 413 ) ) );
		}
	}
	$record = wpagent_companion_consume_direct_media_upload( $user_id, $token );
	if ( is_wp_error( $record ) ) {
		wpagent_companion_direct_media_upload_error( $record );
	}

	$previous_user = get_current_user_id();
	wp_set_current_user( (int) $record['user_id'] );
	if ( (int) $record['post_id'] > 0 && ( ! get_post( (int) $record['post_id'] ) || ! current_user_can( 'edit_post', (int) $record['post_id'] ) ) ) {
		wp_set_current_user( $previous_user );
		wpagent_companion_direct_media_upload_error( new WP_Error( 'wpagent_direct_upload_parent_forbidden', __( 'Der Eltern-Beitrag ist nicht mehr verfügbar oder bearbeitbar.', 'wp-agent-companion' ), array( 'status' => 403 ) ) );
	}
	try {
		$result = wpagent_companion_stream_direct_media_upload( $record );
	} catch ( Throwable $error ) {
		$result = new WP_Error( 'wpagent_direct_upload_processing', __( 'Der direkte Upload ist bei der Medienverarbeitung fehlgeschlagen.', 'wp-agent-companion' ), array( 'status' => 500 ) );
	} finally {
		wp_set_current_user( $previous_user );
	}
	if ( is_wp_error( $result ) ) {
		wpagent_companion_direct_media_upload_error( $result );
	}
	nocache_headers();
	header( 'Referrer-Policy: no-referrer' );
	header( 'X-Content-Type-Options: nosniff' );
	wp_send_json_success( $result, 201 );
}
add_action( 'admin_post_nopriv_wpagently_direct_media_upload', 'wpagent_companion_handle_direct_media_upload' );
add_action( 'admin_post_wpagently_direct_media_upload', 'wpagent_companion_handle_direct_media_upload' );
