<?php

namespace BitApps\PiPro\src\Integrations\PaidMembershipPro;

use BitApps\Pi\Deps\BitApps\WPValidator\Validator;
use BitApps\Pi\src\Flow\NodeInfoProvider;

if (!\defined('ABSPATH')) {
    exit;
}

final class PaidMembershipProServices
{
    private NodeInfoProvider $nodeInfoProvider;

    private array $fieldMappings = [];

    /**
     * PaidMembershipPro services constructor.
     */
    public function __construct(NodeInfoProvider $nodeInfoProvider)
    {
        $this->nodeInfoProvider = $nodeInfoProvider;
        $this->fieldMappings = $this->nodeInfoProvider->getFieldMapData();
    }

    /**
     * Get all membership levels.
     *
     * @return collection
     */
    public function getAllMembershipLevel(): array
    {
        global $wpdb;

        $levels = $wpdb->get_results(
            "SELECT * FROM {$wpdb->pmpro_membership_levels} ORDER BY id ASC"
        );

        if ($wpdb->last_error) {
            return [
                'response'    => $wpdb->last_error,
                'payload'     => [],
                'status_code' => 500
            ];
        }

        return [
            'response'    => $levels ?? [],
            'payload'     => [],
            'status_code' => 200
        ];
    }

    /**
     * Get a specific membership level by ID.
     *
     * @return collection
     */
    public function getMembershipLevel(): array
    {
        if ($error = $this->validateFieldMap(['id' => ['required', 'integer']])) {
            return $error;
        }

        global $wpdb;

        $level = $wpdb->get_results(
            $wpdb->prepare(
                "SELECT * FROM {$wpdb->pmpro_membership_levels} WHERE id = %d LIMIT 1",
                absint($this->fieldMappings['id'])
            )
        );

        if (empty($level)) {
            return [
                'response'    => __('Membership level not found.', 'bit-pi'),
                'payload'     => $this->fieldMappings,
                'status_code' => 404
            ];
        }

        return [
            'response'    => reset($level),
            'payload'     => $this->fieldMappings,
            'status_code' => 200
        ];
    }

    /**
     * Add user to a membership level.
     *
     * @return collection
     */
    public function addUserToMembershipLevel(): array
    {
        if ($error = $this->validateUserEmailAndLevelId()) {
            return $error;
        }

        $functionErrors = $this->validateFunctionExists(
            ['pmpro_getMembershipLevelForUser', 'pmpro_changeMembershipLevel']
        );

        if ($functionErrors) {
            return $functionErrors;
        }

        $userId = $this->getUserIdByEmail();

        if (!$userId) {
            return [
                'response'    => __('User not found with the provided email.', 'bit-pi'),
                'payload'     => $this->fieldMappings,
                'status_code' => 404
            ];
        }

        $membershipId = absint($this->fieldMappings['membership_id']);
        $currentLevel = pmpro_getMembershipLevelForUser($userId);

        if (isset($currentLevel->ID) && absint($currentLevel->ID) === $membershipId) {
            return [
                'response'    => __('User is already a member of this level.', 'bit-pi'),
                'payload'     => $this->fieldMappings,
                'status_code' => 422
            ];
        }

        global $wpdb;

        $level = $wpdb->get_results(
            $wpdb->prepare(
                "SELECT * FROM {$wpdb->pmpro_membership_levels} WHERE id = %d",
                $membershipId
            )
        );

        if (empty($level)) {
            return [
                'response'    => __('Membership level not found.', 'bit-pi'),
                'payload'     => $this->fieldMappings,
                'status_code' => 404
            ];
        }

        $startDate = current_time('mysql');
        $endDate = $this->calculateEndDate($level);

        if ($endDate) {
            $data = $this->prepareMembershipData($userId, $level, $startDate, $endDate);
            $status = pmpro_changeMembershipLevel($data, $userId);
        } else {
            $status = pmpro_changeMembershipLevel($membershipId, $userId);
        }

        if (empty($status)) {
            return [
                'response'    => __('Failed to add user to membership level.', 'bit-pi'),
                'payload'     => $this->fieldMappings,
                'status_code' => 500
            ];
        }

        return [
            'response'    => __('User added to membership level successfully.', 'bit-pi'),
            'payload'     => $this->fieldMappings,
            'status_code' => 200
        ];
    }

