<?php

/**
 * Handles storage of Hub Account Meta in WP posts tables.
 *
 * @package northmill-online-shared
 */
declare (strict_types=1);
namespace Northmill_Online_Shared_Sync\Northmill_Online_Shared\Abstracts\Hub_API\Hub_Accounts;

use Northmill_Online_Shared_Sync\Northmill_Online_Shared\Abstract_Shared_Base;
use Northmill_Online_Shared_Sync\Northmill_Online_Shared\Abstracts\Hub_API\Hub_Accounts\Models\Article_Account_Code_Model;
use Northmill_Online_Shared_Sync\Northmill_Online_Shared\Abstracts\Hub_API\Hub_Accounts\Models\Hub_Account_Model;
use Northmill_Online_Shared_Sync\Northmill_Online_Shared\Abstracts\Hub_API\Hub_Accounts\Models\Income_Account_Code_Model;
use Northmill_Online_Shared_Sync\Northmill_Online_Shared\Abstracts\Hub_API\Hub_Accounts\Models\Shipping_Model;
use Northmill_Online_Shared_Sync\Northmill_Online_Shared\Abstracts\Hub_API\Hub_Accounts\Models\Vat_Account_Code_Model;
use Northmill_Online_Shared_Sync\Northmill_Online_Shared\Modules\Loggers\Log_Level_Enum;
use Throwable;
defined('ABSPATH') || exit;
/**
 * Class Hub_Account_Repository
 */
