<?php

namespace SugarCalendarBookings\Integrations\Outlook;

use SugarCalendarBookings\Integrations\OAuthRelay\OAuthConnectionInterface;
use SugarCalendarBookings\Integrations\OAuthRelay\OAuthConnectionModel;

/**
 * Per-connection Outlook calendar settings repository.
 *
 * Owns the read/save of the JSON `settings` column on oauth_connections rows
 * and the cached Graph calendars list. Multi-calendar-per-connection growth
 * lands here.
 *
 * @since 1.5.0
 */
class OutlookCalendarSettingsRepository {

	/**
	 * Calendars cache TTL in seconds (5 minutes).
	 *
	 * Short enough that calendar additions/deletions in Outlook are visible
	 * on the next settings page open without manual cache busting.
	 *
	 * @since 1.5.0
	 *
	 * @var int
	 */
	private const CALENDARS_CACHE_TTL = 5 * MINUTE_IN_SECONDS;

	/**
	 * Outlook actions client used to refresh the cached calendars list.
	 *
	 * @since 1.5.0
	 *
	 * @var OutlookActionsClient
	 */
	private OutlookActionsClient $client;

	/**
	 * Constructor.
	 *
	 * @since 1.5.0
	 *
	 * @param OutlookActionsClient $client Outlook actions client.
	 */
	public function __construct( OutlookActionsClient $client ) {

		$this->client = $client;
	}

	/**
	 * Load the decoded settings array for a connection.
	 *
	 * Tolerates both a string column value and an already-decoded array,
	 * returning an array in either case.
	 *
	 * @since 1.5.0
	 *
	 * @param OAuthConnectionModel $connection OAuth connection.
	 *
	 * @return array
	 */
	public function load( OAuthConnectionModel $connection ): array {

		$raw = isset( $connection->settings ) ? $connection->settings : null;

		if ( is_array( $raw ) ) {
			return $raw;
		}

		if ( is_string( $raw ) && $raw !== '' ) {
			$decoded = json_decode( $raw, true );

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

		return [];
	}

	/**
	 * Persist a settings array back to the connection row.
	 *
	 * @since 1.5.0
	 *
	 * @param OAuthConnectionModel $connection OAuth connection.
	 * @param array                $settings   Settings array to persist.
	 */
	public function save( OAuthConnectionModel $connection, array $settings ): void {

		$connection->settings = $settings;

		$connection->save();
	}

	/**
	 * List the calendars available to this connection, refreshing the cache when stale.
	 *
	 * Reads the cached `calendars_cache` from the connection's settings. When
	 * missing or older than the TTL, fetches a fresh list via the Graph client
	 * and persists it back to the connection.
	 *
	 * @since 1.5.0
	 *
	 * @param OAuthConnectionModel     $connection OAuth connection.
	 * @param OAuthConnectionInterface $wrapped    Wrapped connection used for the Graph call.
	 *
	 * @return array Array of `{ id, name, is_default }` entries.
	 */
	public function list_calendars( OAuthConnectionModel $connection, OAuthConnectionInterface $wrapped ): array {

		$settings  = $this->load( $connection );
		$cached    = isset( $settings['calendars_cache'] ) && is_array( $settings['calendars_cache'] )
			? $settings['calendars_cache']
			: [];
		$cached_at = isset( $settings['calendars_cache_at'] ) ? strtotime( (string) $settings['calendars_cache_at'] ) : 0;
		$is_fresh  = $cached_at > 0 && ( time() - $cached_at ) < self::CALENDARS_CACHE_TTL;

		if ( ! empty( $cached ) && $is_fresh ) {
			return $this->normalize_calendars_list( $cached );
		}

		$response = $this->client->list_calendars( $wrapped );

		if ( is_wp_error( $response ) ) {
			return $this->normalize_calendars_list( $cached );
		}

		$calendars  = $this->extract_calendars_from_response( $response );
		$normalized = $this->normalize_calendars_list( $calendars );

		// Persist refreshed cache back to the connection settings.
		$settings['calendars_cache']    = $normalized;
		$settings['calendars_cache_at'] = gmdate( 'Y-m-d\TH:i:s\Z' );

		$this->save( $connection, $settings );

		/**
		 * Filter the Outlook calendars list rendered in the UI.
		 *
		 * @since 1.5.0
		 *
		 * @param array                $normalized List of `{ id, name }` entries.
		 * @param OAuthConnectionModel $connection Connection the list was fetched for.
		 */
		return (array) apply_filters( 'sugar_calendar_bookings_outlook_calendars_list', $normalized, $connection );
	}

	/**
	 * Normalize a calendars list into `{ id, name, is_default }` entries.
	 *
	 * @since 1.5.0
	 *
	 * @param array $calendars Raw calendars list.
	 *
	 * @return array
	 */
	private function normalize_calendars_list( array $calendars ): array {

		$out = [];

		foreach ( $calendars as $calendar ) {
			if ( ! is_array( $calendar ) ) {
				continue;
			}

			$id   = isset( $calendar['id'] ) ? (string) $calendar['id'] : '';
			$name = isset( $calendar['name'] ) ? (string) $calendar['name'] : $id;

			if ( $id === '' ) {
				continue;
			}

			// Microsoft Graph flags the account's primary calendar with
			// `isDefaultCalendar`. Preserve it (and tolerate the already-normalized
			// `is_default` key so re-normalizing a cached list keeps the flag) for
			// the settings UI to pre-select. The primary calendar's name is
			// localized per account language, so the flag — not the name — is the
			// reliable way to identify it.
			$is_default = ! empty( $calendar['isDefaultCalendar'] ) || ! empty( $calendar['is_default'] );

			$out[] = [
				'id'         => $id,
				'name'       => $name,
				'is_default' => $is_default,
			];
		}

		return $out;
	}

	/**
	 * Extract a calendars list from a Graph response.
	 *
	 * Microsoft Graph returns calendars under the `value` array.
	 *
	 * @since 1.5.0
	 *
	 * @param array $response Graph response payload.
	 *
	 * @return array
	 */
	private function extract_calendars_from_response( array $response ): array {

		if ( isset( $response['value'] ) && is_array( $response['value'] ) ) {
			return $response['value'];
		}

		return $response;
	}
}
