<?php

declare( strict_types=1 );

namespace WPDesk\ShopMagicGoogleSheets\Controller;

use ShopMagicVendor\Psr\Log\LoggerInterface;
use WPDesk\ShopMagic\Components\Routing\HttpProblemException;
use WPDesk\ShopMagic\Components\UrlGenerator\AdminUrlGenerator;
use WPDesk\ShopMagicGoogleSheets\Client;
use WPDesk\ShopMagicGoogleSheets\Data\GoogleSheetsService;
use WPDesk\ShopMagicGoogleSheets\GoogleSheetsAuthorizationService;

/**
 * Retrieve data from Google Sheets API.
 */
final class GoogleSheetsController {
	private const SIGNATURE_PARAM            = 'sig';
	private const SIGNATURE_TIMESTAMP_PARAM  = 'ts';
	private const SIGNATURE_VERSION_PARAM    = 'v';
	private const SIGNATURE_VERSION          = '1';
	private const SIGNATURE_MAX_SKEW_SECONDS = 300;

	/** @var Client */
	private $client;

	/** @var GoogleSheetsService */
	private $google_sheets;

	/** @var LoggerInterface */
	private $logger;

	public function __construct( Client $client, GoogleSheetsService $google_sheets, LoggerInterface $logger ) {
		$this->client        = $client;
		$this->google_sheets = $google_sheets;
		$this->logger        = $logger;
	}

	public function revoke(): \WP_REST_Response {
		try {
			$this->client->revoke();
			$payload = [
				'body'   => [
					'result'  => 'success',
					'message' => esc_html__( 'Successfully disconnected from Google services. You may log into another account.', 'shopmagic-for-google-sheets' ),
				],
				'status' => \WP_Http::ACCEPTED,
			];
		} catch ( \Throwable $e ) {
			$this->logger->warning( sprintf( 'Failed to revoke token: %s', $e->getMessage() ) );
			$payload = [
				'body'   => [
					'result'  => 'failed',
					'message' => esc_html__( 'Failed to log out from Google services.', 'shopmagic-for-google-sheets' ),
					'details' => $e->getMessage(),
				],
				'status' => \WP_Http::UNPROCESSABLE_ENTITY,
			];
		}

		return new \WP_REST_Response(
			wp_json_encode( $payload['body'] ),
			$payload['status'],
			[ 'Content-Type' => 'application/json; charset=utf-8' ]
		);
	}

	public function authorize(
		\WP_REST_Request $request,
		GoogleSheetsAuthorizationService $service,
		AdminUrlGenerator $url_generator
	): \WP_REST_Response {
		if ( ! $request->has_param( 'message' ) ) {
			return new \WP_REST_Response(
				[
					'error' => esc_html__( 'You are not authorized to access the data.', 'shopmagic-for-google-sheets' ),
				],
				\WP_Http::FORBIDDEN
			);
		}

		if ( ! $this->is_signature_valid( $request, $service, (string) $request->get_param( 'message' ) ) ) {
			return new \WP_REST_Response(
				[
					'error' => esc_html__( 'You are not authorized to access the data.', 'shopmagic-for-google-sheets' ),
				],
				\WP_Http::FORBIDDEN
			);
		}

		$service->authorize_with_encrypted_message( $request->get_param( 'message' ) );

		return new \WP_REST_Response(
			null,
			\WP_Http::FOUND,
			[
				'Location' => $url_generator->generate(
					'admin.php',
					[
						'page'      => 'shopmagic-admin',
						'_fragment' => '/settings/google-sheets',
					]
				),
			]
		);
	}

	public function authorize_start( GoogleSheetsAuthorizationService $service ): \WP_REST_Response {
		return new \WP_REST_Response(
			null,
			\WP_Http::FOUND,
			[
				'Location' => $service->build_authorization_url(),
			]
		);
	}

	private function is_signature_valid( \WP_REST_Request $request, GoogleSheetsAuthorizationService $service, ?string $payload ): bool {
		$signature = $this->get_signature_value( $request, self::SIGNATURE_PARAM, 'X-SM-Signature' );
		$timestamp = $this->get_signature_value( $request, self::SIGNATURE_TIMESTAMP_PARAM, 'X-SM-Timestamp' );
		$version   = $this->get_signature_value( $request, self::SIGNATURE_VERSION_PARAM, 'X-SM-Sig-Version' );

		if ( $signature === null || $timestamp === null || $version === null ) {
			return false;
		}

		if ( $version !== self::SIGNATURE_VERSION ) {
			return false;
		}

		if ( ! ctype_digit( $timestamp ) ) {
			return false;
		}

		$timestamp_int = (int) $timestamp;
		if ( abs( time() - $timestamp_int ) > self::SIGNATURE_MAX_SKEW_SECONDS ) {
			return false;
		}

		$secret = $service->get_signature_secret();
		if ( empty( $secret ) ) {
			return false;
		}

		$body = $payload;
		if ( $body === null ) {
			$body = $request->get_body();
			if ( $body === '' ) {
				$params = $request->get_body_params();
				if ( ! empty( $params ) ) {
					ksort( $params );
					$body = http_build_query( $params );
				}
			}
		}

		$expected = hash_hmac( 'sha256', $timestamp . '.' . (string) $body, $secret );

		return hash_equals( $expected, $signature );
	}

	private function get_signature_value( \WP_REST_Request $request, string $param, string $header ): ?string {
		$value = $request->get_header( $header );
		if ( is_string( $value ) && $value !== '' ) {
			return $value;
		}

		$value = $request->get_param( $param );
		if ( is_string( $value ) && $value !== '' ) {
			return $value;
		}

		return null;
	}

	public function get_worksheets( string $sheet_id ): \WP_REST_Response {
		if ( ! current_user_can( 'edit_posts' ) ) {
			return new \WP_REST_Response(
				[
					'error' => esc_html__( 'You are not authorized to access the data.', 'shopmagic-for-google-sheets' ),
				],
				\WP_Http::FORBIDDEN
			);
		}

		try {
			return new \WP_REST_Response(
				[
					'sheet'      => $sheet_id,
					'title'      => $this->google_sheets->get_spreadsheet_title( $sheet_id ),
					'worksheets' => $this->google_sheets->get_worksheets( $sheet_id ),
				]
			);
		} catch ( \ShopMagicGoogleSheetsVendor\Google\Service\Exception $e ) {
			if ( $e->getCode() === 404 ) {
				throw new HttpProblemException(
					[
						'title'  => 'Google Sheets API error',
						'detail' => __( 'File not found', 'shopmagic-for-google-sheets' ),
					],
					404
				);
			}

			$error_message = json_decode( $e->getMessage(), true )['error']['message'];

			throw new HttpProblemException(
				[
					'title'  => 'Google Sheets API error',
					'detail' => $error_message,
				]
			);
		}
	}

	public function get_worksheet_header( string $sheet_id, string $worksheet ): \WP_REST_Response {
		if ( ! current_user_can( 'edit_posts' ) ) {
			return new \WP_REST_Response(
				[
					'error' => esc_html__( 'You are not authorized to access the data.', 'shopmagic-for-google-sheets' ),
				],
				\WP_Http::FORBIDDEN
			);
		}

		try {
			return new \WP_REST_Response( $this->google_sheets->get_worksheet_header( $sheet_id, $worksheet ) );
		} catch ( \ShopMagicGoogleSheetsVendor\Google\Service\Exception $e ) {
			$error_message = json_decode( $e->getMessage(), true )['error']['message'];

			throw new HttpProblemException(
				[
					'title'  => 'Google Sheets API error',
					'detail' => $error_message,
				]
			);
		}
	}
}
