<?php

/**
 * RetentionLogger
 *
 * Hooks into subscription lifecycle events and writes rows
 * to the arraysubs_retention_logs table.
 *
 * @package ArraySubs\Features\RetentionAnalytics\Services
 * @since 1.9.0
 */

namespace ArraySubs\Features\RetentionAnalytics\Services;

defined('ABSPATH') || exit;

use ArraySubs\Features\RetentionAnalytics\Migrations\CreateRetentionLogsTable;
use WP_Post;

/**
 * Retention event logger
 */
class RetentionLogger
{
    /**
     * Enable auto-loading via AbstractLoader
     *
     * @var bool
     */
    public static $loadable = true;

    /**
     * Allowed event types
     *
     * @var array
     */
    const EVENT_TYPES = [
        'cancelled',
        'scheduled_cancel',
        'cancel_undone',
        'offer_shown',
        'offer_accepted',
        'offer_declined',
    ];

    /**
     * Constructor - Register hooks
     */
    public function __construct()
    {
        // Cancelled — fires when status changes to arraysubs-cancelled.
        \add_action('arraysubs_data_status_changed', [$this, 'onStatusChanged'], 20, 3);

        // Scheduled end-of-period cancel.
        \add_action('arraysubs_data_waiting_cancellation', [$this, 'onScheduledCancel'], 20, 2);

        // Cancellation undone.
        \add_action('arraysubs_data_cancellation_undone', [$this, 'onCancelUndone'], 20, 1);

        // Retention offer shown (new hook to be fired in CancellationController).
        \add_action('arraysubs_retention_offer_shown', [$this, 'onOfferShown'], 20, 2);

        // Retention offer accepted.
        \add_action('arraysubs_retention_offer_accepted', [$this, 'onOfferAccepted'], 20, 1);
    }

    /**
     * Log cancellation when subscription status changes to cancelled
     *
     * @param int    $subscription_id Subscription ID.
     * @param string $new_status      New post status.
     * @param string $old_status      Old post status.
     * @return void
     */
    public function onStatusChanged(int $subscription_id, string $new_status, string $old_status): void
    {
        if ($new_status !== 'arraysubs-cancelled') {
            return;
        }

        $subscription = \arraysubs_get_subscription($subscription_id);
        if (!$subscription instanceof WP_Post) {
            return;
        }

        $offer_was_shown = \get_post_meta($subscription_id, '_retention_offer_shown', true);
        $offer_accepted  = \get_post_meta($subscription_id, '_retention_offer_accepted', true);

        // If offer was shown but not accepted, this is an "offer_declined" event too.
        if ($offer_was_shown && !$offer_accepted) {
            self::insert($subscription, 'offer_declined', [
                'reason' => \get_post_meta($subscription_id, '_cancellation_reason', true),
            ]);
        }

        self::insert($subscription, 'cancelled', [
            'reason'         => \get_post_meta($subscription_id, '_cancellation_reason', true),
            'reason_details' => \get_post_meta($subscription_id, '_cancellation_reason_details', true),
            'initiated_by'   => self::resolveInitiator($subscription_id),
        ]);
    }

    /**
     * Log scheduled (end-of-period) cancellation
     *
     * @param int    $subscription_id Subscription ID.
     * @param string $reason          Cancellation reason.
     * @return void
     */
    public function onScheduledCancel(int $subscription_id, string $reason): void
    {
        $subscription = \arraysubs_get_subscription($subscription_id);
        if (!$subscription instanceof WP_Post) {
            return;
        }

        self::insert($subscription, 'scheduled_cancel', [
            'reason'       => $reason,
            'initiated_by' => self::resolveInitiator($subscription_id),
        ]);
    }

