<?php
/**
 * Google Analytics 4 client.
 *
 * Pulls organic-search traffic data via the GA4 Data API. The user enters
 * their numeric property ID (e.g. "properties/123456789"); the configured
 * service account must be added as a Viewer on that property.
 *
 * Phase 1 surfaces a single endpoint — top organic-search pages over the
 * last 28 days with sessions, users, and engagement rate. This is enough
 * to overlay onto the SEO Agent so users can see "this URL gets X sessions
 * but is missing alt text".
 *
 * @package Antimanual\Seo\Google
 * @since 3.7.0
 */

namespace Antimanual\Seo\Google;

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

/**
 * GA4 Data API client (organic traffic).
 */
class GA4Client {

	private const SCOPE_READONLY = 'https://www.googleapis.com/auth/analytics.readonly';

	private const RUN_REPORT_ENDPOINT = 'https://analyticsdata.googleapis.com/v1beta/properties/%s:runReport';

	private const ACCOUNT_SUMMARIES_ENDPOINT = 'https://analyticsadmin.googleapis.com/v1beta/accountSummaries';

	private const CACHE_PREFIX = 'atml_ga4_';

	private const CACHE_TTL = 6 * HOUR_IN_SECONDS;

	/**
	 * Top pages by organic-search sessions.
	 *
	 * @param string $property_id Numeric GA4 property ID (with or without "properties/" prefix).
	 * @param array  $args        {
	 *     @type int $days      Look-back window (default 28, max 90).
	 *     @type int $row_limit Max rows (default 25, max 250).
	 * }
	 * @param bool   $force       Skip the cache.
	 * @return array|\WP_Error
	 */
	public function organic_top_pages( string $property_id, array $args = array(), bool $force = false ) {
		$property_id = $this->normalize_property_id( $property_id );
		if ( '' === $property_id ) {
			return new \WP_Error( 'no_property', __( 'A GA4 property ID is required.', 'antimanual' ) );
		}

		$args = wp_parse_args(
			$args,
			array(
				'days'      => 28,
				'row_limit' => 25,
			)
		);

		$days      = max( 1, min( 90, (int) $args['days'] ) );
		$row_limit = max( 1, min( 250, (int) $args['row_limit'] ) );

		$body = array(
			'dateRanges' => array(
				array(
					'startDate' => $days . 'daysAgo',
					'endDate'   => 'yesterday',
				),
			),
			'dimensions' => array(
				array( 'name' => 'pagePath' ),
				array( 'name' => 'pageTitle' ),
			),
			'metrics'    => array(
				array( 'name' => 'sessions' ),
				array( 'name' => 'totalUsers' ),
				array( 'name' => 'engagementRate' ),
				array( 'name' => 'averageSessionDuration' ),
			),
			'dimensionFilter' => array(
				'filter' => array(
					'fieldName'    => 'sessionDefaultChannelGroup',
					'stringFilter' => array(
						'matchType' => 'EXACT',
						'value'     => 'Organic Search',
					),
				),
			),
			'orderBys'   => array(
				array(
					'metric' => array( 'metricName' => 'sessions' ),
					'desc'   => true,
				),
			),
			'limit'      => $row_limit,
		);

		$cache_key = self::CACHE_PREFIX . md5( $property_id . '|' . wp_json_encode( $body ) );
		if ( ! $force ) {
			$cached = get_transient( $cache_key );
			if ( is_array( $cached ) ) {
				$cached['cached'] = true;
				return $cached;
			}
		}

		$token = GoogleAuthService::instance()->get_access_token( self::SCOPE_READONLY );
		if ( is_wp_error( $token ) ) {
			return $token;
		}

		$endpoint = sprintf( self::RUN_REPORT_ENDPOINT, rawurlencode( $property_id ) );
		$response = wp_remote_post(
			$endpoint,
			array(
				'timeout' => 60,
				'headers' => array(
					'Authorization' => 'Bearer ' . $token,
					'Content-Type'  => 'application/json',
				),
				'body'    => wp_json_encode( $body ),
			)
		);

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

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

		if ( $status < 200 || $status >= 300 ) {
			$msg = is_array( $data ) && isset( $data['error']['message'] )
				? (string) $data['error']['message']
				: sprintf(
					/* translators: %d: HTTP status code. */
					__( 'GA4 returned HTTP %d.', 'antimanual' ),
					$status
				);
			return new \WP_Error( 'ga4_error', sanitize_text_field( $msg ), array( 'status' => $status ) );
		}

		if ( ! is_array( $data ) ) {
			return new \WP_Error( 'ga4_invalid', __( 'GA4 returned an invalid response.', 'antimanual' ) );
		}

		$rows       = is_array( $data['rows'] ?? null ) ? $data['rows'] : array();
		$normalized = array();
		foreach ( $rows as $row ) {
			if ( ! is_array( $row ) ) {
				continue;
			}
			$dim_values = is_array( $row['dimensionValues'] ?? null ) ? $row['dimensionValues'] : array();
			$met_values = is_array( $row['metricValues'] ?? null ) ? $row['metricValues'] : array();

			$normalized[] = array(
				'page_path'                => isset( $dim_values[0]['value'] ) ? sanitize_text_field( (string) $dim_values[0]['value'] ) : '',
				'page_title'               => isset( $dim_values[1]['value'] ) ? sanitize_text_field( (string) $dim_values[1]['value'] ) : '',
				'sessions'                 => isset( $met_values[0]['value'] ) ? (int) $met_values[0]['value'] : 0,
				'total_users'              => isset( $met_values[1]['value'] ) ? (int) $met_values[1]['value'] : 0,
				'engagement_rate'          => isset( $met_values[2]['value'] ) ? round( (float) $met_values[2]['value'], 4 ) : 0.0,
				'avg_session_duration_sec' => isset( $met_values[3]['value'] ) ? round( (float) $met_values[3]['value'], 1 ) : 0.0,
			);
		}

		$totals       = is_array( $data['totals'][0]['metricValues'] ?? null ) ? $data['totals'][0]['metricValues'] : array();
		$total_sessions = isset( $totals[0]['value'] ) ? (int) $totals[0]['value'] : 0;
		$total_users    = isset( $totals[1]['value'] ) ? (int) $totals[1]['value'] : 0;

		$payload = array(
			'property_id'   => $property_id,
			'days'          => $days,
			'fetched_at'    => gmdate( 'c' ),
			'cached'        => false,
			'rows'          => $normalized,
			'totals'        => array(
				'sessions'    => $total_sessions,
				'total_users' => $total_users,
			),
		);

		set_transient( $cache_key, $payload, self::CACHE_TTL );

		return $payload;
	}

