<?php
/**
 * Gmail API send via Google OAuth 2.0 (gmail.send scope).
 *
 * @package EmailSmtp
 * @link https://developers.google.com/workspace/gmail/api/guides/sending
 */

namespace EmailSmtp\Providers\Google;

use EmailSmtp\Includes\ApiHttpClient;
use EmailSmtp\Includes\DeliveryResult;
use EmailSmtp\Includes\EmailMessage;
use EmailSmtp\Includes\ErrorNormalizer;
use EmailSmtp\Includes\OAuth\AbstractOAuthMailProvider;
use EmailSmtp\Includes\OAuth\OAuthState;
use EmailSmtp\Includes\OAuth\OAuthTokenStore;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

final class Provider extends AbstractOAuthMailProvider {

	private const AUTH_URL  = 'https://accounts.google.com/o/oauth2/v2/auth';
	private const TOKEN_URL = 'https://oauth2.googleapis.com/token';
	private const SEND_URL  = 'https://gmail.googleapis.com/gmail/v1/users/me/messages/send';
	private const USERINFO  = 'https://www.googleapis.com/oauth2/v2/userinfo';

	/** Soft cap for To+Cc+Bcc (Gmail / Workspace practical per-message limit). */
	private const RECIPIENT_CAP = 100;

	public function get_id(): string {
		return 'google';
	}

	public function get_name(): string {
		return __( 'Gmail / Google Workspace', 'wp-powersuite' );
	}

	public function get_description(): string {
		return __( 'Send through the Gmail API with OAuth 2.0 (gmail.send). No mailbox password stored.', 'wp-powersuite' );
	}

	public function get_recommended_use(): string {
		return __( 'Low-to-moderate volume from a Google account or Workspace mailbox. Prefer a dedicated ESP for high volume.', 'wp-powersuite' );
	}

	public function get_docs_url(): string {
		return 'https://developers.google.com/workspace/gmail/api/guides/sending';
	}

	public function get_icon(): string {
		return 'dashicons-email';
	}


	public function get_scopes(): array {
		return [
			'https://www.googleapis.com/auth/gmail.send',
			'https://www.googleapis.com/auth/userinfo.email',
			'openid',
		];
	}

	public function get_authorize_url( array $settings, string $state, string $code_challenge ): string {
		$data = OAuthTokenStore::read( $settings, 'google' );
		$args = [
			'client_id'              => $data['client_id'],
			'redirect_uri'           => OAuthState::callback_url( 'google' ),
			'response_type'          => 'code',
			'scope'                  => implode( ' ', $this->get_scopes() ),
			'state'                  => $state,
			'access_type'            => 'offline',
			'prompt'                 => 'consent',
			'include_granted_scopes' => 'true',
			'code_challenge'         => $code_challenge,
			'code_challenge_method'  => 'S256',
		];
		return self::AUTH_URL . '?' . http_build_query( $args, '', '&', PHP_QUERY_RFC3986 );
	}

	public function exchange_code( array $settings, string $code, string $code_verifier ) {
		$data     = OAuthTokenStore::read( $settings, 'google' );
		$response = $this->post_token_form(
			self::TOKEN_URL,
			[
				'code'          => $code,
				'client_id'     => $data['client_id'],
				'client_secret' => $data['client_secret'],
				'redirect_uri'  => OAuthState::callback_url( 'google' ),
				'grant_type'    => 'authorization_code',
				'code_verifier' => $code_verifier,
			]
		);

		if ( ! empty( $response['error'] ) || (int) $response['code'] < 200 || (int) $response['code'] > 299 || ! is_array( $response['json'] ) ) {
			return new \WP_Error( 'token_exchange', ApiHttpClient::extract_error_text( $response ) );
		}

		$json  = $response['json'];
		$token = (string) ( $json['access_token'] ?? '' );
		if ( $token === '' ) {
			return new \WP_Error( 'token_exchange', __( 'Google did not return an access token.', 'wp-powersuite' ) );
		}

		$email = $this->fetch_account_email( $token );

		return [
			'access_token'  => $token,
			'refresh_token' => (string) ( $json['refresh_token'] ?? '' ),
			'expires_in'    => (int) ( $json['expires_in'] ?? 3600 ),
			'email'         => $email,
		];
	}

	public function refresh_access_token( array $settings, string $refresh_token ) {
		$data     = OAuthTokenStore::read( $settings, 'google' );
		$response = $this->post_token_form(
			self::TOKEN_URL,
			[
				'client_id'     => $data['client_id'],
				'client_secret' => $data['client_secret'],
				'refresh_token' => $refresh_token,
				'grant_type'    => 'refresh_token',
			]
		);

		if ( ! empty( $response['error'] ) || (int) $response['code'] < 200 || (int) $response['code'] > 299 || ! is_array( $response['json'] ) ) {
			return new \WP_Error( 'token_refresh', ApiHttpClient::extract_error_text( $response ) );
		}

		$json  = $response['json'];
		$token = (string) ( $json['access_token'] ?? '' );
		if ( $token === '' ) {
			return new \WP_Error( 'token_refresh', __( 'Google token refresh failed.', 'wp-powersuite' ) );
		}

		return [
			'access_token'  => $token,
			'refresh_token' => (string) ( $json['refresh_token'] ?? '' ),
			'expires_in'    => (int) ( $json['expires_in'] ?? 3600 ),
		];
	}

	public function fetch_account_email( string $access_token ): string {
		$response = ApiHttpClient::request(
			'GET',
			self::USERINFO,
			[ 'Authorization' => 'Bearer ' . $access_token ]
		);
		if ( (int) $response['code'] >= 200 && (int) $response['code'] < 300 && is_array( $response['json'] ) ) {
			$email = sanitize_email( (string) ( $response['json']['email'] ?? '' ) );
			return is_email( $email ) ? $email : '';
		}
		return '';
	}

	public function send( EmailMessage $message, array $settings ): DeliveryResult {
		$missing = $this->require_from( $message );
		if ( $missing ) {
			return $missing;
		}

		$token = $this->get_valid_access_token( $settings );
		if ( is_wp_error( $token ) ) {
			$result = ErrorNormalizer::from_mixed( $token );
			return DeliveryResult::failure(
				$result->get_user_message(),
				$result->get_technical_message(),
				'authentication',
				'auth_expired',
				false,
				__( 'Disconnect and Connect the Google account again. Publishing the OAuth app may be required outside test users.', 'wp-powersuite' )
			);
		}

		if ( array_slice( $message->get_to(), 0, self::RECIPIENT_CAP ) === [] ) {
			return DeliveryResult::failure( __( 'At least one recipient is required.', 'wp-powersuite' ), 'missing_to', 'recipient' );
		}

		$raw      = $this->build_rfc822( $message, self::RECIPIENT_CAP );
		$response = ApiHttpClient::request(
			'POST',
			self::SEND_URL,
			[
				'Authorization' => 'Bearer ' . $token,
				'Content-Type'  => 'application/json',
			],
			[ 'raw' => $this->base64url( $raw ) ]
		);

		return ApiHttpClient::to_delivery_result(
			$response,
			static function ( array $res ) {
				$id = is_array( $res['json'] ) ? (string) ( $res['json']['id'] ?? '' ) : '';
				return [
					'message' => __( 'Gmail accepted the message.', 'wp-powersuite' ),
					'id'      => $id,
				];
			}
		);
	}
}
