<?php

namespace WeDevs\DokanPro\CLI;

use WP_CLI;
use WP_CLI_Command;
use WeDevs\DokanPro\CLI\Traits\FieldFormatter;

/**
 * Manage the Dokan Pro license from the command line.
 *
 * Activating and deactivating run as an administrator, since Appsero requires `manage_options`.
 *
 * @since 5.0.17
 */
class LicenseCommand extends WP_CLI_Command {

    use FieldFormatter;

    /**
     * Activate the Dokan Pro license with a license key.
     *
     * ## OPTIONS
     *
     * <license-key>
     * : The Dokan Pro license key to activate.
     *
     * ## EXAMPLES
     *
     *     # Activate a license key
     *     $ wp dokan license activate XXXXXXXX-XXXXXXXX-XXXXXXXX-XXXXXXXX
     *     Success: Dokan Pro license activated.
     *
     * @since 5.0.17
     *
     * @when after_wp_load
     *
     * @param array $args       Positional arguments.
     * @param array $assoc_args Associative arguments.
     *
     * @return void
     */
    public function activate( $args, $assoc_args ) {
        $license_key = isset( $args[0] ) ? trim( $args[0] ) : '';

        if ( empty( $license_key ) ) {
            WP_CLI::error( __( 'Please provide a valid license key.', 'dokan' ) );
        }

        $result = $this->run_license_action( 'activate', $license_key );

        if ( empty( $result['success'] ) ) {
            WP_CLI::error( $this->message( $result, __( 'Failed to activate the license.', 'dokan' ) ) );
        }

        WP_CLI::success( $this->message( $result, __( 'Dokan Pro license activated.', 'dokan' ) ) );
    }

    /**
     * Deactivate the currently active Dokan Pro license.
     *
     * ## EXAMPLES
     *
     *     # Deactivate the active license
     *     $ wp dokan license deactivate
     *     Success: Dokan Pro license deactivated.
     *
     * @since 5.0.17
     *
     * @when after_wp_load
     *
     * @return void
     */
    public function deactivate() {
        if ( ! dokan_pro()->license->has_license_key() ) {
            WP_CLI::error( __( 'No license key found to deactivate.', 'dokan' ) );
        }

        $result = $this->run_license_action( 'deactivate' );

        if ( empty( $result['success'] ) ) {
            WP_CLI::error( $this->message( $result, __( 'Failed to deactivate the license.', 'dokan' ) ) );
        }

        WP_CLI::success( $this->message( $result, __( 'Dokan Pro license deactivated.', 'dokan' ) ) );
    }

    /**
     * Show the current Dokan Pro license status.
     *
     * ## OPTIONS
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - json
     *   - yaml
     * ---
     *
     * [--show-key]
     * : Print the license key in full instead of masking it.
     *
     * [--field=<field>]
     * : Print just the value of a single field.
     *
     * ## EXAMPLES
     *
     *     # Show license status
     *     $ wp dokan license status
     *
     *     # Output as JSON
     *     $ wp dokan license status --format=json
     *
     *     # Reveal the full license key
     *     $ wp dokan license status --show-key
     *
     * @since 5.0.17
     *
     * @when after_wp_load
     *
     * @param array $args       Positional arguments.
     * @param array $assoc_args Associative arguments.
     *
     * @return void
     */
    public function status( $args, $assoc_args ) {
        $license  = dokan_pro()->license;
        $show_key = (bool) WP_CLI\Utils\get_flag_value( $assoc_args, 'show-key', false );

        $status = [
            'valid'       => $license->is_valid() ? 'yes' : 'no',
            'plan'        => $license->get_plan(),
            'key'         => $this->mask_key( $license->get_license_key(), $show_key ),
            'expiry_days' => $license->get_expiry_days(),
            'source_id'   => $license->get_license_source_id(),
        ];

        $this->render_fields( $status, $assoc_args );
    }

    /**
     * Resolves a human readable message from a license action result.
     *
     * @since 5.0.17
     *
     * @param array  $result   Result returned by the license manager.
     * @param string $fallback Fallback message when none is provided.
     *
     * @return string
     */
    protected function message( $result, $fallback ) {
        return ! empty( $result['message'] ) ? $result['message'] : $fallback;
    }

    /**
     * Mask a license key so `status` output is safe to leave in a shell history or CI log.
     *
     * @since 5.0.17
     *
     * @param string $license_key Stored license key.
     * @param bool   $show        Whether the caller asked for the key in full.
     *
     * @return string
     */
    protected function mask_key( $license_key, $show ) {
        if ( $show ) {
            return $license_key;
        }

        $length = strlen( $license_key );

        // Keep the last four characters so an operator can still tell two keys apart.
        return $length > 4
            ? str_repeat( '*', $length - 4 ) . substr( $license_key, -4 )
            : str_repeat( '*', $length );
    }

    /**
     * Run a license action as a user allowed to manage the license.
     *
     * @since 5.0.17
     *
     * @param string $action      Either 'activate' or 'deactivate'.
     * @param string $license_key License key, required for activation.
     *
     * @return array
     */
    protected function run_license_action( $action, $license_key = '' ) {
        // Appsero's handler requires `manage_options`; WP-CLI runs as nobody unless --user is passed.
        $previous_user = get_current_user_id();

        wp_set_current_user( $this->resolve_manager_user() );

        try {
            return 'deactivate' === $action
                ? dokan_pro()->license->deactivate_license()
                : dokan_pro()->license->activate_license( $license_key );
        } finally {
            wp_set_current_user( $previous_user );
        }
    }

    /**
     * Resolve a user allowed to manage the license.
     *
     * Honours an explicit `--user` when that account qualifies, otherwise falls back
     * to the lowest-ID administrator on the current site.
     *
     * @since 5.0.17
     *
     * @return int
     */
    protected function resolve_manager_user() {
        $current_user = get_current_user_id();

        if ( user_can( $current_user, 'manage_options' ) ) {
            return $current_user;
        }

        $admins = get_users(
            [
                'role'    => 'administrator',
                'number'  => 1,
                'orderby' => 'ID',
                'order'   => 'ASC',
                'fields'  => 'ID',
            ]
        );

        if ( empty( $admins ) ) {
            WP_CLI::error( __( 'No administrator account found to run this license action. Pass --user=<id|login> with an account that can manage_options.', 'dokan' ) );
        }

        $admin_id = (int) $admins[0];

        // Only surprising when --user was explicit; running as an admin with no current user is the documented default.
        if ( $current_user > 0 ) {
            WP_CLI::warning(
                sprintf(
                    /* translators: 1: requested user id, 2: administrator id used instead */
                    __( 'User #%1$d cannot manage the license; running as administrator #%2$d instead.', 'dokan' ),
                    $current_user,
                    $admin_id
                )
            );
        }

        return $admin_id;
    }
}
