<?php

namespace Limb_Chatbot\Includes\Integrations\Slack\Services;

use Limb_Chatbot\Includes\Data_Objects\Config;
use Limb_Chatbot\Includes\Exceptions\Error_Codes;
use Limb_Chatbot\Includes\Exceptions\Exception;

/**
 * Service for validating Slack integration configurations.
 *
 * Validates OAuth tokens, required scopes, and webhook configuration.
 *
 * @package Limb_Chatbot\Includes\Services\Integrations
 * @since 1.0.0
 */
class Slack_Validation_Service {

	/**
	 * Required OAuth scopes for Slack integration.
	 *
	 * @var string[]
	 * @since 1.0.0
	 */
	const REQUIRED_SCOPES = [
		'chat:write',
		'chat:write.public',
		'channels:read',
		'channels:history',
		'groups:read',
		'groups:history',
		'im:read',
		'im:write',
		'mpim:read',
		'mpim:write',
		'users:read',
		'users:read.email',
		'app_mentions:read',
		'channels:manage',
		'groups:write',
	];

	/**
	 * Slack test endpoints used for validating scope access.
	 */
	private const SCOPE_TESTS = [
		'chat:write'        => [ 'chat.postMessage', [ 'channel' => 'DUMMY', 'text' => 'test' ] ],
		'chat:write.public' => [ 'chat.postMessage', [ 'channel' => 'C123INVALID', 'text' => 'test' ] ],
		'channels:read'     => [ 'conversations.list', [] ],
		'channels:history'  => [ 'conversations.history', [ 'channel' => 'C123INVALID' ] ],
		'channels:manage'   => [ 'conversations.rename', [ 'channel' => 'C123INVALID', 'name' => 'test-rename' ] ],
		'groups:read'       => [ 'conversations.list', [ 'types' => 'private_channel' ] ],
		'groups:history'    => [ 'conversations.history', [ 'channel' => 'G123INVALID' ] ],
		'groups:write'      => [ 'conversations.create', [ 'name' => 'test-invalid-group-12345', 'is_private' => true ] ],
		'im:read'           => [ 'conversations.list', [ 'types' => 'im' ] ],
		'im:write'          => [ 'conversations.open', [ 'users' => 'U123INVALID' ] ],
		'mpim:read'         => [ 'conversations.list', [ 'types' => 'mpim' ] ],
		'mpim:write'        => [ 'conversations.open', [ 'users' => 'U123INVALID,U456INVALID' ] ],
		'users:read'        => [ 'users.list', [] ],
		'users:read.email'  => [ 'users.lookupByEmail', [ 'email' => 'invalid@example.com' ] ],
		'app_mentions:read' => [ 'auth.test', [] ],
	];

	/**
	 * Validates the Slack configuration.
	 *
	 * @param  Config  $config  Configuration object containing Slack credentials.
	 *
	 * @return true Validation result with status and details.
	 * @throws Exception If validation fails.
	 * @since 1.0.0
	 */
	public function validate( Config $config ) {
		$params = $config->get_params();

		// Validate required parameters
		$this->validate_required_params( $params );

		$bot_token      = $params['bot_token'] ?? '';
		$signing_secret = $params['signing_secret'] ?? '';
		$app_token      = $params['app_token'] ?? null;

		$validation_results = [
			'valid'          => true,
			'bot_token'      => [ 'valid' => false, 'message' => '' ],
			'signing_secret' => [ 'valid' => false, 'message' => '' ],
			'app_token'      => [ 'valid' => true, 'message' => 'Optional' ],
			'scopes'         => [ 'valid' => false, 'missing' => [], 'message' => '' ],
			'webhook'        => [ 'valid' => false, 'message' => '' ],
		];

		// Test bot token and scopes
		try {
			$auth_test = $this->test_auth( $bot_token );
			if ( $auth_test['ok'] ) {
				$validation_results['bot_token']['valid']   = true;
				$validation_results['bot_token']['message'] = __( 'Bot token is valid', 'limb-chatbot' );

				// Validate scopes
				$scopes_result                = $this->validate_scopes( $bot_token );
				$validation_results['scopes'] = $scopes_result;

				if ( ! $scopes_result['valid'] ) {
					$validation_results['valid'] = false;
				}
			} else {
				$validation_results['bot_token']['valid']   = false;
				$validation_results['bot_token']['message'] = $auth_test['error'] ?? __( 'Invalid bot token',
					'limb-chatbot' );
				$validation_results['valid']                = false;
			}
		} catch ( \Exception $e ) {
			$validation_results['bot_token']['valid']   = false;
			$validation_results['bot_token']['message'] = $e->getMessage();
			$validation_results['valid']                = false;
		}

		// Validate signing secret (basic format check)
		if ( ! empty( $signing_secret ) && strlen( $signing_secret ) === 32 ) {
			$validation_results['signing_secret']['valid']   = true;
			$validation_results['signing_secret']['message'] = __( 'Signing secret format is valid', 'limb-chatbot' );
		} else {
			$validation_results['signing_secret']['valid']   = false;
			$validation_results['signing_secret']['message'] = __( 'Invalid signing secret format. Should be 32 characters.',
				'limb-chatbot' );
			$validation_results['valid']                     = false;
		}

		// Validate app-level token if provided
		if ( ! empty( $app_token ) ) {
			if ( strpos( $app_token, 'xapp-' ) === 0 ) {
				$validation_results['app_token']['valid']   = true;
				$validation_results['app_token']['message'] = __( 'App-level token format is valid', 'limb-chatbot' );
			} else {
				$validation_results['app_token']['valid']   = false;
				$validation_results['app_token']['message'] = __( 'Invalid app-level token format. Should start with "xapp-"',
					'limb-chatbot' );
			}
		}

		// Check webhook configuration
		$webhook_url                   = $this->get_webhook_url();
		$validation_results['webhook'] = [
			'valid'   => true,
			'url'     => $webhook_url,
			'message' => sprintf(
			/* translators: %s: webhook URL */
				__( 'Configure this webhook URL in your Slack app: %s', 'limb-chatbot' ),
				$webhook_url
			),
		];

		if ( ! ( $validation_results['valid'] ?? false ) ) {
			$error_message = $this->build_validation_error_message( $validation_results );

			throw new Exception(
				Error_Codes::VALIDATION_INVALID_VALUE,
				$error_message,
				$validation_results,
				400
			);
		}

		return true;
	}

