<?php

namespace WeDevs\DokanPro\CLI;

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

/**
 * Activate, deactivate and inspect Dokan Pro modules from the command line.
 *
 * @since 5.0.17
 */
class ModuleCommand extends WP_CLI_Command {

    use FieldFormatter;

    /**
     * List Dokan Pro modules.
     *
     * ## OPTIONS
     *
     * [--status=<status>]
     * : Filter modules by status.
     * ---
     * default: all
     * options:
     *   - all
     *   - active
     *   - inactive
     *   - available
     * ---
     *
     * [--fields=<fields>]
     * : Limit the output to specific fields. Defaults to id,name,status,available,plan.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - json
     *   - yaml
     *   - count
     * ---
     *
     * ## EXAMPLES
     *
     *     # List every module
     *     $ wp dokan module list
     *
     *     # List active modules only
     *     $ wp dokan module list --status=active
     *
     *     # Limit the columns and output as CSV
     *     $ wp dokan module list --fields=id,status --format=csv
     *
     * @since 5.0.17
     *
     * @subcommand list
     * @when after_wp_load
     *
     * @param array $args       Positional arguments.
     * @param array $assoc_args Associative arguments.
     *
     * @return void
     */
    public function list_modules( $args, $assoc_args ) {
        $module_manager    = dokan_pro()->module;
        $all_modules       = $module_manager->get_all_modules();
        $active_modules    = $this->get_stored_modules();
        $available_modules = $module_manager->get_available_modules();

        $status = ! empty( $assoc_args['status'] ) ? $assoc_args['status'] : 'all';
        $items  = [];

        foreach ( $all_modules as $module_key => $module ) {
            // `dokan_pro_modules` is filterable, so no key beyond the array key is guaranteed.
            $module_id    = $module['id'] ?? $module_key;
            $is_active    = in_array( $module_id, $active_modules, true );
            $is_available = in_array( $module_id, $available_modules, true );

            if ( 'active' === $status && ! $is_active ) {
                continue;
            }

            if ( 'inactive' === $status && $is_active ) {
                continue;
            }

            if ( 'available' === $status && ! $is_available ) {
                continue;
            }

            $items[] = [
                'id'        => $module_id,
                'name'      => $module['name'] ?? $module_id,
                'status'    => $is_active ? 'active' : 'inactive',
                'available' => $is_available ? 'yes' : 'no',
                'plan'      => implode( ', ', (array) ( $module['plan'] ?? [] ) ),
            ];
        }

        $format         = WP_CLI\Utils\get_flag_value( $assoc_args, 'format', 'table' );
        $default_fields = [ 'id', 'name', 'status', 'available', 'plan' ];
        $fields         = ! empty( $assoc_args['fields'] ) ? array_map( 'trim', explode( ',', $assoc_args['fields'] ) ) : $default_fields;
        $unknown        = array_diff( $fields, $default_fields );

        // An unknown field would surface as a PHP notice from deep inside format_items().
        if ( ! empty( $unknown ) ) {
            WP_CLI::error(
                sprintf(
                    /* translators: 1: unknown field names, 2: available field names */
                    __( 'Unknown field(s): %1$s. Available fields: %2$s.', 'dokan' ),
                    implode( ', ', $unknown ),
                    implode( ', ', $default_fields )
                )
            );
        }

        WP_CLI\Utils\format_items( $format, $items, $fields );
    }

