<?php

namespace Automattic\WCShipping\Connect;

class WC_Connect_Service_Schemas_Store {

	/**
	 * @var WC_Connect_API_Client
	 */
	protected $api_client;

	/**
	 * @var WC_Connect_Logger
	 */
	protected $logger;

	public function __construct( WC_Connect_API_Client $api_client, WC_Connect_Logger $logger ) {

		$this->api_client = $api_client;
		$this->logger     = $logger;
	}

	public function fetch_service_schemas_from_connect_server() {

		$response_body = $this->api_client->get_service_schemas();

		if ( is_wp_error( $response_body ) ) {
			$error_data = $response_body->get_error_data();
			if ( isset( $error_data['response_status_code'] ) ) {
				$this->update_last_fetch_result_code( $error_data['response_status_code'] );
			}
			$this->logger->log( $response_body, __METHOD__ );
			return false;
		}
		$this->update_last_fetch_result_code( '200' );

		$this->logger->log( 'Successfully loaded service schemas from server response.', __METHOD__ );
		$this->update_last_fetch_timestamp();
		$this->maybe_update_heartbeat();

		$old_schemas = $this->get_service_schemas();
		if ( $old_schemas == $response_body ) {
			// schemas weren't changed, but were fetched without problems
			return true;
		}

		// If we made it this far, it is safe to store the object
		return $this->update_service_schemas( $response_body );
	}

	public function get_service_schemas() {
		return WC_Connect_Options::get_option( 'services', null );
	}

	protected function update_service_schemas( $service_schemas ) {
		return WC_Connect_Options::update_option( 'services', $service_schemas );
	}

	public function get_last_fetch_timestamp() {
		return WC_Connect_Options::get_option( 'services_last_update', null );
	}

	protected function update_last_fetch_timestamp() {
		WC_Connect_Options::update_option( 'services_last_update', time() );
	}

	public function get_last_fetch_result_code() {
		return WC_Connect_Options::get_option( 'services_last_result_code' );
	}

	/**
	 * @param int $result_status_code
	 */
	protected function update_last_fetch_result_code( $result_status_code ) {
		WC_Connect_Options::update_option( 'services_last_result_code', $result_status_code );
	}

	protected function maybe_update_heartbeat() {
		$last_heartbeat = WC_Connect_Options::get_option( 'last_heartbeat' );
		$now            = time();

		if ( ! $last_heartbeat ) {
			$should_update = true;
		} else {
			$last_heartbeat = absint( $last_heartbeat );
			if ( $last_heartbeat > $now ) {
				// last heartbeat in the future? wacky
				$should_update = true;
			} else {
				$elapsed       = $now - $last_heartbeat;
				$should_update = $elapsed > DAY_IN_SECONDS;
			}
		}

		if ( $should_update ) {
			WC_Connect_Options::update_option( 'last_heartbeat', $now );
		}
	}

	/**
	 * Returns all service ids of a specific type (e.g. shipping)
	 *
	 * @param string $type The type of services to return
	 *
	 * @return array An array of that type's service ids, or an empty array if no such type is known
	 */
	public function get_all_service_ids_of_type( $type ) {

		if ( empty( $type ) ) {
			return array();
		}

		$service_schemas = $this->get_service_schemas();
		if ( ! is_object( $service_schemas ) || ! property_exists( $service_schemas, $type ) || ! is_array( $service_schemas->$type ) ) {
			return array();
		}

		$service_schema_ids = array();
		foreach ( $service_schemas->$type as $service_schema ) {
			$service_schema_ids[] = $service_schema->id;
		}

		return $service_schema_ids;
	}

	/**
	 * Returns all shipping method ids
	 *
	 * @return array|bool An array of supported shipping method ids or false if schema does not support method_id
	 */
	public function get_all_shipping_method_ids() {
		$shipping_method_ids = array();
		$service_schemas     = $this->get_service_schemas();
		if ( ! is_object( $service_schemas ) || ! property_exists( $service_schemas, 'shipping' ) || ! is_array( $service_schemas->shipping ) ) {
			return $shipping_method_ids;
		}

		foreach ( $service_schemas->shipping as $service_schema ) {
			if ( ! property_exists( $service_schema, 'method_id' ) ) {
				continue;
			}

			$shipping_method_ids[] = $service_schema->method_id;
		}

		return $shipping_method_ids;
	}

	/**
	 * Returns a particular service's schema given its id
	 *
	 * @param string $service_id The service id for which to return the schema
	 *
	 * @return object|null The service schema or null if no such id was found
	 */
	public function get_service_schema_by_id( $service_id ) {
		$service_schemas = $this->get_service_schemas();
		if ( ! is_object( $service_schemas ) ) {
			return null;
		}

		foreach ( $service_schemas as $service_type => $service_type_service_schemas ) {
			$matches = wp_filter_object_list( $service_type_service_schemas, array( 'id' => $service_id ) );
			if ( $matches ) {
				return array_shift( $matches );
			}
		}

		return null;
	}

	/**
	 * Returns a particular service's schema given its method_id
	 *
	 * @param $method_id
	 *
	 * @return object|null The service schema or null if no such id was found
	 */
	public function get_service_schema_by_method_id( $method_id ) {
		$service_schemas = $this->get_service_schemas();
		if ( ! is_object( $service_schemas ) ) {
			return null;
		}

		foreach ( $service_schemas as $service_type => $service_type_service_schemas ) {
			$matches = wp_filter_object_list( $service_type_service_schemas, array( 'method_id' => $method_id ) );
			if ( $matches ) {
				return array_shift( $matches );
			}
		}

		return null;
	}

	/**
	 * Returns a service's schema given its shipping zone instance
	 *
	 * @param string $instance_id The shipping zone instance id for which to return the schema
	 *
	 * @return object|null The service schema or null if no such instance was found
	 */
	public function get_service_schema_by_instance_id( $instance_id ) {
		global $wpdb;
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$method_id = $wpdb->get_var(
			$wpdb->prepare(
				"SELECT method_id FROM {$wpdb->prefix}woocommerce_shipping_zone_methods WHERE instance_id = %d;",
				$instance_id
			)
		);

		return $this->get_service_schema_by_method_id( $method_id );
	}

	/**
	 * Returns a service's schema given an id or shipping zone instance.
	 *
	 * @param string $id_or_instance_id String ID or numeric instance ID.
	 * @return object|null Service schema on success, null on failure
	 */
	public function get_service_schema_by_id_or_instance_id( $id_or_instance_id ) {

		if ( is_numeric( $id_or_instance_id ) ) {
			return $this->get_service_schema_by_instance_id( $id_or_instance_id );
		}

		if ( ! empty( $id_or_instance_id ) ) {
			return $this->get_service_schema_by_method_id( $id_or_instance_id );
		}

		return null;
	}

	/**
	 * Returns packages schema
	 *
	 * @return object|null Packages schema on success, null on failure
	 */
	public function get_packages_schema() {
		$service_schemas = $this->get_service_schemas();
		if ( ! is_object( $service_schemas ) || ! property_exists( $service_schemas, 'boxes' ) ) {
			return null;
		}

		return $service_schemas->boxes;
	}

	/**
	 * @return array|null
	 */
	public function get_predefined_packages_schema() {
		$service_schemas = $this->get_service_schemas();
		if ( ! is_object( $service_schemas ) ) {
			return null;
		}

		$predefined_packages = array();
		foreach ( $service_schemas->shipping as $service_schema ) {
			if ( ! isset( $service_schema->packages ) ) {
				continue;
			}

			$predefined_packages[ $service_schema->id ] = $service_schema->packages;
		}

		return $predefined_packages;
	}
}