    /**
     * Log cancellation undone
     *
     * @param int $subscription_id Subscription ID.
     * @return void
     */
    public function onCancelUndone(int $subscription_id): void
    {
        $subscription = \arraysubs_get_subscription($subscription_id);
        if (!$subscription instanceof WP_Post) {
            return;
        }

        self::insert($subscription, 'cancel_undone', [
            'initiated_by' => self::resolveInitiator($subscription_id),
        ]);
    }

    /**
     * Log retention offer shown
     *
     * @param int   $subscription_id  Subscription ID.
     * @param array $available_offers Available offers that were shown.
     * @return void
     */
    public function onOfferShown(int $subscription_id, array $available_offers): void
    {
        $subscription = \arraysubs_get_subscription($subscription_id);
        if (!$subscription instanceof WP_Post) {
            return;
        }

        $reason = \get_post_meta($subscription_id, '_cancellation_reason', true)
            ?: \get_post_meta($subscription_id, '_cancellation_reason_at_offer', true);

        self::insert($subscription, 'offer_shown', [
            'reason'     => $reason,
            'offer_data' => \wp_json_encode(array_keys($available_offers)),
        ]);
    }

    /**
     * Log retention offer accepted
     *
     * Defensive: only logs a real acceptance if offer_type meta is present.
     * Otherwise the event is a cancel-undo and another handler covers it.
     *
     * @param int $subscription_id Subscription ID.
     * @return void
     */
    public function onOfferAccepted(int $subscription_id): void
    {
        $subscription = \arraysubs_get_subscription($subscription_id);
        if (!$subscription instanceof WP_Post) {
            return;
        }

        $offer_type = \get_post_meta($subscription_id, '_retention_offer_type', true);

        // If no offer_type is set, this was fired from cancellation-undo logic,
        // which is already handled by onCancelUndone.
        if (empty($offer_type)) {
            return;
        }

        self::insert($subscription, 'offer_accepted', [
            'offer_type' => $offer_type,
            'reason'     => \get_post_meta($subscription_id, '_cancellation_reason', true),
        ]);
    }

