<?php

declare(strict_types=1);

namespace SiteFort\Modules\Cloud\Services;

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

use SiteFort\Includes\SettingsStore;
use SiteFort\Modules\Cloud\SecureApi;
use SiteFort\Modules\Cloud\Service\CloudSettings;
use SiteFort\Modules\Cloudflare\Manager as CloudflareManager;
use SiteFort\Modules\Firewall\FirewallSettings;
use SiteFort\Modules\Firewall\GeoIP\MaxMindDownloader;

/**
 * Applies one-time console-delivered integration credentials to local secure storage.
 *
 * The command queue carries only delivery references. Raw secret material is
 * fetched over the existing site-authenticated control-plane channel as an
 * encrypted envelope and decrypted locally using the linked site secret.
 */
class IntegrationSecretApplyService {

	private const DELIVERY_ENVELOPE_SALT_PREFIX = 'site-integration-delivery:v1';
	private const DELIVERY_ENVELOPE_INFO = 'sitefort-site-integration-delivery';

	/**
	 * @param array<string,mixed> $params
	 * @return array<string,mixed>|\WP_Error
	 */
	public function apply( array $params ) {
		$reference = $this->normalize_reference( $params );
		if ( is_wp_error( $reference ) ) {
			return $reference;
		}

		$site_credentials = $this->load_site_credentials();
		if ( is_wp_error( $site_credentials ) ) {
			return $site_credentials;
		}

		$api = new SecureApi(
			'',
			$site_credentials['site_token'],
			$site_credentials['site_secret']
		);

		$response = $api->get(
			'services/integration-secrets/envelope',
			$this->build_request_query( $reference )
		);
		if ( is_wp_error( $response ) ) {
			return $response;
		}
		if ( ! is_array( $response ) || ! is_array( $response['secret'] ?? null ) ) {
			return new \WP_Error( 'integration_secret_invalid_response', 'Console returned an invalid secret envelope.' );
		}

		$envelope = $this->normalize_envelope( $response['secret'], $reference );
		if ( is_wp_error( $envelope ) ) {
			return $envelope;
		}

		$plaintext = $this->decrypt_envelope(
			$envelope,
			$site_credentials['site_secret']
		);
		if ( is_wp_error( $plaintext ) ) {
			return $plaintext;
		}

		$apply_result = $this->store_secret(
			$envelope['integration'],
			$envelope['secret_name'],
			$plaintext,
			is_array( $envelope['metadata'] ) ? $envelope['metadata'] : []
		);
		if ( is_wp_error( $apply_result ) ) {
			return $apply_result;
		}

		return [
			'ok'             => true,
			'integration'    => $envelope['integration'],
			'secret_name'    => $envelope['secret_name'],
			'delivery_token' => $envelope['delivery_token'],
		];
	}

	/**
	 * @param array<string,mixed> $params
	 * @return array<string,mixed>|\WP_Error
	 */
	private function normalize_reference( array $params ) {
		$integration = sanitize_key( (string) ( $params['integration'] ?? '' ) );
		$secret_name = sanitize_key( (string) ( $params['secret_name'] ?? '' ) );
		$delivery_token = sanitize_text_field( (string) ( $params['delivery_token'] ?? '' ) );

		if ( $integration === '' || $secret_name === '' || $delivery_token === '' ) {
			return new \WP_Error( 'integration_secret_invalid_reference', 'Integration secret reference is incomplete.' );
		}

		return [
			'integration'    => $integration,
			'secret_name'    => $secret_name,
			'delivery_token' => $delivery_token,
		];
	}

	/**
	 * @param array<string,mixed> $reference
	 * @return array<string,mixed>
	 */
	private function build_request_query( array $reference ): array {
		return [
			'integration'    => $reference['integration'],
			'secret_name'    => $reference['secret_name'],
			'delivery_token' => $reference['delivery_token'],
		];
	}

	/**
	 * @return array{site_token:string,site_secret:string}|\WP_Error
	 */
	private function load_site_credentials() {
		$settings = CloudSettings::get_all();
		$site_token = trim( (string) ( $settings['site_token'] ?? '' ) );
		$site_secret = trim( (string) ( $settings['site_secret'] ?? '' ) );

		if ( $site_token === '' || $site_secret === '' ) {
			return new \WP_Error( 'integration_secret_site_not_linked', 'Console site credentials are unavailable.' );
		}

		return [
			'site_token'  => $site_token,
			'site_secret' => $site_secret,
		];
	}