class WP_Repository_Metas
{
    /**
     * The prefix for the custom post-type meta-fields.
     */
    public const CPT_META_PREFIX = 'nm_hub_account_';
    /**
     * Get a single meta-field for a Hub account.
     *
     * @param int    $account_id Hub Account WP Post ID.
     * @param string $property   Property to get.
     *
     * @return mixed
     */
    public static function get_single(int $account_id, string $property): mixed
    {
        $default_if_not_exists = match ($property) {
            'enabled' => \true,
            'allowed_shipping_methods' => array(),
            default => '',
        };
        $full_property_name = self::CPT_META_PREFIX . $property;
        if (!metadata_exists('post', $account_id, $full_property_name)) {
            return $default_if_not_exists;
        }
        $meta = get_post_meta($account_id, $full_property_name, \true);
        switch ($property) {
            case 'article_account_codes':
            case 'income_account_codes':
            case 'vat_account_codes':
            case 'shipping_settings':
                $meta = self::get_json_stored_meta($property, $meta, $default_if_not_exists);
                break;
        }
        return $meta;
    }
    /**
     * Get a JSON stored meta-field for a Hub account.
     *
     * @param string $property      Property to get.
     * @param string $meta_json     JSON string to decode.
     * @param mixed  $default_value Default value to return if decoding fails.
     *
     * @return mixed
     */
    private static function get_json_stored_meta(string $property, string $meta_json, mixed $default_value = null): mixed
    {
        $meta = json_decode($meta_json, \true);
        if (!self::was_json_valid($meta_json) || !is_array($meta)) {
            return $default_value;
        }
        switch ($property) {
            case 'article_account_codes':
                $meta = self::get_models_array($meta, Article_Account_Code_Model::class, 'get_account_code');
                break;
            case 'income_account_codes':
                $meta = self::get_models_array($meta, Income_Account_Code_Model::class, 'get_payment_method_id');
                break;
            case 'vat_account_codes':
                $meta = self::get_models_array($meta, Vat_Account_Code_Model::class, 'get_vat_rate');
                break;
            case 'shipping_settings':
                $meta = new Shipping_Model($meta);
                break;
        }
        return $meta;
    }
    /**
     * Get an array of a data model, based on the meta stored in JSON format.
     *
     * @param array       $meta          Meta-data from JSON to process.
     * @param string      $model_class   The class to instantiate for each model.
     * @param string|null $method_as_key Optional method name whose returned value to use as the key in the resulting array.
     *
     * @return object[]
     */
    private static function get_models_array(array $meta, string $model_class, ?string $method_as_key = null): array
    {
        $processed_meta = array();
        foreach ($meta as $model_data) {
            $model = new $model_class($model_data);
            if ($method_as_key) {
                $processed_meta[$model->{$method_as_key}()] = $model;
            } else {
                $processed_meta[] = $model;
            }
        }
        return $processed_meta;
    }
    /**
     * Check if the last JSON decoding was successful.
     *
     * @param string $json JSON string that was decoded.
     *
     * @return bool
     */
    private static function was_json_valid(string $json): bool
    {
        $json_error = json_last_error();
        if (\JSON_ERROR_NONE !== $json_error) {
            Abstract_Shared_Base::base_instance()->logger->log('hub-accounts', 'Failed to decode JSON stored meta', array('meta' => $json, 'error' => $json_error, 'error_msg' => json_last_error_msg()), Log_Level_Enum::ERROR);
            return \false;
        }
        return \true;
    }
    /**
     * Update all meta-fields for a Hub account.
     *
     * @param Hub_Account_Model $account The account to update.
     *
     * @return void
     */
    public static function update(Hub_Account_Model $account): void
    {
        self::update_single($account->wp_id, 'display_name', $account->display_name ?? '');
        self::update_single($account->wp_id, 'url', $account->url);
        self::update_single($account->wp_id, 'user', $account->user);
        self::update_single($account->wp_id, 'pass', $account->pass);
        self::update_single($account->wp_id, 'type', $account->type);
        self::update_single($account->wp_id, 'env', $account->env);
        self::update_single($account->wp_id, 'enabled', $account->is_enabled());
        self::update_single($account->wp_id, 'allowed_shipping_methods', $account->allowed_shipping_methods);
        self::update_single($account->wp_id, 'org_name', $account->org_name);
        self::update_single($account->wp_id, 'org_num', $account->org_num);
        self::update_single($account->wp_id, 'last_sync', $account->last_sync);
        self::update_single($account->wp_id, 'article_account_codes', $account->get_article_account_codes());
        self::update_single($account->wp_id, 'income_account_codes', $account->get_income_account_codes());
        self::update_single($account->wp_id, 'vat_account_codes', $account->get_vat_account_codes());
        self::update_single($account->wp_id, 'shipping_settings', $account->get_shipping_settings());
        self::update_single($account->wp_id, 'partial_refund_article_sku', $account->partial_refund_article_sku);
    }
    /**
     * Update specific property on a Hub account.
     *
     * @param int    $account_id Hub account WP Post ID.
     * @param string $property   Property to update.
     * @param mixed  $value      Value to set.
     *
     * @return bool Update status. True on update, false on failure or no change.
     */
    public static function update_single(int $account_id, string $property, mixed $value): bool
    {
        if (!property_exists(Hub_Account_Model::class, $property)) {
            Abstract_Shared_Base::base_instance()->logger->log('hub-accounts', 'Trying to update non-existing property on Hub account', array('property' => $property, 'account' => $account_id), Log_Level_Enum::ERROR);
            return \false;
        }
        $full_property_name = self::CPT_META_PREFIX . $property;
        try {
            // Try setting the property on a temporary model to see if it is of the correct type.
            $temp_account = new Hub_Account_Model(array(), \false);
            if (method_exists($temp_account, 'set_' . $property)) {
                $temp_account->{'set_' . $property}($value);
            } else {
                $temp_account->{$property} = $value;
            }
        } catch (Throwable $e) {
            Abstract_Shared_Base::base_instance()->logger->log('hub-accounts', 'Trying to update property on Hub account with incorrect type', array('property' => $property, 'account' => $account_id, 'value' => $value, 'error' => $e->getMessage()), Log_Level_Enum::ERROR);
            return \false;
        }
        $value = self::maybe_json_encode($value, $property);
        return (bool) update_post_meta($account_id, $full_property_name, $value);
    }
    /**
     * Maybe JSON encode a value based on the property.
     *
     * @param mixed  $value    Value to encode.
     * @param string $property Property to check.
     *
     * @return mixed
     */
    private static function maybe_json_encode(mixed $value, string $property): mixed
    {
        switch ($property) {
            case 'article_account_codes':
            case 'income_account_codes':
            case 'vat_account_codes':
            case 'shipping_settings':
                $value = wp_json_encode($value);
                break;
        }
        return $value;
    }
}
