<?php
/**
 * Notification importer service for seeding and importing notifications.
 *
 * @package RewardsWP
 */

namespace Awesomemotive\Rewardswp\Services;

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

use Awesomemotive\Rewardswp\Database\DatabaseHandler;
use Awesomemotive\Rewardswp\Database\Tables\NotificationsTable;
use Awesomemotive\Rewardswp\Models\Notification;
use DI\Container;
use Exception;
use function Awesomemotive\Rewardswp\Helpers\get_current_time;
use function Awesomemotive\Rewardswp\Helpers\rewardswp_log_error;
use function esc_html;

/**
 * Notification Importer Service
 *
 * Handles fetching and syncing notifications from remote feed.
 *
 * @since 1.1.0
 */
class NotificationImporterService {

	/**
	 * Remote feed URL.
	 *
	 * @since 1.1.0
	 * @var string
	 */
	private const REMOTE_FEED_URL = 'https://plugin.affiliatewp.com/wp-content/notifications-rwp.json';

	/**
	 * Database handler instance.
	 *
	 * @since 1.1.0
	 * @var DatabaseHandler
	 */
	private DatabaseHandler $db_handler;

	/**
	 * Notifications table instance.
	 *
	 * @since 1.1.0
	 * @var NotificationsTable
	 */
	private NotificationsTable $notifications_table;

	/**
	 * DI Container instance.
	 *
	 * @since 1.1.0
	 * @var Container
	 */
	private Container $container;

	/**
	 * Constructor.
	 *
	 * @since 1.1.0
	 *
	 * @param DatabaseHandler    $db_handler         Database handler instance.
	 * @param NotificationsTable $notifications_table Notifications table instance.
	 * @param Container          $container          DI Container instance.
	 */
	public function __construct(
		DatabaseHandler $db_handler,
		NotificationsTable $notifications_table,
		Container $container
	) {
		$this->db_handler          = $db_handler;
		$this->notifications_table = $notifications_table;
		$this->container           = $container;
	}

	/**
	 * Sync notifications from remote feed.
	 *
	 * Fetches notifications from the remote API and imports them locally.
	 * Updates existing notifications and inserts new ones.
	 *
	 * @since 1.1.0
	 * @return void
	 */
	public function sync(): void {
		try {
			$notifications = $this->fetch_notifications();

			if ( empty( $notifications ) ) {
				return;
			}

			foreach ( $notifications as $notification_data ) {
				try {
					$this->process_notification( $notification_data );
				} catch ( Exception $e ) {
					rewardswp_log_error(
						'Failed to process notification {notification_id}: {message}',
						[
							'notification_id' => $notification_data->id ?? 'unknown',
							'message'         => $e->getMessage(),
						]
					);
				}
			}
		} catch ( Exception $e ) {
			rewardswp_log_error(
				'Failed to sync notifications: {message}',
				[
					'message' => $e->getMessage(),
				]
			);
		}
	}

	/**
	 * Fetch notifications from remote feed.
	 *
	 * @since 1.1.0
	 * @return array Array of notification objects from JSON.
	 * @throws Exception If fetch fails.
	 */
	private function fetch_notifications(): array {
		$url = defined( 'REWARDSWP_NOTIFICATIONS_API_URL' )
			? REWARDSWP_NOTIFICATIONS_API_URL
			: self::REMOTE_FEED_URL;

		$response = wp_remote_get(
			$url,
			[
				'timeout' => 15,
				'headers' => [
					'Accept' => 'application/json',
				],
			]
		);

		if ( is_wp_error( $response ) ) {
			throw new Exception( esc_html( $response->get_error_message() ) );
		}

		$body = wp_remote_retrieve_body( $response );

		if ( empty( $body ) ) {
			return [];
		}

		$notifications = json_decode( $body );

		if ( json_last_error() !== JSON_ERROR_NONE ) {
			throw new Exception( 'Invalid JSON response from remote feed.' );
		}

		return is_array( $notifications ) ? $notifications : [];
	}

