<?php
/**
 * Notion Payload Transformer
 *
 * Transforms form data to Notion API format.
 *
 * @package SureForms
 * @since 2.4.0
 */

namespace SRFM_Pro\Inc\Pro\Native_Integrations\Transformers;

use SRFM\Inc\Helper;
use SRFM_Pro\Inc\Pro\Native_Integrations\Interfaces\Payload_Transformer;
use SRFM_Pro\Inc\Pro\Native_Integrations\Services\Workflow_Processor;

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly.
}

/**
 * Notion Transformer class.
 *
 * @since 2.4.0
 */
class Notion_Transformer implements Payload_Transformer {
	/**
	 * Transform payload for Notion integration
	 *
	 * @param array $form_data Original form data.
	 * @param array $field_mappings Field mappings configuration.
	 * @param array $context Additional context data (workflow, action_config).
	 * @return array Transformed payload.
	 * @since 2.4.0
	 */
	public function transform( $form_data, $field_mappings = [], $context = [] ) {
		// Get action ID from context.
		$action_id = $context['action_config']['id'] ?? '';

		// Route to appropriate transformer based on action.
		switch ( $action_id ) {
			case 'create_page':
				return $this->transform_create_page( $form_data, $field_mappings, $context );

			case 'add_database_row':
			default:
				return $this->transform_add_database_row( $form_data, $field_mappings, $context );
		}
	}

	/**
	 * Prepare test payload with sample data
	 *
	 * @param array $workflow_fields Fields from the workflow configuration.
	 * @param array $field_definitions Field definitions from the action configuration.
	 * @return array Test payload.
	 * @since 2.4.0
	 */
	public function prepare_test_payload( $workflow_fields, $field_definitions ) {
		$payload = [];

		// Generate test payload based on field definitions.
		foreach ( $field_definitions as $field_def ) {
			$field_key = $field_def['key'] ?? '';

			if ( empty( $field_key ) ) {
				continue;
			}

			// Skip fields that are excluded from payload.
			$exclude_from_payload = $field_def['exclude_from_payload'] ?? false;
			if ( $exclude_from_payload ) {
				continue;
			}

			// Get field value or generate test data.
			if ( isset( $workflow_fields[ $field_key ] ) && ! empty( $workflow_fields[ $field_key ] ) ) {
				$payload[ $field_key ] = $workflow_fields[ $field_key ];
			} else {
				$payload[ $field_key ] = Workflow_Processor::generate_test_value( $field_def );
			}
		}

		// Apply the same transformation logic as the main transform method.
		// We need to determine the action from the field definitions.
		$action_id = $this->get_action_from_field_definitions( $field_definitions );

		// Use the same transformation logic as the main method.
		$context = [ 'action_config' => [ 'id' => $action_id ] ];
		return $this->transform( $payload, [], $context );
	}

	/**
	 * Get integration name this transformer handles
	 *
	 * @return string Integration name.
	 * @since 2.4.0
	 */
	public function get_integration_name() {
		return 'notion';
	}

	/**
	 * Determine action type from field definitions
	 *
	 * @param array $field_definitions Field definitions from action config.
	 * @return string Action ID.
	 * @since 2.4.0
	 */
	private function get_action_from_field_definitions( $field_definitions ) {
		// Check for page-specific fields to identify create_page action.
		foreach ( $field_definitions as $field_def ) {
			$field_key = $field_def['key'] ?? '';
			if ( in_array( $field_key, [ 'parent_page_id', 'page_title', 'page_content' ], true ) ) {
				return 'create_page';
			}
		}

		// Default to database row action.
		return 'add_database_row';
	}

	/**
	 * Transform data for Create Page action
	 *
	 * @param array $form_data Original form data.
	 * @param array $field_mappings Field mappings configuration.
	 * @param array $context Additional context data.
	 * @return array Transformed payload for creating a page.
	 * @since 2.4.0
	 */
	private function transform_create_page( $form_data, $field_mappings, $context ) {
		$payload = [];

		// Set up parent relationship.
		if ( ! empty( $form_data['parent_page_id'] ) ) {
			// Use parent page if provided.
			$parent_page_id = $form_data['parent_page_id'];
			// Ensure UUID format without hyphens for Notion API.
			$parent_page_id    = str_replace( '-', '', $parent_page_id );
			$payload['parent'] = [
				'page_id' => $parent_page_id,
			];
		} else {
			// Default to workspace as parent.
			$payload['parent'] = [
				'workspace' => true,
			];
		}

		// Build properties for the page.
		$properties = $this->build_page_properties( $form_data, $field_mappings, $context );

		// Add title property (required for pages).
		if ( ! empty( $form_data['page_title'] ) ) {
			$properties['title'] = $this->format_title_property( $form_data['page_title'] );
		}

		$payload['properties'] = $properties;

		// Add content blocks if provided.
		if ( ! empty( $form_data['page_content'] ) ) {
			$payload['children'] = $this->build_content_blocks( $form_data['page_content'] );
		}

		return $payload;
	}