    /**
     * Insert a log row from a WP_Post subscription
     *
     * @param WP_Post $subscription Subscription post object.
     * @param string   $event_type   One of self::EVENT_TYPES.
     * @param array    $extra        Additional data to merge.
     * @return void
     */
    public static function insert(WP_Post $subscription, string $event_type, array $extra = []): void
    {
        global $wpdb;

        $sub_id       = $subscription->ID;
        $customer_id  = \absint(\get_post_meta($sub_id, '_customer_id', true));
        $variation_id = \get_post_meta($sub_id, '_variation_id', true);
        $product_id   = \get_post_meta($sub_id, '_product_id', true);

        $effective_product_id = \absint($variation_id ?: $product_id);

        $product_name = '';
        if (\function_exists('arraysubs_get_subscription_product_name_by_subscription')) {
            $product_name = \arraysubs_get_subscription_product_name_by_subscription($sub_id);
        }

        $customer_name = self::resolveCustomerName($customer_id);
        $recurring     = (float) \get_post_meta($sub_id, '_recurring_amount', true);
        $currency      = \get_post_meta($sub_id, '_currency', true) ?: \get_woocommerce_currency();
        $sub_age       = \absint(\get_post_meta($sub_id, '_subscription_age_at_cancel', true));
        $payments      = \absint(\get_post_meta($sub_id, '_payments_at_cancel', true));
        $reason        = \sanitize_text_field($extra['reason'] ?? '');
        $reason_details = \sanitize_textarea_field($extra['reason_details'] ?? '');
        $offer_type    = \sanitize_text_field($extra['offer_type'] ?? '');
        $offer_data    = $extra['offer_data'] ?? '';
        $initiated_by  = \sanitize_text_field($extra['initiated_by'] ?? '');

        // If subscription age is 0 and there's a start_date, calculate on the fly.
        if ($sub_age <= 0) {
            $start_date = \get_post_meta($sub_id, '_start_date', true);
            if (!empty($start_date)) {
                $sub_age = max(0, (int) ((\time() - \strtotime($start_date)) / DAY_IN_SECONDS));
            }
        }

        // If payments_count = 0, try _completed_payments.
        if ($payments <= 0) {
            $payments = \absint(\get_post_meta($sub_id, '_completed_payments', true));
        }

        $table = CreateRetentionLogsTable::getTableName();

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
        $wpdb->insert(
            $table,
            [
                'subscription_id'       => $sub_id,
                'customer_id'           => $customer_id,
                'customer_name_snapshot' => $customer_name,
                'event_type'            => $event_type,
                'reason'                => $reason,
                'reason_details'        => $reason_details,
                'offer_type'            => $offer_type,
                'offer_data'            => is_string($offer_data) ? $offer_data : \wp_json_encode($offer_data),
                'initiated_by'          => $initiated_by,
                'effective_product_id'  => $effective_product_id,
                'product_name_snapshot' => $product_name,
                'subscription_age'      => $sub_age,
                'payments_count'        => $payments,
                'recurring_amount'      => $recurring,
                'currency'              => $currency,
                'created_at'            => \current_time('mysql', true),
            ],
            ['%d', '%d', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%d', '%s', '%d', '%d', '%f', '%s', '%s']
        );
    }

    /**
     * Backfill: reconstruct a cancelled-event row from existing meta
     *
     * @param WP_Post $subscription Subscription post.
     * @return void
     */
    public static function logFromMeta(WP_Post $subscription): void
    {
        $sub_id = $subscription->ID;

        // Build extra data from stored meta.
        $extra = [
            'reason'         => \get_post_meta($sub_id, '_cancellation_reason', true) ?: '',
            'reason_details' => \get_post_meta($sub_id, '_cancellation_reason_details', true) ?: '',
            'initiated_by'   => \get_post_meta($sub_id, '_cancelled_by', true) ?: 'unknown',
        ];

        self::insert($subscription, 'cancelled', $extra);

        // Also log offer_shown if meta indicates it.
        $offer_shown = \get_post_meta($sub_id, '_retention_offer_shown', true);
        if ($offer_shown) {
            self::insert($subscription, 'offer_shown', [
                'reason' => $extra['reason'],
            ]);

            // And offer_accepted if applicable.
            $offer_accepted = \get_post_meta($sub_id, '_retention_offer_accepted', true);
            $offer_type     = \get_post_meta($sub_id, '_retention_offer_type', true);
            if ($offer_accepted && !empty($offer_type)) {
                self::insert($subscription, 'offer_accepted', [
                    'offer_type' => $offer_type,
                    'reason'     => $extra['reason'],
                ]);
            }
        }
    }

    /**
     * Resolve who initiated the action
     *
     * @param int $subscription_id Subscription ID.
     * @return string
     */
    private static function resolveInitiator(int $subscription_id): string
    {
        // If current user is an admin, this is admin-initiated.
        if (\current_user_can('manage_woocommerce') || \current_user_can('manage_options')) {
            return 'admin';
        }

        // Check _cancelled_by meta if set.
        $cancelled_by = \get_post_meta($subscription_id, '_cancelled_by', true);
        if (!empty($cancelled_by)) {
            return \sanitize_text_field($cancelled_by);
        }

        // If a user is logged in, they are the customer.
        if (\is_user_logged_in()) {
            return 'customer';
        }

        return 'system';
    }

    /**
     * Get a display name for a customer ID
     *
     * @param int $customer_id Customer user ID.
     * @return string
     */
    private static function resolveCustomerName(int $customer_id): string
    {
        if ($customer_id <= 0) {
            return '';
        }

        $user = \get_userdata($customer_id);
        if (!$user) {
            return '';
        }

        $name = \trim($user->first_name . ' ' . $user->last_name);
        if (empty($name)) {
            $name = $user->display_name;
        }
        if (empty($name)) {
            $name = $user->user_email;
        }

        return $name;
    }
}