	/**
	 * Process a single notification.
	 *
	 * Validates, checks if it exists, and inserts or updates accordingly.
	 *
	 * @since 1.1.0
	 *
	 * @param object $notification_data Notification data from remote feed.
	 * @return void
	 * @throws Exception If validation or database operation fails.
	 */
	private function process_notification( $notification_data ): void {
		// Validate required fields.
		if ( empty( $notification_data->id ) || empty( $notification_data->title ) || empty( $notification_data->content ) ) {
			throw new Exception( 'Missing required notification fields (id, title, content).' );
		}

		// Skip if notification has expired.
		if ( ! empty( $notification_data->end ) && strtotime( $notification_data->end ) < time() ) {
			return;
		}

		// Check if notification already exists.
		$existing_id = $this->get_notification_by_remote_id( (string) $notification_data->id );

		if ( $existing_id ) {
			$this->update_notification( $existing_id, $notification_data );
		} else {
			$this->insert_notification( $notification_data );
		}
	}

	/**
	 * Get notification ID by remote ID.
	 *
	 * @since 1.1.0
	 *
	 * @param string $remote_id Remote notification ID.
	 * @return int|null Notification ID if found, null otherwise.
	 */
	private function get_notification_by_remote_id( string $remote_id ): ?int {
		$result = $this->db_handler->get_row(
			$this->notifications_table->get_prefixed_table_name(),
			[ 'remote_id' => $remote_id ],
			[ 'id' ]
		);

		return $result ? (int) $result->id : null;
	}

	/**
	 * Insert new notification.
	 *
	 * @since 1.1.0
	 *
	 * @param object $notification_data Notification data from remote feed.
	 * @return void
	 * @throws Exception If insert fails.
	 */
	private function insert_notification( $notification_data ): void {
		/**
		 * Notification model instance.
		 *
		 * @var Notification $notification
		 */
		$notification = $this->container->make( Notification::class );

		$notification->hydrate( $this->map_notification_data( $notification_data ) );
		$notification->create();
	}

	/**
	 * Update existing notification.
	 *
	 * @since 1.1.0
	 *
	 * @param int    $notification_id   Local notification ID.
	 * @param object $notification_data Notification data from remote feed.
	 * @return void
	 */
	private function update_notification( int $notification_id, $notification_data ): void {
		$data               = $this->map_notification_data( $notification_data );
		$data['updated_at'] = get_current_time();

		// Don't update dismissed status - preserve user's choice.
		unset( $data['dismissed'] );

		$this->db_handler->update(
			$this->notifications_table->get_prefixed_table_name(),
			$data,
			[ 'id' => $notification_id ]
		);
	}

	/**
	 * Map notification data from remote feed to database format.
	 *
	 * @since 1.1.0
	 *
	 * @param object $notification_data Notification data from remote feed.
	 * @return array Mapped data array.
	 */
	private function map_notification_data( $notification_data ): array {
		$buttons = null;
		if ( ! empty( $notification_data->btns ) ) {
			$buttons_array = [];
			foreach ( (array) $notification_data->btns as $btn_type => $btn_data ) {
				if ( ! empty( $btn_data->url ) && ! empty( $btn_data->text ) ) {
					$buttons_array[] = [
						'type' => ( 'main' === $btn_type ) ? 'primary' : 'secondary',
						'url'  => $btn_data->url,
						'text' => $btn_data->text,
					];
				}
			}
			$buttons = ! empty( $buttons_array ) ? wp_json_encode( $buttons_array ) : null;
		}

		$conditions = null;
		if ( ! empty( $notification_data->type ) && is_array( $notification_data->type ) ) {
			$conditions = wp_json_encode( $notification_data->type );
		}

		return [
			'remote_id'  => (string) $notification_data->id,
			'title'      => $notification_data->title,
			'content'    => $notification_data->content,
			'buttons'    => $buttons,
			'type'       => $notification_data->notification_type ?? 'success',
			'conditions' => $conditions,
			'start'      => $notification_data->start ?? null,
			'end'        => $notification_data->end ?? null,
		];
	}
}