	/**
	 * Transform data for Add Database Row action
	 *
	 * @param array $form_data Original form data.
	 * @param array $field_mappings Field mappings configuration.
	 * @param array $context Additional context data.
	 * @return array Transformed payload for adding a database row.
	 * @since 2.4.0
	 */
	private function transform_add_database_row( $form_data, $field_mappings, $context ) {
		$payload = [];

		// Set data source as parent.
		$payload['parent'] = [
			'type'           => 'data_source_id',
			'data_source_id' => $form_data['data_source_id'] ?? '',
		];

		// Build properties for the database row.
		$properties = $this->build_database_properties( $form_data, $field_mappings, $context );

		$payload['properties'] = $properties;

		return $payload;
	}

	/**
	 * Build properties for a page
	 *
	 * @param array $form_data Form data.
	 * @param array $field_mappings Field mappings.
	 * @param array $context Context data.
	 * @return array Properties array.
	 * @since 2.4.0
	 */
	private function build_page_properties( $form_data, $field_mappings, $context ) {
		// For pages, we typically just need basic properties.
		// Additional properties can be added based on form fields.
		$exclude_fields = [
			'parent_page_id',
			'page_title',
			'page_content',
		];

		return $this->build_properties( $form_data, $field_mappings, $context, $exclude_fields );
	}

	/**
	 * Build properties for a database row
	 *
	 * @param array $form_data Form data.
	 * @param array $field_mappings Field mappings.
	 * @param array $context Context data.
	 * @return array Properties array.
	 * @since 2.4.0
	 */
	private function build_database_properties( $form_data, $field_mappings, $context ) {
		// For database rows, exclude only system fields.
		$exclude_fields = [
			'data_source_id',
		];

		// Get database schema for proper property formatting.
		$database_properties = $this->get_database_properties( $form_data, $context );

		return $this->build_properties( $form_data, $field_mappings, $context, $exclude_fields, $database_properties );
	}

	/**
	 * Build properties from form data
	 *
	 * @param array $form_data Form data.
	 * @param array $field_mappings Field mappings.
	 * @param array $context Context data.
	 * @param array $exclude_fields Fields to exclude.
	 * @param array $database_properties Database schema properties.
	 * @return array Properties array.
	 * @since 2.4.0
	 */
	private function build_properties( $form_data, $field_mappings, $context, $exclude_fields = [], $database_properties = [] ) {
		$properties = [];

		// Create a reverse mapping from decoded property IDs to encoded property IDs.
		$property_id_mapping = [];
		foreach ( $database_properties as $encoded_id => $property_data ) {
			$decoded_id                         = rawurldecode( $encoded_id );
			$property_id_mapping[ $decoded_id ] = $encoded_id;

			// Also create a mapping for the base property name (without special chars).
			$base_id = preg_replace( '/[^a-zA-Z0-9_-]/', '', $decoded_id );
			if ( $base_id !== $decoded_id ) {
				$property_id_mapping[ $base_id ] = $encoded_id;
			}
		}

		// If no database properties were found, we need to URL-encode the property IDs.
		// This is a fallback for when database schema isn't available in context.
		if ( empty( $property_id_mapping ) ) {
			foreach ( $form_data as $field_key => $field_value ) {
				if ( ! in_array( $field_key, array_merge( $exclude_fields, [ 'form_id', 'form_title', 'integration_id' ] ), true ) ) {
					// For Notion property IDs, decode any HTML entities from WordPress meta.
					$clean_field_key                   = html_entity_decode( $field_key, ENT_QUOTES, 'UTF-8' );
					$property_id_mapping[ $field_key ] = $clean_field_key;
				}
			}
		}

		// Add common system fields to exclude.
		$exclude_fields = array_merge(
			$exclude_fields,
			[
				'form_id',
				'form_title',
				'integration_id',
			]
		);

		foreach ( $form_data as $field_key => $field_value ) {
			// Skip excluded system fields.
			if ( in_array( $field_key, $exclude_fields, true ) ) {
				continue;
			}

			// Skip empty values - Notion doesn't accept empty properties.
			if ( empty( $field_value ) && '0' !== $field_value && false !== $field_value ) {
				continue;
			}

			// Skip whitespace-only strings.
			if ( is_string( $field_value ) && '' === trim( $field_value ) ) {
				continue;
			}

			// Decode HTML entities from field key (WordPress meta may HTML-encode property IDs).
			$clean_field_key = html_entity_decode( $field_key, ENT_QUOTES, 'UTF-8' );

			// Get the correct property ID for Notion API.
			$encoded_property_id = $property_id_mapping[ $clean_field_key ] ?? $property_id_mapping[ $field_key ] ?? $clean_field_key;

			// Get the expected property type.
			$expected_type = $database_properties[ $encoded_property_id ]['type'] ?? $field_mappings[ $field_key ]['type'] ?? null;

			// If no type found, try to get from context.
			if ( ! $expected_type && isset( $context['dynamic_fields'][ $field_key ]['type'] ) ) {
				$expected_type = $context['dynamic_fields'][ $field_key ]['type'];
			}

			// Transform based on expected type.
			$property_value = $this->transform_property_by_type( $field_key, $field_value, $expected_type );

			if ( null !== $property_value ) {
				$properties[ $encoded_property_id ] = $property_value;
			}
		}

		return $properties;
	}

