<?php

namespace Limb_Chatbot\Includes\Services\Actions\Executors;

use Exception;
use Limb_Chatbot\Includes\Integrations\Slack\Services\Slack_Bot_Message_Builder;
use Limb_Chatbot\Includes\Data_Objects\Action_Callback;
use Limb_Chatbot\Includes\Data_Objects\Config;
use Limb_Chatbot\Includes\Exceptions\Error_Codes;
use Limb_Chatbot\Includes\Exceptions\Exception as Custom_Exception;
use Limb_Chatbot\Includes\Integrations\Slack\Slack;
use Limb_Chatbot\Includes\Integrations\Slack\Utilities\Channels_Utility;
use Limb_Chatbot\Includes\Services\Actions\Action_Callback_Execution_Context;
use Limb_Chatbot\Includes\Services\User_Manager;
use Limb_Chatbot\Includes\Utilities\Slack_Channels_Utility;

/**
 * Slack Callback Executor
 *
 * Executes Slack callbacks by sending beautifully formatted messages to Slack channels.
 * Uses config-based approach with Slack API integration instead of webhooks.
 * Automatically includes user information and submitted parameters in messages.
 *
 * @package Limb_Chatbot\Includes\Services\Actions\Executors
 * @since 1.0.0
 */
class Slack_Callback_Executor extends Abstract_Callback_Executor {

	/**
	 * Get callback type.
	 *
	 * @return string
	 * @since 1.0.0
	 */
	public function get_type(): string {
		return Action_Callback::TYPE_SLACK;
	}

	/**
	 * Get required configuration fields.
	 *
	 * @return array Required field names.
	 * @since 1.0.0
	 */
	public function get_required_fields(): array {
		return [ 'config_id', 'content', 'channel' ];
	}

	/**
	 * Validate configuration.
	 *
	 * Ensures config_id exists, content is provided, and channel is valid.
	 *
	 * @param  array  $config  Configuration to validate.
	 *
	 * @return bool True if valid.
	 * @throws Custom_Exception
	 * @since 1.0.0
	 */
	public function validate_config( array $config ): bool {
		// Check required fields
		parent::validate_config( $config );

		// Validate config_id exists
		$config_id = absint( $config['config_id'] );
		if ( empty( $config_id ) ) {
			throw new Custom_Exception( Error_Codes::VALIDATION_INVALID_VALUE,
				__( 'Please select the slack connection.', 'limb-chatbot' ) );
		}
		if ( ! $slack_config = Config::find( $config_id ) ) {
			throw new Custom_Exception( Error_Codes::VALIDATION_INVALID_VALUE,
				__( 'The slack connection not found', 'limb-chatbot' ) );
		}
		if ( ! $slack_config->get_related_to_instance() instanceof Slack ) {
			throw new Custom_Exception( Error_Codes::VALIDATION_INVALID_VALUE,
				__( 'The given connection does not belong to Slack.', 'limb-chatbot' ) );
		}
		if ( empty( $config['channel'] ) ) {
			throw new Custom_Exception( Error_Codes::VALIDATION_INVALID_VALUE,
				__( 'Please select the slack channel', 'limb-chatbot' ) );
		}
		$global_utility      = new Slack_Channels_Utility( $slack_config );
		$utility             = new Channels_Utility( $global_utility );
		$utility->channel_id = $config['channel'];
		try {
			$channel             = $utility->retrieve();
		} catch (Exception $e){
			throw new Custom_Exception( Error_Codes::VALIDATION_INVALID_VALUE, __( 'The channel not found', 'limb-chatbot' ) );
		}
		if ( empty( $channel ) ) {
			throw new Custom_Exception( Error_Codes::VALIDATION_INVALID_VALUE, __( 'The channel not found', 'limb-chatbot' ) );
		}
		if ( empty( $channel['is_member'] ) ) {
			throw new Custom_Exception( Error_Codes::VALIDATION_INVALID_VALUE, __( 'The bot is not a member for the given channel', 'limb-chatbot' ) );
		}

		return true;
	}

	/**
	 * Execute Slack callback.
	 *
	 * Sends a beautifully formatted message to Slack channel using the Slack API.
	 * Includes user information and submitted parameters automatically.
	 *
	 * @param  array  $config  Configuration.
	 * @param  Action_Callback_Execution_Context  $context  Execution context.
	 *
	 * @return Callback_Executor_Response Response object with data and metadata.
	 * @throws Custom_Exception On execution failure.
	 * @since 1.0.0
	 */
	public function execute_callback( array $config, Action_Callback_Execution_Context $context ): Callback_Executor_Response {
		// Load Slack configuration
		$slack_config = Config::find( $config['config_id'] );
		// Get bot token from config
		$bot_token = $slack_config->get_params()['bot_token'] ?? '';
		// Get channel name (with # prefix if not present)
		$channel = $config['channel'];

		// Build message payload with blocks
		$slack_message_builder = new Slack_Bot_Message_Builder();
		$payload               = $slack_message_builder->build( $config['content'], $channel, $context );
		// Send message to Slack
		$response = $this->send_slack_message( $bot_token, $payload );

		// Return success response
		return Callback_Executor_Response::success(
			[
				'sent'          => true,
				'channel'       => $channel,
				'message'       => $config['content'],
				'sent_at'       => current_time( 'mysql', true ),
				'slack_ts'      => $response['ts'] ?? null,
				'slack_channel' => $response['channel'] ?? null,
			],
			[
				'config_id' => $config['config_id'],
				'channel'   => $channel,
			]
		);
	}

	/**
	 * Send message to Slack using chat.postMessage API.
	 *
	 * @param  string  $bot_token  Bot OAuth token.
	 * @param  array  $payload  Message payload.
	 *
	 * @return array Slack API response.
	 * @throws Custom_Exception If sending fails.
	 * @since 1.0.0
	 */
	private function send_slack_message( string $bot_token, array $payload ): array {
		$response = wp_remote_post(
			'https://slack.com/api/chat.postMessage',
			[
				'headers' => [
					'Authorization' => 'Bearer ' . $bot_token,
					'Content-Type'  => 'application/json',
				],
				'body'    => wp_json_encode( $payload ),
				'timeout' => 30,
			]
		);
		if ( is_wp_error( $response ) ) {
			throw new Custom_Exception(
				Error_Codes::TECHNICAL_ERROR,
				sprintf(
				/* translators: %s: error message */
					__( 'Failed to send Slack message: %s', 'limb-chatbot' ),
					$response->get_error_message()
				),
				null,
				500
			);
		}

		$body = wp_remote_retrieve_body( $response );
		$data = json_decode( $body, true );

		if ( ! isset( $data['ok'] ) || ! $data['ok'] ) {
			throw new Custom_Exception(
				Error_Codes::TECHNICAL_ERROR,
				sprintf(
				/* translators: %s: Slack error */
					__( 'Slack API error: %s', 'limb-chatbot' ),
					$data['error'] ?? __( 'Unknown error', 'limb-chatbot' )
				),
				$data,
				400
			);
		}

		return $data;
	}
}
