<?php
/**
 * Factory for article availabilities, when dealing with DB data.
 *
 * @package flo-sync
 */

namespace NMFS\Sync\Items\Article_Availabilities;

use InvalidArgumentException;
use NMFS\Sync\Items\Article_Availabilities\Models\Article_Availability_Model;
use NMFS\Sync\Items\Article_Availabilities\Models\Schedules\Day_Of_Week_Schedule_Model;
use WP_Error;
use WP_Post;

defined( 'ABSPATH' ) || exit;

/**
 * Class Factory_DB
 */
class Factory_DB {

	/**
	 * Mapping of string keys to class names.
	 *
	 * TODO: Add MonthAvailability and DateAvailability when implemented in Hub.
	 *
	 * @var array<string, string>
	 */
	private static array $schedule_map = array(
		'schedule_day_of_week' => Day_Of_Week_Schedule_Model::class,
	);

	/**
	 * Get the class name based on the key.
	 *
	 * @param string $key The key.
	 * @return string
	 * @throws InvalidArgumentException If the key is invalid.
	 */
	public static function get_class_from_key( string $key ): string {
		if ( ! isset( self::$schedule_map[ $key ] ) ) {
			throw new InvalidArgumentException( 'Invalid schedule key: ' . esc_html( $key ) );
		}

		return self::$schedule_map[ $key ];
	}

	/**
	 * Get the key based on the class name.
	 *
	 * @param string $class_name The class name.
	 * @return string
	 * @throws InvalidArgumentException If the class is not valid.
	 */
	public static function get_key_from_class( string $class_name ): string {
		$key = array_search( $class_name, self::$schedule_map, true );
		if ( false === $key ) {
			throw new InvalidArgumentException( 'Class not found in schedule map: ' . esc_html( $class_name ) );
		}

		return $key;
	}

	/**
	 * Create an article availability model from the database.
	 *
	 * @param WP_Post $post The WordPress post object.
	 *
	 * @return Article_Availability_Model|null The created article availability model, or null on data error.
	 */
	public static function create_availability_from_db( WP_Post $post ): ?Article_Availability_Model {
		$model_json = get_post_meta( $post->ID, Article_Availabilities::META_KEY_MODEL, true );

		return Article_Availability_Model::jsonUnserialize( $model_json );
	}

	/**
	 * Update article availability in the database.
	 *
	 * @param Article_Availability_Model $article_availability_model The article availability model.
	 *
	 * @return array With keys 'wp_id' and 'action'. Action is either 'add' or 'update'.
	 */
	public static function update_in_db( Article_Availability_Model $article_availability_model ): array {
		if ( empty( $article_availability_model->wp_id ) ) {
			$id = Article_Availabilities::get_availability_by_hub_id( $article_availability_model->hub_id );

			if ( ! $id ) {
				$added_wp_id = self::store_in_db( $article_availability_model );

				if ( is_wp_error( $added_wp_id ) ) {
					return array(
						'action' => 'error',
					);
				}

				return array(
					'wp_id'  => $added_wp_id,
					'action' => 'add',
				);
			}

			$article_availability_model->wp_id = $id;
		}

		$updated_wp_id = wp_update_post(
			array(
				'ID'          => $article_availability_model->wp_id,
				'post_title'  => $article_availability_model->name,
				'post_status' => 'publish',
			)
		);

		if ( is_wp_error( $updated_wp_id ) ) {
			return array(
				'action' => 'error',
			);
		}

		self::update_meta( $updated_wp_id, $article_availability_model );

		return array(
			'wp_id'  => $updated_wp_id,
			'action' => 'update',
		);
	}

	/**
	 * Store the article availability in the database.
	 *
	 * @param Article_Availability_Model $article_availability_model The article availability model.
	 *
	 * @return int|WP_Error The ID of the stored article availability.
	 */
	private static function store_in_db( Article_Availability_Model $article_availability_model ): int|WP_Error {
		$post_id = wp_insert_post(
			array(
				'post_type'   => Setup::CPT_KEY,
				'post_title'  => $article_availability_model->name,
				'post_status' => 'publish',
			)
		);

		if ( is_wp_error( $post_id ) ) {
			return $post_id;
		}

		$article_availability_model->wp_id = $post_id;

		self::update_meta( $post_id, $article_availability_model );

		return $post_id;
	}

	/**
	 * Update metadata for the article availability in the database.
	 *
	 * @param int                        $post_id                    The post ID of the article.
	 * @param Article_Availability_Model $article_availability_model The article availability model.
	 *
	 * @return void
	 */
	private static function update_meta( int $post_id, Article_Availability_Model $article_availability_model ): void {
		$model_json = wp_json_encode( $article_availability_model );
		update_post_meta( $post_id, Article_Availabilities::META_KEY_MODEL, $model_json );
		update_post_meta( $post_id, Article_Availabilities::META_KEY_HUB_ID, $article_availability_model->hub_id );
		update_post_meta( $post_id, Article_Availabilities::META_KEY_ENABLED, $article_availability_model->enabled );
	}
}