	/**
	 * Build content blocks for a page
	 *
	 * @param string $content Content to add to the page.
	 * @return array Array of content blocks.
	 * @since 2.4.0
	 */
	private function build_content_blocks( $content ) {
		// Split content by double newlines to create multiple paragraphs.
		$paragraphs = explode( "\n\n", $content );
		$blocks     = [];

		foreach ( $paragraphs as $paragraph ) {
			if ( ! empty( trim( $paragraph ) ) ) {
				$blocks[] = [
					'object'    => 'block',
					'type'      => 'paragraph',
					'paragraph' => [
						'rich_text' => [
							[
								'type' => 'text',
								'text' => [
									'content' => $paragraph,
								],
							],
						],
					],
				];
			}
		}

		// If no paragraphs were created, create a single block with all content.
		if ( empty( $blocks ) && ! empty( $content ) ) {
			$blocks[] = [
				'object'    => 'block',
				'type'      => 'paragraph',
				'paragraph' => [
					'rich_text' => [
						[
							'type' => 'text',
							'text' => [
								'content' => $content,
							],
						],
					],
				],
			];
		}

		return $blocks;
	}

	/**
	 * Transform property value based on expected Notion type from database schema
	 *
	 * @param string $field_key Field key.
	 * @param mixed  $field_value Field value.
	 * @param string $expected_type Expected Notion property type from database schema.
	 * @return array|null Transformed property value or null if invalid.
	 * @since 2.4.0
	 */
	private function transform_property_by_type( $field_key, $field_value, $expected_type ) {
		// Unset unused parameter to avoid warning.
		unset( $field_key );
		$field_value_str = Helper::get_string_value( $field_value );

		// Transform based on expected Notion property type.
		switch ( $expected_type ) {
			case 'title':
				return $this->format_title_property( $field_value_str );

			case 'rich_text':
				return $this->format_rich_text_property( $field_value_str );

			case 'email':
				return $this->format_email_property( $field_value_str );

			case 'phone_number':
				return $this->format_phone_property( $field_value_str );

			case 'number':
				return $this->format_number_property( $field_value_str );

			case 'date':
				return $this->format_date_property( $field_value_str );

			case 'checkbox':
				return $this->format_checkbox_property( $field_value );

			case 'url':
				return $this->format_url_property( $field_value_str );

			case 'select':
				return $this->format_select_property( $field_value_str );

			case 'multi_select':
				return $this->format_multi_select_property( $field_value_str );

			case 'status':
				return $this->format_status_property( $field_value_str );

			case 'people':
				return $this->format_people_property( $field_value_str );

			case 'files':
				return $this->format_files_property( $field_value_str );

			default:
				// Fallback to rich text for unknown types.
				return $this->format_rich_text_property( $field_value_str );
		}
	}

	/**
	 * Format title property
	 *
	 * @param string $value Field value.
	 * @return array Notion title property.
	 * @since 2.4.0
	 */
	private function format_title_property( $value ) {
		return [
			'type'  => 'title',
			'title' => [
				[
					'type' => 'text',
					'text' => [
						'content' => substr( $value, 0, 2000 ), // Notion limit.
					],
				],
			],
		];
	}

	/**
	 * Format rich text property
	 *
	 * @param string $value Field value.
	 * @return array Notion rich text property.
	 * @since 2.4.0
	 */
	private function format_rich_text_property( $value ) {
		return [
			'type'      => 'rich_text',
			'rich_text' => [
				[
					'type' => 'text',
					'text' => [
						'content' => substr( $value, 0, 2000 ), // Notion limit.
					],
				],
			],
		];
	}