    /**
     * Activate one or more Dokan Pro modules.
     *
     * ## OPTIONS
     *
     * [<module>...]
     * : One or more module ids to activate.
     *
     * [--all]
     * : Activate every module available under the current license plan.
     *
     * ## EXAMPLES
     *
     *     # Activate a single module
     *     $ wp dokan module activate booking
     *
     *     # Activate multiple modules at once
     *     $ wp dokan module activate stripe paypal_marketplace
     *
     *     # Activate all available modules
     *     $ wp dokan module activate --all
     *
     * @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 ) {
        $this->guard_license();

        $module_manager = dokan_pro()->module;
        $available      = $module_manager->get_available_modules();
        $all_ids        = $module_manager->get_all_module_ids();
        $modules        = $this->resolve_modules( $args, $assoc_args, $available );

        $to_activate = [];
        $rejected    = 0;

        foreach ( $modules as $module_id ) {
            if ( ! in_array( $module_id, $all_ids, true ) ) {
                ++$rejected;
                /* translators: %s: module id */
                WP_CLI::warning( sprintf( __( 'Skipping "%s": no such module.', 'dokan' ), $module_id ) );
                continue;
            }

            if ( ! in_array( $module_id, $available, true ) ) {
                ++$rejected;
                /* translators: %s: module id */
                WP_CLI::warning( sprintf( __( 'Skipping "%s": not available under the current license plan.', 'dokan' ), $module_id ) );
                continue;
            }

            if ( $module_manager->is_active( $module_id ) ) {
                /* translators: %s: module id */
                WP_CLI::log( sprintf( __( 'Module "%s" is already active.', 'dokan' ), $module_id ) );
                continue;
            }

            $to_activate[] = $module_id;
        }

        $count = count( $to_activate );

        if ( $count ) {
            $module_manager->activate_modules( $to_activate );
            $module_manager->set_modules( [] );

            foreach ( $to_activate as $module_id ) {
                /* translators: %s: module id */
                WP_CLI::log( sprintf( __( 'Activated: %s', 'dokan' ), $module_id ) );
            }
        }

        // A rejected id must exit non-zero, or a typo passes unnoticed in a provisioning script.
        if ( $rejected ) {
            WP_CLI::error(
                $count
                    ? sprintf(
                        /* translators: 1: number of modules activated, 2: number of modules requested */
                        __( 'Only %1$d of %2$d requested modules activated.', 'dokan' ),
                        $count,
                        $count + $rejected
                    )
                    : __( 'No modules activated.', 'dokan' )
            );
        }

        WP_CLI::success(
            $count
                ? sprintf(
                    /* translators: %d: number of activated modules */
                    _n( '%d module activated.', '%d modules activated.', $count, 'dokan' ),
                    $count
                )
                : __( 'No modules needed activation.', 'dokan' )
        );
    }

    /**
     * Deactivate one or more Dokan Pro modules.
     *
     * ## OPTIONS
     *
     * [<module>...]
     * : One or more module ids to deactivate.
     *
     * [--all]
     * : Deactivate every currently active module.
     *
     * ## EXAMPLES
     *
     *     # Deactivate a single module
     *     $ wp dokan module deactivate booking
     *
     *     # Deactivate multiple modules at once
     *     $ wp dokan module deactivate stripe paypal_marketplace
     *
     *     # Deactivate all active modules
     *     $ wp dokan module deactivate --all
     *
     * @since 5.0.17
     *
     * @when after_wp_load
     *
     * @param array $args       Positional arguments.
     * @param array $assoc_args Associative arguments.
     *
     * @return void
     */
    public function deactivate( $args, $assoc_args ) {
        $module_manager = dokan_pro()->module;
        $active_modules = $this->get_stored_modules();
        $all_ids        = $module_manager->get_all_module_ids();
        $modules        = $this->resolve_modules( $args, $assoc_args, $active_modules );

        $to_deactivate = [];
        $rejected      = 0;

        foreach ( $modules as $module_id ) {
            // Without this an unknown id is indistinguishable from an already-inactive one.
            if ( ! in_array( $module_id, $all_ids, true ) ) {
                ++$rejected;
                /* translators: %s: module id */
                WP_CLI::warning( sprintf( __( 'Skipping "%s": no such module.', 'dokan' ), $module_id ) );
                continue;
            }

            // Checked against the forced list above; is_active() is license-gated and
            // reports every module as inactive once the license expires.
            if ( ! in_array( $module_id, $active_modules, true ) ) {
                /* translators: %s: module id */
                WP_CLI::log( sprintf( __( 'Module "%s" is not active.', 'dokan' ), $module_id ) );
                continue;
            }

            $to_deactivate[] = $module_id;
        }

        $count = count( $to_deactivate );

        if ( $count ) {
            $module_manager->deactivate_modules( $to_deactivate );
            $module_manager->set_modules( [] );

            foreach ( $to_deactivate as $module_id ) {
                /* translators: %s: module id */
                WP_CLI::log( sprintf( __( 'Deactivated: %s', 'dokan' ), $module_id ) );
            }
        }

        // A rejected id must exit non-zero, or a typo passes unnoticed in a provisioning script.
        if ( $rejected ) {
            WP_CLI::error(
                $count
                    ? sprintf(
                        /* translators: 1: number of modules deactivated, 2: number of modules requested */
                        __( 'Only %1$d of %2$d requested modules deactivated.', 'dokan' ),
                        $count,
                        $count + $rejected
                    )
                    : __( 'No modules deactivated.', 'dokan' )
            );
        }

        WP_CLI::success(
            $count
                ? sprintf(
                    /* translators: %d: number of deactivated modules */
                    _n( '%d module deactivated.', '%d modules deactivated.', $count, 'dokan' ),
                    $count
                )
                : __( 'No modules needed deactivation.', 'dokan' )
        );
    }

    /**
     * Show the status of a single Dokan Pro module.
     *
     * ## OPTIONS
     *
     * <module>
     * : The module id to inspect.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - json
     *   - yaml
     * ---
     *
     * [--field=<field>]
     * : Print just the value of a single field.
     *
     * ## EXAMPLES
     *
     *     # Inspect a module
     *     $ wp dokan module status booking
     *
     * @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 ) {
        $module_id      = isset( $args[0] ) ? sanitize_text_field( $args[0] ) : '';
        $module_manager = dokan_pro()->module;
        $all_modules    = $module_manager->get_all_modules();

        if ( ! isset( $all_modules[ $module_id ] ) ) {
            /* translators: %s: module id */
            WP_CLI::error( sprintf( __( 'Module "%s" does not exist.', 'dokan' ), $module_id ) );
        }

        $active_modules = $this->get_stored_modules();

        $module = $all_modules[ $module_id ];
        $status = [
            'id'        => $module['id'] ?? $module_id,
            'name'      => $module['name'] ?? $module_id,
            'status'    => in_array( $module_id, $active_modules, true ) ? 'active' : 'inactive',
            'available' => $module_manager->is_available( $module_id ) ? 'yes' : 'no',
            'plan'      => implode( ', ', (array) ( $module['plan'] ?? [] ) ),
        ];

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

    /**
     * Resolve the list of module ids from the CLI input.
     *
     * @since 5.0.17
     *
     * @param array $args       Positional arguments.
     * @param array $assoc_args Associative arguments.
     * @param array $all_flag_modules Modules to use when the --all flag is set.
     *
     * @return array
     */
    protected function resolve_modules( $args, $assoc_args, $all_flag_modules ) {
        if ( WP_CLI\Utils\get_flag_value( $assoc_args, 'all', false ) ) {
            return $all_flag_modules;
        }

        $modules = array_map( 'sanitize_text_field', $args );

        if ( empty( $modules ) ) {
            WP_CLI::error( __( 'Please specify at least one module id or use the --all flag.', 'dokan' ) );
        }

        return $modules;
    }

    /**
     * Read the stored active-module list, bypassing the license gate.
     *
     * `get_active_modules()` returns an empty list on an invalid license, which would hide
     * every module from `list`, `status` and `deactivate` exactly when an operator needs
     * them most. Which modules actually load stays gated by `load_active_modules()`.
     *
     * @since 5.0.17
     *
     * @return array
     */
    protected function get_stored_modules() {
        return dokan_pro()->module->get_active_modules( true );
    }

    /**
     * Ensure a valid Dokan Pro license is present before managing modules.
     *
     * @since 5.0.17
     *
     * @return void
     */
    protected function guard_license() {
        if ( ! dokan_pro()->license->is_valid() ) {
            WP_CLI::error( __( 'A valid Dokan Pro license is required to manage modules. Activate it with: wp dokan license activate <license-key>', 'dokan' ) );
        }
    }
}
