<?php

namespace WeDevs\AppBridge\Core\Notifications\Drivers;

use WeDevs\AppBridge\Core\Notifications\Contracts\NotificationDriverInterface;
use WeDevs\AppBridge\Core\Notifications\Contracts\OneSignalMessage;
use WeDevs\AppBridge\Core\Notifications\Notification;

/**
 * OneSignal transport.
 *
 * Wire payload mirrors the legacy `OnesignalIntegration::send()` shape so
 * the mobile apps see the exact same fields they always have:
 *
 *   {
 *     "external_id":      "<uuid>",            // dedup, derived from message
 *     "app_id":           "...",
 *     "headings":         { "en": "<title>" },
 *     "contents":         { "en": "<message>" },
 *     "included_segments":["All"],              // or audience targeting
 *     "isIos":            1,
 *     "isAndroid":        1,
 *     "large_icon":       "<url>",              // when icon supplied
 *     "data": {
 *       "action_type":    "...",                // legacy routing
 *       "action_type_id": "...",
 *       ...                                     // arbitrary extras (event, channel, etc.)
 *     }
 *   }
 */
class OneSignalDriver implements NotificationDriverInterface {

	private const ENDPOINT = 'https://onesignal.com/api/v1/notifications';

	private const LAST_UPDATED_OPTION = 'app_bridge_onesignal_last_updated';

	private string $app_id;
	private string $api_key;

	public function __construct( string $app_id, string $api_key ) {
		$this->app_id  = $app_id;
		$this->api_key = $api_key;
	}

	public function send( Notification $notification ): bool {
		if ( ! $notification instanceof OneSignalMessage ) {
			return false;
		}

		if ( '' === $this->app_id || '' === $this->api_key ) {
			return false;
		}

		$payload = $notification->to_onesignal();
		$title   = (string) ( $payload['title'] ?? '' );
		$message = (string) ( $payload['message'] ?? '' );

		$body = [
			'external_id' => self::uuid( $message ),
			'app_id'      => $this->app_id,
			'headings'    => [ 'en' => $title ],
			'contents'    => [ 'en' => $message ],
			'isIos'       => 1,
			'isAndroid'   => 1,
		];

		// Audience targeting — pass through whichever the message specifies.
		// Defaults to `included_segments: ['All']` when nothing is set.
		$audience_keys = [
			'included_segments',
			'excluded_segments',
			'include_player_ids',
			'include_external_user_ids',
			'include_aliases',
			'filters',
		];

		$has_audience = false;
		foreach ( $audience_keys as $key ) {
			if ( ! empty( $payload[ $key ] ) ) {
				$body[ $key ] = $payload[ $key ];
				$has_audience = true;
			}
		}

		if ( ! $has_audience ) {
			$body['included_segments'] = [ 'All' ];
		}

		if ( ! empty( $payload['icon'] ) ) {
			$body['large_icon'] = $payload['icon'];
		}

		// Compose `data`: caller-supplied data array + legacy action_type/action_type_id.
		$data = (array) ( $payload['data'] ?? [] );

		if ( ! empty( $payload['action_type'] ) ) {
			$data['action_type'] = (string) $payload['action_type'];
		}
		if ( ! empty( $payload['action_type_id'] ) ) {
			$data['action_type_id'] = (string) $payload['action_type_id'];
		}

		if ( ! empty( $data ) ) {
			$body['data'] = $data;
		}

		$response = wp_remote_post(
			self::ENDPOINT,
			[
				'headers' => [
					'Content-Type'  => 'application/json;charset=utf-8',
					'Authorization' => 'Basic ' . $this->api_key,
				],
				'body'    => wp_json_encode( $body ),
				'timeout' => 30,
			]
		);

		return ! is_wp_error( $response ) && wp_remote_retrieve_response_code( $response ) < 300;
	}

	/**
	 * Build a stable UUID from the message + a per-minute timestamp.
	 *
	 * Mirrors the legacy dedup trick so the same message body fired multiple
	 * times within the same minute collapses into one OneSignal delivery
	 * (workaround for the WP 5.0 duplicate notification issue).
	 */
	private static function uuid( string $message ): string {
		$now         = explode( ':', gmdate( 'z:H:i' ) );
		$now_minutes = ( (int) $now[0] ) * 60 * 24 + ( (int) $now[1] ) * 60 + (int) $now[2];

		$prev_minutes = get_option( self::LAST_UPDATED_OPTION );

		if ( false !== $prev_minutes && ( $now_minutes - (int) $prev_minutes ) > 0 ) {
			update_option( self::LAST_UPDATED_OPTION, $now_minutes );
			$timestamp = $now_minutes;
		} elseif ( false === $prev_minutes ) {
			add_option( self::LAST_UPDATED_OPTION, $now_minutes );
			$timestamp = $now_minutes;
		} else {
			$timestamp = (int) $prev_minutes;
		}

		$sha1 = substr( sha1( $message . $timestamp ), 0, 32 );

		return sprintf(
			'%s-%s-%s-%s-%s',
			substr( $sha1, 0, 8 ),
			substr( $sha1, 8, 4 ),
			substr( $sha1, 12, 4 ),
			substr( $sha1, 16, 4 ),
			substr( $sha1, 20, 12 )
		);
	}
}