	/**
	 * Format email property
	 *
	 * @param string $value Email value.
	 * @return array Notion email property or null if invalid.
	 * @since 2.4.0
	 */
	private function format_email_property( $value ) {
		if ( ! is_email( $value ) ) {
			// Fallback to rich text if not a valid email.
			return $this->format_rich_text_property( $value );
		}

		return [
			'type'  => 'email',
			'email' => substr( $value, 0, 200 ), // Notion limit.
		];
	}

	/**
	 * Format phone number property
	 *
	 * @param string $value Phone value.
	 * @return array Notion phone number property.
	 * @since 2.4.0
	 */
	private function format_phone_property( $value ) {
		return [
			'type'         => 'phone_number',
			'phone_number' => substr( $value, 0, 200 ), // Notion limit.
		];
	}

	/**
	 * Format number property
	 *
	 * @param string $value Number value.
	 * @return array|null Notion number property or null if invalid.
	 * @since 2.4.0
	 */
	private function format_number_property( $value ) {
		if ( ! is_numeric( $value ) ) {
			// Return null to exclude invalid numbers from payload.
			return null;
		}

		return [
			'type'   => 'number',
			'number' => floatval( $value ),
		];
	}

	/**
	 * Format date property
	 *
	 * @param string $value Date value.
	 * @return array|null Notion date property or null if invalid.
	 * @since 2.4.0
	 */
	private function format_date_property( $value ) {
		// Try to parse the date.
		$timestamp = strtotime( $value );

		if ( false === $timestamp ) {
			// Return null to exclude invalid dates from payload.
			return null;
		}

		return [
			'type' => 'date',
			'date' => [
				'start' => gmdate( 'Y-m-d', $timestamp ),
			],
		];
	}

	/**
	 * Format checkbox property
	 *
	 * @param mixed $value Checkbox value.
	 * @return array Notion checkbox property.
	 * @since 2.4.0
	 */
	private function format_checkbox_property( $value ) {
		// Convert various truthy values to boolean.
		$is_checked = in_array( strtolower( Helper::get_string_value( $value ) ), [ '1', 'true', 'yes', 'on', 'checked' ], true ) || true === $value;

		return [
			'type'     => 'checkbox',
			'checkbox' => $is_checked,
		];
	}

	/**
	 * Format URL property
	 *
	 * @param string $value URL value (may contain HTML).
	 * @return array|null Notion URL property or null if invalid.
	 * @since 2.4.0
	 */
	private function format_url_property( $value ) {
		// Extract URL from HTML if present.
		$clean_url = $this->extract_url_from_html( $value );

		if ( ! filter_var( $clean_url, FILTER_VALIDATE_URL ) ) {
			// Return null to exclude invalid URLs from payload.
			return null;
		}

		return [
			'type' => 'url',
			'url'  => substr( $clean_url, 0, 2000 ), // Notion limit.
		];
	}

	/**
	 * Format select property
	 *
	 * @param string $value Select value.
	 * @return array Notion select property.
	 * @since 2.4.0
	 */
	private function format_select_property( $value ) {
		return [
			'type'   => 'select',
			'select' => [
				'name' => Helper::get_string_value( $value ),
			],
		];
	}

	/**
	 * Format multi-select property
	 *
	 * @param string $value Multi-select value (comma or <br> separated).
	 * @return array Notion multi-select property.
	 * @since 2.4.0
	 */
	private function format_multi_select_property( $value ) {
		// Clean HTML and handle different separators.
		$clean_value = str_replace( [ '<br>', '<br/>', '<br />' ], ',', $value );
		$clean_value = wp_strip_all_tags( $clean_value );

		$options = array_map( 'trim', explode( ',', $clean_value ) );
		$options = array_filter( $options ); // Remove empty values.

		$multi_select = [];
		foreach ( $options as $option ) {
			if ( ! empty( $option ) ) {
				$multi_select[] = [
					'name' => Helper::get_string_value( $option ),
				];
			}
		}

		return [
			'type'         => 'multi_select',
			'multi_select' => $multi_select,
		];
	}

	/**
	 * Format status property
	 *
	 * @param string $value Status value.
	 * @return array|null Notion status property or null if empty.
	 * @since 2.4.0
	 */
	private function format_status_property( $value ) {
		$status_value = trim( Helper::get_string_value( $value ) );

		// Status cannot be empty - return null to exclude from payload.
		if ( empty( $status_value ) ) {
			return null;
		}

		return [
			'type'   => 'status',
			'status' => [
				'name' => $status_value,
			],
		];
	}

