<?php
/**
 * GSC Credentials.
 *
 * Stores the Google service-account JSON and the selected Search Console
 * property. The service-account JSON contains a private key, so it is
 * encrypted at rest (AES-256-CBC) with a key derived from the site's WP
 * salts — which live in wp-config.php, NOT the database. A stolen DB dump
 * therefore can't read the key without also having wp-config.
 *
 * The raw key is never echoed back to the browser; the settings UI only
 * ever shows the connected client_email.
 *
 * @package Platform\Helm\Modules\GSC
 */

declare( strict_types = 1 );

namespace Platform\Helm\Modules\GSC;

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

final class GscCredentials {

	public const OPTION_CREDENTIALS = 'platform_helm_gsc_credentials'; // encrypted service-account JSON.
	public const OPTION_PROPERTY    = 'platform_helm_gsc_property';    // e.g. https://example.com/ or sc-domain:example.com
	public const OPTION_RANGE_DAYS  = 'platform_helm_gsc_range_days';  // lookback window for Search Analytics.
	public const OPTION_OAUTH       = 'platform_helm_gsc_oauth';       // encrypted { client_id, client_secret, refresh_token }
	public const OPTION_AUTH_METHOD = 'platform_helm_gsc_auth_method'; // 'oauth' | 'service_account'

	public const METHOD_OAUTH           = 'oauth';
	public const METHOD_SERVICE_ACCOUNT = 'service_account';

	public const DEFAULT_RANGE_DAYS = 90;

	/** @var array<string,mixed>|null Parsed service-account, lazily decrypted. */
	private static ?array $parsed = null;

	/**
	 * Validate and store the service-account JSON + property.
	 *
	 * @return true|\WP_Error
	 */
	public static function save( string $json, string $property ) {
		$data = json_decode( trim( $json ), true );
		if ( ! is_array( $data ) ) {
			return new \WP_Error( 'gsc_bad_json', __( 'That does not look like valid JSON. Paste the full service-account key file.', 'platform-helm' ) );
		}
		foreach ( [ 'client_email', 'private_key', 'token_uri' ] as $field ) {
			if ( empty( $data[ $field ] ) || ! is_string( $data[ $field ] ) ) {
				return new \WP_Error( 'gsc_missing_field', sprintf(
					/* translators: %s: field name */
					__( 'The key file is missing the "%s" field. Make sure you downloaded a service-account JSON key.', 'platform-helm' ),
					$field
				) );
			}
		}

		$encrypted = self::encrypt( wp_json_encode( $data ) ?: '' );
		if ( '' === $encrypted ) {
			return new \WP_Error( 'gsc_encrypt_failed', __( 'Could not securely store the key on this server.', 'platform-helm' ) );
		}

		update_option( self::OPTION_CREDENTIALS, $encrypted, false );
		update_option( self::OPTION_PROPERTY, sanitize_text_field( $property ), false );
		self::$parsed = $data;
		return true;
	}

	public static function set_property( string $property ): void {
		update_option( self::OPTION_PROPERTY, sanitize_text_field( $property ), false );
	}

	public static function clear(): void {
		delete_option( self::OPTION_CREDENTIALS );
		delete_option( self::OPTION_PROPERTY );
		delete_option( self::OPTION_OAUTH );
		delete_option( self::OPTION_AUTH_METHOD );
		self::$parsed = null;
	}

	/** 'oauth' | 'service_account'. Defaults to whichever has stored data, else oauth. */
	public static function auth_method(): string {
		$m = (string) get_option( self::OPTION_AUTH_METHOD, '' );
		if ( self::METHOD_OAUTH === $m || self::METHOD_SERVICE_ACCOUNT === $m ) {
			return $m;
		}
		if ( '' !== (string) get_option( self::OPTION_CREDENTIALS, '' ) ) {
			return self::METHOD_SERVICE_ACCOUNT;
		}
		return self::METHOD_OAUTH;
	}

	public static function is_connected(): bool {
		if ( self::METHOD_OAUTH === self::auth_method() ) {
			return '' !== self::oauth_refresh_token();
		}
		return '' !== (string) get_option( self::OPTION_CREDENTIALS, '' );
	}

	/* ---------- OAuth ---------- */