	/**
	 * @param array<string,mixed> $envelope
	 * @param array<string,mixed> $reference
	 * @return array<string,mixed>|\WP_Error
	 */
	private function normalize_envelope( array $envelope, array $reference ) {
		$integration = sanitize_key( (string) ( $envelope['integration'] ?? '' ) );
		$secret_name = sanitize_key( (string) ( $envelope['secret_name'] ?? '' ) );
		$delivery_token = sanitize_text_field( (string) ( $envelope['delivery_token'] ?? '' ) );
		$algorithm = strtolower( sanitize_text_field( (string) ( $envelope['algorithm'] ?? '' ) ) );
		$kdf = strtolower( sanitize_text_field( (string) ( $envelope['kdf'] ?? '' ) ) );
		$ciphertext = (string) ( $envelope['ciphertext'] ?? '' );
		$nonce = (string) ( $envelope['nonce'] ?? '' );

		if ( $algorithm !== 'aes-256-gcm' || $kdf !== 'hkdf-sha256' ) {
			return new \WP_Error( 'integration_secret_algorithm_unsupported', 'Console returned an unsupported secret envelope format.' );
		}

		if ( $ciphertext === '' || $nonce === '' ) {
			return new \WP_Error( 'integration_secret_envelope_incomplete', 'Console returned an incomplete secret envelope.' );
		}

		if (
			$integration !== $reference['integration']
			|| $secret_name !== $reference['secret_name']
			|| $delivery_token !== $reference['delivery_token']
		) {
			return new \WP_Error( 'integration_secret_reference_mismatch', 'Console returned a secret envelope that does not match the requested delivery reference.' );
		}

		return [
			'integration'    => $integration,
			'secret_name'    => $secret_name,
			'delivery_token' => $delivery_token,
			'algorithm'      => $algorithm,
			'kdf'            => $kdf,
			'ciphertext'     => $ciphertext,
			'nonce'          => $nonce,
			'metadata'       => is_array( $envelope['metadata'] ?? null ) ? $envelope['metadata'] : [],
		];
	}

	/**
	 * @param array<string,mixed> $envelope
	 * @return string|\WP_Error
	 */
	private function decrypt_envelope( array $envelope, string $site_secret ) {
		if ( ! function_exists( 'hash_hkdf' ) ) {
			return new \WP_Error( 'integration_secret_hkdf_unavailable', 'HKDF support is unavailable on this host.' );
		}
		if ( ! function_exists( 'openssl_decrypt' ) ) {
			return new \WP_Error( 'integration_secret_openssl_unavailable', 'OpenSSL AES-GCM support is unavailable on this host.' );
		}

		$ciphertext = base64_decode( (string) $envelope['ciphertext'], true );
		$nonce = base64_decode( (string) $envelope['nonce'], true );
		if ( $ciphertext === false || $nonce === false || strlen( $nonce ) !== 12 ) {
			return new \WP_Error( 'integration_secret_envelope_invalid', 'Console returned an invalid encrypted secret envelope.' );
		}
		if ( strlen( $ciphertext ) <= 16 ) {
			return new \WP_Error( 'integration_secret_envelope_invalid', 'Console returned a truncated encrypted secret envelope.' );
		}

		$tag = substr( $ciphertext, -16 );
		$encrypted_payload = substr( $ciphertext, 0, -16 );
		$key = $this->derive_envelope_key( $envelope, $site_secret );
		$aad = $this->build_aad( $envelope );

		$plaintext = openssl_decrypt(
			$encrypted_payload,
			'aes-256-gcm',
			$key,
			OPENSSL_RAW_DATA,
			$nonce,
			$tag,
			$aad
		);

		if ( is_string( $key ) ) {
			$key = str_repeat( "\0", strlen( $key ) );
		}

		if ( ! is_string( $plaintext ) || $plaintext === '' ) {
			return new \WP_Error( 'integration_secret_decrypt_failed', 'The secret envelope could not be decrypted with the linked site credentials.' );
		}

		return $plaintext;
	}