	/**
	 * Format people property
	 *
	 * @param string $value People value (email, user ID, or comma-separated list).
	 * @return array Notion people property.
	 * @since 2.4.0
	 */
	private function format_people_property( $value ) {
		if ( empty( $value ) ) {
			return [
				'type'   => 'people',
				'people' => [],
			];
		}

		$people = [];
		// Support comma-separated values for multiple people.
		$values = array_map( 'trim', explode( ',', $value ) );

		foreach ( $values as $person ) {
			if ( empty( $person ) ) {
				continue;
			}

			// Check if it's an email.
			if ( is_email( $person ) ) {
				// Add as email (Notion will try to match to existing users).
				$people[] = [
					'object' => 'user',
					'person' => [
						'email' => $person,
					],
				];
			} elseif ( preg_match( '/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i', $person ) ) {
				// Check if it's a UUID (user ID format).
				$people[] = [
					'object' => 'user',
					'id'     => $person,
				];
			}
			// Skip invalid values.
		}

		return [
			'type'   => 'people',
			'people' => $people,
		];
	}

	/**
	 * Format files property
	 *
	 * @param string $value File URLs (comma-separated if multiple, may contain HTML).
	 * @return array Notion files property.
	 * @since 2.4.0
	 */
	private function format_files_property( $value ) {
		if ( empty( $value ) ) {
			return [
				'type'  => 'files',
				'files' => [],
			];
		}

		$files = [];
		// Support comma-separated URLs for multiple files.
		$urls = array_map( 'trim', explode( ',', $value ) );

		foreach ( $urls as $url ) {
			if ( empty( $url ) ) {
				continue;
			}

			// Extract URL from HTML if present.
			$clean_url = $this->extract_url_from_html( $url );

			// Validate URL.
			if ( filter_var( $clean_url, FILTER_VALIDATE_URL ) ) {
				$files[] = [
					'type'     => 'external',
					'name'     => basename( $clean_url ), // Use filename as name.
					'external' => [
						'url' => $clean_url,
					],
				];
			}
		}

		return [
			'type'  => 'files',
			'files' => $files,
		];
	}

	/**
	 * Get database properties from Notion API or context
	 *
	 * @param array $form_data Form data containing database_id.
	 * @param array $context Context data that might contain database properties.
	 * @return array Database properties with field types.
	 * @since 2.4.0
	 */
	private function get_database_properties( $form_data, $context ) {
		$properties = [];

		// First check if database properties are available in the context.
		if ( ! empty( $context['database_properties'] ) ) {
			return $context['database_properties'];
		}

		// If we have the necessary context data, try to fetch properties.
		$entity_id = $form_data['data_source_id'] ?? $form_data['database_id'] ?? '';
		if ( ! empty( $entity_id ) && ! empty( $context['credentials'] ) ) {
			// Make direct API call to Notion to get data source schema.
			$url     = 'https://api.notion.com/v1/data_sources/' . $entity_id;
			$headers = [
				'Authorization'  => 'Bearer ' . ( $context['credentials']['integration_token'] ?? '' ),
				'Notion-Version' => '2025-09-03',
				'Content-Type'   => 'application/json',
			];

			$response = wp_remote_get(
				$url,
				[
					'headers' => $headers,
					'timeout' => 30,
				]
			);

			if ( ! is_wp_error( $response ) && 200 === wp_remote_retrieve_response_code( $response ) ) {
				$body = wp_remote_retrieve_body( $response );
				$data = json_decode( $body, true );

				if ( is_array( $data ) && ! empty( $data['properties'] ) ) {
					// Process data source properties.
					foreach ( $data['properties'] as $property_name => $property_data ) {
						$property_id                = $property_data['id'] ?? $property_name;
						$property_type              = $property_data['type'] ?? 'rich_text';
						$properties[ $property_id ] = [ 'type' => $property_type ];
					}
				}
			}
		}

		return $properties;
	}

	/**
	 * Extract URL from HTML content
	 *
	 * @param string $value Potentially HTML-wrapped value.
	 * @return string Clean URL or original value.
	 * @since 2.4.0
	 */
	private function extract_url_from_html( $value ) {
		// Check for HTML anchor tag pattern: <a ...href="URL"...>text</a>.
		if ( preg_match( '/<a[^>]*\s+href="([^"]+)"[^>]*>/', $value, $matches ) ) {
			// Unescape forward slashes and return clean URL.
			return str_replace( '\/', '/', $matches[1] );
		}

		// If no HTML found, return original value with unescaped slashes.
		return str_replace( '\/', '/', $value );
	}
}