	/**
	 * Validates required parameters are present.
	 *
	 * @param  array  $params  Configuration parameters.
	 *
	 * @throws Exception If required parameters are missing.
	 * @since 1.0.0
	 */
	private function validate_required_params( array $params ): void {
		$required = [ 'bot_token', 'signing_secret' ];
		$missing  = [];

		foreach ( $required as $param ) {
			if ( empty( $params[ $param ] ) ) {
				$missing[] = $param;
			}
		}

		if ( ! empty( $missing ) ) {
			throw new Exception(
				Error_Codes::VALIDATION_REQUIRED,
				sprintf(
				/* translators: %s: comma-separated list of missing parameters */
					__( 'Missing required parameters: %s', 'limb-chatbot' ),
					implode( ', ', $missing )
				),
				[ 'missing_params' => $missing ],
				400
			);
		}
	}

	/**
	 * Tests Slack authentication with the provided bot token.
	 *
	 * @param  string  $bot_token  Slack bot OAuth token.
	 *
	 * @return array Slack API response.
	 * @throws Exception If API request fails.
	 * @since 1.0.0
	 */
	private function test_auth( string $bot_token ): array {
		$response = wp_remote_post(
			'https://slack.com/api/auth.test',
			[
				'headers' => [
					'Authorization' => 'Bearer ' . $bot_token,
					'Content-Type'  => 'application/json',
				],
				'timeout' => 15,
			]
		);

		if ( is_wp_error( $response ) ) {
			throw new Exception(
				Error_Codes::TECHNICAL_ERROR,
				sprintf(
				/* translators: %s: error message */
					__( 'Failed to connect to Slack API: %s', 'limb-chatbot' ),
					$response->get_error_message()
				),
				null,
				500
			);
		}

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

		if ( json_last_error() !== JSON_ERROR_NONE ) {
			throw new Exception(
				Error_Codes::UNEXPECTED_RESPONSE_FORMAT,
				__( 'Invalid response from Slack API', 'limb-chatbot' ),
				null,
				500
			);
		}

		return $data;
	}