	/**
	 * @param array<string,mixed> $envelope
	 */
	private function derive_envelope_key( array $envelope, string $site_secret ): string {
		return hash_hkdf(
			'sha256',
			$site_secret,
			32,
			self::DELIVERY_ENVELOPE_INFO,
			self::DELIVERY_ENVELOPE_SALT_PREFIX . ':' . (string) $envelope['delivery_token']
		);
	}

	/**
	 * @param array<string,mixed> $envelope
	 */
	private function build_aad( array $envelope ): string {
		return implode(
			"\n",
			[
				(string) $envelope['integration'],
				(string) $envelope['secret_name'],
				(string) $envelope['delivery_token'],
			]
		);
	}

	/**
	 * @param array<string,mixed> $metadata
	 * @return array<string,mixed>|\WP_Error
	 */
	private function store_secret(
		string $integration,
		string $secret_name,
		string $plaintext,
		array $metadata
	) {
		switch ( $integration ) {
			case 'cloudflare':
				return $this->store_cloudflare_secret( $secret_name, $plaintext, $metadata );
			case 'maxmind':
				return $this->store_maxmind_secret( $secret_name, $plaintext, $metadata );
		}

		return new \WP_Error( 'integration_secret_unsupported', 'This integration secret type is not supported by the site agent yet.' );
	}

	/**
	 * @param array<string,mixed> $metadata
	 * @return array<string,mixed>|\WP_Error
	 */
	private function store_maxmind_secret( string $secret_name, string $plaintext, array $metadata ) {
		if ( $secret_name !== 'license_key' ) {
			return new \WP_Error( 'maxmind_secret_name_unsupported', 'Unsupported MaxMind secret type.' );
		}

		$account_id  = sanitize_text_field( trim( (string) ( $metadata['account_id'] ?? '' ) ) );
		$license_key = trim( $plaintext );
		if ( $account_id === '' || $license_key === '' ) {
			return new \WP_Error( 'maxmind_credentials_incomplete', 'MaxMind account ID and license key are both required.' );
		}

		$downloader = new MaxMindDownloader();
		$verify = $downloader->verify_credentials( $account_id, $license_key );
		if ( is_wp_error( $verify ) ) {
			return $verify;
		}

		if ( ! FirewallSettings::store_geoip_license_key( $license_key ) ) {
			return new \WP_Error( 'maxmind_secret_store_failed', 'MaxMind license key could not be stored securely.' );
		}

		$settings = FirewallSettings::get();
		$settings['geoip_account_id'] = $account_id;
		$settings = FirewallSettings::sanitize( $settings );
		if (
			! FirewallSettings::persist_runtime_secrets( $settings )
			|| ! SettingsStore::save( SettingsStore::COL_FIREWALL, FirewallSettings::prepare_for_storage( $settings ) )
		) {
			return new \WP_Error( 'maxmind_secret_store_failed', 'MaxMind credentials could not be saved.' );
		}

		// Best effort: the weekly refresh cron was just scheduled by the
		// settings save, so a failed download here self-heals.
		$download = $downloader->download( $account_id, $license_key );

		return [
			'integration' => 'maxmind',
			'secret_name' => $secret_name,
			'verified'    => true,
			'database'    => [ 'usable' => ! is_wp_error( $download ) ],
		];
	}

	/**
	 * @param array<string,mixed> $metadata
	 * @return array<string,mixed>|\WP_Error
	 */
	private function store_cloudflare_secret( string $secret_name, string $plaintext, array $metadata ) {
		if ( ! class_exists( '\\SiteFort\\Modules\\Cloudflare\\Manager' ) ) {
			return new \WP_Error( 'cloudflare_module_unavailable', 'Cloudflare integration is unavailable on this site.' );
		}

		$manager = new CloudflareManager();
		$result = $manager->apply_transferred_secret( $secret_name, $plaintext, $metadata );
		if ( is_wp_error( $result ) ) {
			return $result;
		}

		return [
			'integration' => 'cloudflare',
			'secret_name' => $secret_name,
			'result'      => is_array( $result ) ? $result : [],
		];
	}
}
