<?php
/**
 * TDD spec for the ppok/post/get-blocks ability.
 *
 * @package Ppok\AIToolkit\Tests
 * @license GPL-2.0-or-later
 *
 * Phase 2b: thin wrapper around parse_blocks(). Cap: edit_post per ID.
 */

namespace Ppok\AIToolkit\Tests;

use Ppok\AIToolkit\Abilities\Post_Get_Blocks;

class Test_Post_Get_Blocks extends \WP_UnitTestCase {

	public function test_slug_constant(): void {
		$this->assertSame( 'ppok/get-post-blocks', Post_Get_Blocks::SLUG );
	}

	public function test_descriptor_has_required_keys(): void {
		$d = Post_Get_Blocks::descriptor();
		foreach ( array( 'label', 'description', 'input_schema', 'output_schema', 'permission_callback', 'execute_callback', 'meta' ) as $key ) {
			$this->assertArrayHasKey( $key, $d, "Descriptor missing required key: {$key}" );
		}
	}

	public function test_descriptor_marks_read_only_and_mcp_public(): void {
		$d = Post_Get_Blocks::descriptor();
		$this->assertFalse( $d['meta']['ppok']['write'], 'Get-blocks must be read-only.' );
		$this->assertTrue( $d['meta']['mcp']['public'], 'Get-blocks must be exposed to MCP.' );
	}

	public function test_input_schema_requires_post_id(): void {
		$schema = Post_Get_Blocks::descriptor()['input_schema'];
		$this->assertContains( 'post_id', $schema['required'] );
		$this->assertSame( 'integer', $schema['properties']['post_id']['type'] );
	}

	public function test_permission_denied_for_subscriber(): void {
		$post_id    = self::factory()->post->create( array( 'post_status' => 'publish' ) );
		$subscriber = self::factory()->user->create( array( 'role' => 'subscriber' ) );
		wp_set_current_user( $subscriber );

		$this->assertFalse( Post_Get_Blocks::permission_callback( array( 'post_id' => $post_id ) ) );
	}

	public function test_permission_granted_for_editor(): void {
		$post_id = self::factory()->post->create( array( 'post_status' => 'publish' ) );
		$editor  = self::factory()->user->create( array( 'role' => 'editor' ) );
		wp_set_current_user( $editor );

		$this->assertTrue( Post_Get_Blocks::permission_callback( array( 'post_id' => $post_id ) ) );
	}

	public function test_permission_denied_for_invalid_post_id(): void {
		$editor = self::factory()->user->create( array( 'role' => 'editor' ) );
		wp_set_current_user( $editor );

		$this->assertFalse( Post_Get_Blocks::permission_callback( array( 'post_id' => 0 ) ) );
		$this->assertFalse( Post_Get_Blocks::permission_callback( array() ) );
	}

	public function test_returns_wp_error_when_post_does_not_exist(): void {
		$result = Post_Get_Blocks::execute_callback( array( 'post_id' => 999999999 ) );

		$this->assertInstanceOf( \WP_Error::class, $result );
		$this->assertSame( 'ppok_post_not_found', $result->get_error_code() );
		$this->assertSame( 404, $result->get_error_data()['status'] );
	}

	public function test_returns_parsed_blocks_for_block_content(): void {
		$content = "<!-- wp:paragraph -->\n<p>Hello block world.</p>\n<!-- /wp:paragraph -->\n\n<!-- wp:heading -->\n<h2 class=\"wp-block-heading\">Section title</h2>\n<!-- /wp:heading -->";
		$post_id = self::factory()->post->create(
			array(
				'post_status'  => 'publish',
				'post_content' => $content,
			)
		);

		$result = Post_Get_Blocks::execute_callback( array( 'post_id' => $post_id ) );

		$this->assertIsArray( $result );
		$this->assertSame( $post_id, $result['post_id'] );
		$this->assertSame( 'publish', $result['post_status'] );
		$this->assertSame( 'post', $result['post_type'] );
		$this->assertIsArray( $result['blocks'] );

		// parse_blocks may include null-name "blocks" for whitespace between
		// real blocks; filter to the named ones for the assertion.
		$named = array_values(
			array_filter(
				$result['blocks'],
				static fn( $b ) => isset( $b['blockName'] ) && $b['blockName'] !== null
			)
		);
		$this->assertCount( 2, $named );
		$this->assertSame( 'core/paragraph', $named[0]['blockName'] );
		$this->assertSame( 'core/heading', $named[1]['blockName'] );
	}

	public function test_returns_block_tree_with_nesting(): void {
		$content = "<!-- wp:group -->\n<div class=\"wp-block-group\"><!-- wp:paragraph -->\n<p>Inside group.</p>\n<!-- /wp:paragraph --></div>\n<!-- /wp:group -->";
		$post_id = self::factory()->post->create(
			array(
				'post_status'  => 'publish',
				'post_content' => $content,
			)
		);

		$result = Post_Get_Blocks::execute_callback( array( 'post_id' => $post_id ) );
		$named  = array_values(
			array_filter(
				$result['blocks'],
				static fn( $b ) => isset( $b['blockName'] ) && $b['blockName'] !== null
			)
		);

		$this->assertSame( 'core/group', $named[0]['blockName'] );
		$this->assertIsArray( $named[0]['innerBlocks'] );
		$this->assertCount( 1, $named[0]['innerBlocks'] );
		$this->assertSame( 'core/paragraph', $named[0]['innerBlocks'][0]['blockName'] );
	}

	public function test_returns_blocks_for_classic_content(): void {
		// Pre-Gutenberg / classic content has no block delimiters. parse_blocks
		// returns a single "block" with blockName === null wrapping the raw HTML.
		$post_id = self::factory()->post->create(
			array(
				'post_status'  => 'publish',
				'post_content' => '<p>Just a plain paragraph.</p>',
			)
		);

		$result = Post_Get_Blocks::execute_callback( array( 'post_id' => $post_id ) );

		$this->assertIsArray( $result['blocks'] );
		$this->assertNotEmpty( $result['blocks'] );
		$first = $result['blocks'][0];
		$this->assertNull( $first['blockName'] );
		$this->assertStringContainsString( 'Just a plain paragraph', $first['innerHTML'] );
	}
}