	/** Store the OAuth client (id + secret) and switch auth method to OAuth. */
	public static function save_oauth_client( string $client_id, string $client_secret ): void {
		$bundle = self::oauth_bundle();
		$bundle['client_id']     = trim( $client_id );
		$bundle['client_secret'] = trim( $client_secret );
		self::store_oauth_bundle( $bundle );
		update_option( self::OPTION_AUTH_METHOD, self::METHOD_OAUTH, false );
	}

	public static function set_refresh_token( string $token ): void {
		if ( '' === $token ) {
			return;
		}
		$bundle                  = self::oauth_bundle();
		$bundle['refresh_token'] = $token;
		self::store_oauth_bundle( $bundle );
	}

	public static function oauth_client_id(): string {
		return (string) ( self::oauth_bundle()['client_id'] ?? '' );
	}

	public static function oauth_client_secret(): string {
		return (string) ( self::oauth_bundle()['client_secret'] ?? '' );
	}

	public static function oauth_refresh_token(): string {
		return (string) ( self::oauth_bundle()['refresh_token'] ?? '' );
	}

	/** @return array<string,string> */
	private static function oauth_bundle(): array {
		$stored = (string) get_option( self::OPTION_OAUTH, '' );
		if ( '' === $stored ) {
			return [];
		}
		$data = json_decode( self::decrypt( $stored ), true );
		return is_array( $data ) ? $data : [];
	}

	/** @param array<string,string> $bundle */
	private static function store_oauth_bundle( array $bundle ): void {
		$enc = self::encrypt( wp_json_encode( $bundle ) ?: '' );
		if ( '' !== $enc ) {
			update_option( self::OPTION_OAUTH, $enc, false );
		}
	}

	public static function client_email(): string {
		$d = self::parsed();
		return (string) ( $d['client_email'] ?? '' );
	}

	public static function private_key(): string {
		$d = self::parsed();
		return (string) ( $d['private_key'] ?? '' );
	}

	public static function token_uri(): string {
		$d = self::parsed();
		return (string) ( $d['token_uri'] ?? 'https://oauth2.googleapis.com/token' );
	}

	public static function property(): string {
		return (string) get_option( self::OPTION_PROPERTY, '' );
	}

	public static function range_days(): int {
		$days = (int) get_option( self::OPTION_RANGE_DAYS, self::DEFAULT_RANGE_DAYS );
		return max( 7, min( 480, $days ) );
	}

	public static function set_range_days( int $days ): void {
		update_option( self::OPTION_RANGE_DAYS, max( 7, min( 480, $days ) ), false );
	}

	/**
	 * @return array<string,mixed>
	 */
	private static function parsed(): array {
		if ( null !== self::$parsed ) {
			return self::$parsed;
		}
		$stored = (string) get_option( self::OPTION_CREDENTIALS, '' );
		if ( '' === $stored ) {
			self::$parsed = [];
			return self::$parsed;
		}
		$json = self::decrypt( $stored );
		$data = json_decode( $json, true );
		self::$parsed = is_array( $data ) ? $data : [];
		return self::$parsed;
	}

	/* ---------- at-rest encryption ---------- */

	private static function cipher_key(): string {
		// 32 raw bytes derived from a salt that lives in wp-config, not the DB.
		return hash( 'sha256', wp_salt( 'auth' ) . '|platform_helm_gsc', true );
	}

	private static function encrypt( string $plain ): string {
		if ( '' === $plain || ! function_exists( 'openssl_encrypt' ) ) {
			return '';
		}
		$iv     = random_bytes( 16 );
		$cipher = openssl_encrypt( $plain, 'aes-256-cbc', self::cipher_key(), OPENSSL_RAW_DATA, $iv );
		if ( false === $cipher ) {
			return '';
		}
		return base64_encode( $iv . $cipher );
	}

	private static function decrypt( string $stored ): string {
		if ( ! function_exists( 'openssl_decrypt' ) ) {
			return '';
		}
		$raw = base64_decode( $stored, true );
		if ( false === $raw || strlen( $raw ) < 17 ) {
			return '';
		}
		$iv     = substr( $raw, 0, 16 );
		$cipher = substr( $raw, 16 );
		$plain  = openssl_decrypt( $cipher, 'aes-256-cbc', self::cipher_key(), OPENSSL_RAW_DATA, $iv );
		return is_string( $plain ) ? $plain : '';
	}
}