	/**
	 * ✔ NEW validate_scopes() – full working version
	 */
	private function validate_scopes( string $bot_token ): array {
		$missing = [];
		$details = [];

		foreach ( self::SCOPE_TESTS as $scope => $test ) {
			[ $method, $args ] = $test;

			$response = $this->slack_api_call( $bot_token, $method, $args );

			if ( $response === null ) {
				$details[ $scope ] = 'unverified';
				continue;
			}

			// Missing scope → FAIL
			if ( isset( $response['error'] ) && $response['error'] === 'missing_scope' ) {
				$missing[]         = $scope;
				$details[ $scope ] = 'missing';
				continue;
			}

			// No authorization → FAIL
			if ( isset( $response['error'] ) && in_array( $response['error'],
					[ 'not_authed', 'invalid_auth' ],
					true ) ) {
				$missing[]         = $scope;
				$details[ $scope ] = $response['error'];
				continue;
			}

			// Expected safe errors (channel not found, invalid email, invalid names, etc.)
			// These errors prove the scope exists and is granted
			$safe_errors = [
				'channel_not_found',
				'invalid_email',
				'invalid_channel_id',
				'invalid_name',
				'invalid_name_required',
				'invalid_name_specials',
				'name_taken',
				'restricted_action',
				'users_not_found',
				'invalid_users',
				'cannot_dm_bot',
				'user_not_found',
			];

			if ( isset( $response['error'] ) && in_array( $response['error'], $safe_errors, true ) ) {
				$details[ $scope ] = 'ok';
				continue;
			}

			// If ok=true → PASS
			if ( isset( $response['ok'] ) && $response['ok'] === true ) {
				$details[ $scope ] = 'ok';
				continue;
			}

			// Any other case → unverified
			$details[ $scope ] = 'unverified';
		}

		return [
			'valid'   => empty( $missing ),
			'missing' => array_values( $missing ),
			'details' => $details,
			'message' =>
				empty( $missing )
					? __( 'All required scopes are granted.', 'limb-chatbot' )
					: __( 'Missing Slack OAuth scopes.', 'limb-chatbot' ),
		];
	}

	private function slack_api_call( string $token, string $method, array $args ): ?array {
		$response = wp_remote_post(
			"https://slack.com/api/{$method}",
			[
				'headers' => [
					'Authorization' => "Bearer {$token}",
					'Content-Type'  => 'application/json;charset=utf-8',
				],
				'body'    => json_encode( $args ),
				'timeout' => 12,
			]
		);

		if ( is_wp_error( $response ) ) {
			return null;
		}

		return json_decode( wp_remote_retrieve_body( $response ), true );
	}

	/**
	 * Gets the webhook URL for Slack integration.
	 *
	 * @return string Webhook URL.
	 * @since 1.0.0
	 */
	private function get_webhook_url(): string {
		return rest_url( 'limb/chatbot/v1/integrations/slack/events' );
	}

	/**
	 * Builds a detailed error message from validation results.
	 *
	 * @param  array  $validation_result  Validation result array.
	 *
	 * @return string Formatted error message.
	 * @since 1.0.0
	 */
	private function build_validation_error_message( array $validation_result ): string {
		$errors = [];

		// Bot token errors
		if ( isset( $validation_result['bot_token'] ) && ! $validation_result['bot_token']['valid'] ) {
			$errors[] = sprintf(
				__( 'Bot User OAuth Token: %s', 'limb-chatbot' ),
				$validation_result['bot_token']['message']
			);
		}

		// Signing secret errors
		if ( isset( $validation_result['signing_secret'] ) && ! $validation_result['signing_secret']['valid'] ) {
			$errors[] = sprintf(
				__( 'Signing Secret: %s', 'limb-chatbot' ),
				$validation_result['signing_secret']['message']
			);
		}

		// App token errors (if provided and invalid)
		if ( isset( $validation_result['app_token'] )
		     && ! $validation_result['app_token']['valid']
		     && $validation_result['app_token']['message'] !== 'Optional' ) {
			$errors[] = sprintf(
				__( 'App Token: %s', 'limb-chatbot' ),
				$validation_result['app_token']['message']
			);
		}

		// Scopes errors
		if ( isset( $validation_result['scopes'] ) && ! $validation_result['scopes']['valid'] ) {
			if ( ! empty( $validation_result['scopes']['missing'] ) ) {
				$errors[] = sprintf(
					__( 'Missing OAuth Scopes: %s', 'limb-chatbot' ),
					implode( ', ', $validation_result['scopes']['missing'] )
				);
			} else {
				$errors[] = sprintf(
					__( 'OAuth Scopes: %s', 'limb-chatbot' ),
					$validation_result['scopes']['message']
				);
			}
		}

		// Build final message
		if ( empty( $errors ) ) {
			return __( 'Slack configuration is invalid. Please check your credentials.', 'limb-chatbot' );
		}

		return __( 'Slack configuration validation failed: ', 'limb-chatbot' ) . implode( ' | ', $errors );
	}

	/**
	 * Gets the list of required scopes.
	 *
	 * @return string[] Array of required scope strings.
	 * @since 1.0.0
	 */
	public function get_required_scopes(): array {
		return self::REQUIRED_SCOPES;
	}
}