    /**
     * Remove user from a membership level.
     *
     * @return collection
     */
    public function removeUserFromMembershipLevel(): array
    {
        if ($error = $this->validateUserEmailAndLevelId()) {
            return $error;
        }

        $functionErrors = $this->validateFunctionExists(
            ['pmpro_getMembershipLevelsForUser', 'pmpro_cancelMembershipLevel']
        );

        if ($functionErrors) {
            return $functionErrors;
        }

        $userId = $this->getUserIdByEmail();

        if (!$userId) {
            return [
                'response'    => __('User not found with the provided email.', 'bit-pi'),
                'payload'     => $this->fieldMappings,
                'status_code' => 404
            ];
        }

        $membershipId = absint($this->fieldMappings['membership_id']);
        $userLevels = wp_list_pluck((array) pmpro_getMembershipLevelsForUser($userId), 'id');

        if (!\in_array($membershipId, $userLevels)) {
            return [
                'response'    => __('User does not belong to any membership levels.', 'bit-pi'),
                'payload'     => $this->fieldMappings,
                'status_code' => 404
            ];
        }

        $status = pmpro_cancelMembershipLevel($membershipId, $userId);

        if (empty($status)) {
            return [
                'response'    => __('Failed to remove user from membership level.', 'bit-pi'),
                'payload'     => $this->fieldMappings,
                'status_code' => 500
            ];
        }

        return [
            'response'    => __('User removed from membership level successfully.', 'bit-pi'),
            'payload'     => $this->fieldMappings,
            'status_code' => 200
        ];
    }

    /**
     * Get user ID by email.
     *
     * @return false|int
     */
    private function getUserIdByEmail()
    {
        $user = get_user_by('email', $this->fieldMappings['email']);

        return isset($user->ID) ? absint($user->ID) : false;
    }

    /**
     * Validate user email and level ID.
     *
     * @return null|array
     */
    private function validateUserEmailAndLevelId()
    {
        $rules = [
            'email'         => ['required', 'sanitize:email'],
            'membership_id' => ['required', 'integer']
        ];

        return $this->validateFieldMap($rules);
    }

    /**
     * Validate field map.
     *
     * @param array $validationRules
     * @param null|array $payload
     */
    private function validateFieldMap($validationRules, $payload = null)
    {
        $validator = new Validator();

        $validation = $validator->make($this->fieldMappings, $validationRules);

        if ($validation->fails()) {
            return [
                'response'    => $validation->errors(),
                'payload'     => $payload ?? $this->fieldMappings,
                'status_code' => 422
            ];
        }
    }

    /**
     * Validate required functions exist.
     *
     * @param array|string $functionNames
     *
     * @return null|array
     */
    private function validateFunctionExists($functionNames)
    {
        if (!\is_array($functionNames)) {
            $functionNames = [$functionNames];
        }

        foreach ($functionNames as $functionName) {
            if (!\function_exists($functionName)) {
                return [
                    'response' => \sprintf(
                        // translators: %s: Function name
                        __('Required function %s does not exist. Please ensure Paid Memberships Pro plugin is active.', 'bit-pi'),
                        $functionName
                    ),
                    'payload'     => $this->fieldMappings,
                    'status_code' => 400
                ];
            }
        }
    }

    /**
     * Calculate membership end date.
     *
     * @param object $level
     *
     * @return null|string
     */
    private function calculateEndDate($level)
    {
        if (empty($level->expiration_number) || empty($level->expiration_period)) {
            return;
        }

        return date_i18n(
            'Y-m-d',
            strtotime("+ {$level->expiration_number} {$level->expiration_period}")
        );
    }

    /**
     * Prepare membership data.
     *
     * @param int $userId
     * @param object $level
     * @param string $startDate
     * @param string $endDate
     *
     * @return array
     */
    private function prepareMembershipData($userId, $level, $startDate, $endDate)
    {
        return [
            'user_id'         => $userId,
            'membership_id'   => $level->id,
            'code_id'         => 0,
            'initial_payment' => 0,
            'billing_amount'  => 0,
            'cycle_number'    => 0,
            'cycle_period'    => 0,
            'billing_limit'   => 0,
            'trial_amount'    => 0,
            'trial_limit'     => 0,
            'startdate'       => $startDate,
            'enddate'         => $endDate,
        ];
    }
}