	/**
	 * List all GA4 properties accessible to the configured service account.
	 *
	 * Uses the Analytics Admin API accountSummaries endpoint, which returns
	 * every account and its child properties in a single request.
	 *
	 * @return array|\WP_Error Array of [ property_id, display_name, account_name ].
	 */
	public function list_properties() {
		$token = GoogleAuthService::instance()->get_access_token( self::SCOPE_READONLY );
		if ( is_wp_error( $token ) ) {
			return $token;
		}

		$response = \wp_remote_get(
			self::ACCOUNT_SUMMARIES_ENDPOINT,
			array(
				'timeout' => 30,
				'headers' => array(
					'Authorization' => 'Bearer ' . $token,
				),
			)
		);

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

		$status = (int) \wp_remote_retrieve_response_code( $response );
		$body   = \wp_remote_retrieve_body( $response );
		$data   = json_decode( $body, true );

		if ( $status < 200 || $status >= 300 ) {
			$msg = is_array( $data ) && isset( $data['error']['message'] )
				? (string) $data['error']['message']
				: sprintf(
					/* translators: %d: HTTP status code. */
					__( 'Analytics Admin API returned HTTP %d.', 'antimanual' ),
					$status
				);
			return new \WP_Error( 'ga4_admin_error', \sanitize_text_field( $msg ), array( 'status' => $status ) );
		}

		if ( ! is_array( $data ) ) {
			return new \WP_Error( 'ga4_admin_invalid', __( 'Analytics Admin API returned an invalid response.', 'antimanual' ) );
		}

		$properties = array();
		$summaries  = is_array( $data['accountSummaries'] ?? null ) ? $data['accountSummaries'] : array();

		foreach ( $summaries as $account ) {
			if ( ! is_array( $account ) ) {
				continue;
			}
			$account_name = \sanitize_text_field( (string) ( $account['displayName'] ?? $account['account'] ?? '' ) );
			$prop_list    = is_array( $account['propertySummaries'] ?? null ) ? $account['propertySummaries'] : array();

			foreach ( $prop_list as $prop ) {
				if ( ! is_array( $prop ) ) {
					continue;
				}
				// "properties/123456789" → "123456789".
				$raw_id      = (string) ( $prop['property'] ?? '' );
				$property_id = str_replace( 'properties/', '', $raw_id );
				$display     = \sanitize_text_field( (string) ( $prop['displayName'] ?? $raw_id ) );

				if ( '' !== $property_id ) {
					$properties[] = array(
						'property_id'  => $property_id,
						'display_name' => $display,
						'account_name' => $account_name,
					);
				}
			}
		}

		return $properties;
	}

	/**
	 * Strip the optional "properties/" prefix and validate the ID is numeric.
	 *
	 * @param string $raw User-supplied property string.
	 * @return string Plain numeric ID, or empty string if invalid.
	 */
	private function normalize_property_id( string $raw ): string {
		$id = trim( $raw );
		if ( 0 === strpos( $id, 'properties/' ) ) {
			$id = substr( $id, strlen( 'properties/' ) );
		}
		$id = preg_replace( '/[^0-9]/', '', $id );
		return is_string( $id ) ? $id : '';
	}
}
