<?php

declare(strict_types=1);

namespace Instinctor\Services;

use Instinctor\Core\Config;
use Instinctor\Core\DB;


if ( ! defined( 'ABSPATH' ) ) exit;

/**
 * Loyalty Service
 * Manages points-based rewards program
 */
class LoyaltyService
{
    private static array $tiers = [
        'bronze' => ['name' => 'Bronze', 'min_points' => 0, 'multiplier' => 1.0],
        'silver' => ['name' => 'Silver', 'min_points' => 1000, 'multiplier' => 1.25],
        'gold' => ['name' => 'Gold', 'min_points' => 5000, 'multiplier' => 1.5],
        'platinum' => ['name' => 'Platinum', 'min_points' => 15000, 'multiplier' => 2.0],
    ];

    /**
     * Get loyalty settings
     */
    public static function getSettings(): array
    {
        return [
            'enabled' => Config::get('instinctor_loyalty_enabled', false),
            'points_per_dollar' => floatval(Config::get('instinctor_loyalty_points_per_dollar', 1)),
            'points_value' => floatval(Config::get('instinctor_loyalty_points_value', 0.01)), // $0.01 per point
            'min_redeem' => intval(Config::get('instinctor_loyalty_min_redeem', 100)),
            'points_for_signup' => intval(Config::get('instinctor_loyalty_signup_points', 100)),
            'points_for_referral' => intval(Config::get('instinctor_loyalty_referral_points', 200)),
            'points_expiry_days' => intval(Config::get('instinctor_loyalty_expiry_days', 365)),
        ];
    }

    /**
     * Get customer loyalty data
     */
    public static function getCustomerPoints(int $customerId): object
    {
        $table = DB::prefix() . 'instinctor_loyalty_points';

        $record = DB::get_row(DB::prepare(
            "SELECT * FROM $table WHERE customer_id = %d",
            $customerId
        ));

        if (!$record) {
            // Create new record
            DB::insert($table, [
                'customer_id' => $customerId,
                'points' => 0,
                'lifetime_points' => 0,
                'tier' => 'bronze',
            ]);

            $record = (object) [
                'id' => DB::insert_id(),
                'customer_id' => $customerId,
                'points' => 0,
                'lifetime_points' => 0,
                'tier' => 'bronze',
            ];
        }

        // Add tier info
        $record->tier_info = self::$tiers[$record->tier] ?? self::$tiers['bronze'];
        $record->next_tier = self::getNextTier($record->lifetime_points);
        
        return $record;
    }

    /**
     * Get next tier info
     */
    private static function getNextTier(int $lifetimePoints): ?array
    {
        $tiers = array_values(self::$tiers);
        
        for ($i = 0; $i < count($tiers); $i++) {
            if ($lifetimePoints < $tiers[$i]['min_points']) {
                return [
                    'tier' => $tiers[$i],
                    'points_needed' => $tiers[$i]['min_points'] - $lifetimePoints,
                ];
            }
        }

        return null; // Already at max tier
    }

    /**
     * Award points
     */
    public static function awardPoints(int $customerId, int $points, string $type, ?string $source = null, ?int $referenceId = null, ?string $description = null): bool
    {
        if ($points <= 0) {
            return false;
        }

        $points_table = DB::prefix() . 'instinctor_loyalty_points';
        $trans_table = DB::prefix() . 'instinctor_loyalty_transactions';

        $settings = self::getSettings();
        $current = self::getCustomerPoints($customerId);

        // Apply tier multiplier for earned points
        if ($type === 'earn') {
            $multiplier = $current->tier_info['multiplier'];
            $points = (int) round($points * $multiplier);
        }

        // Calculate expiry
        $expiresAt = null;
        if ($settings['points_expiry_days'] > 0) {
            // phpcs:disable WordPress.DateTime.RestrictedFunctions -- date() used intentionally for local-timezone formatting; gmdate() would force UTC and change behavior.
            $expiresAt = date('Y-m-d H:i:s', strtotime('+' . $settings['points_expiry_days'] . ' days'));
            // phpcs:enable WordPress.DateTime.RestrictedFunctions
        }

        // Update points balance
        DB::query(DB::prepare(
            "UPDATE $points_table SET 
                points = points + %d,
                lifetime_points = lifetime_points + %d
             WHERE customer_id = %d",
            $points, $points, $customerId
        ));

        // Log transaction
        DB::insert($trans_table, [
            'customer_id' => $customerId,
            'points' => $points,
            'type' => $type,
            'source' => $source,
            'reference_id' => $referenceId,
            'description' => $description,
            'expires_at' => $expiresAt,
        ]);

        // Check for tier upgrade
        self::updateTier($customerId);

        return true;
    }

    /**
     * Redeem points
     */
    public static function redeemPoints(int $customerId, int $points, ?int $orderId = null): array
    {
        $settings = self::getSettings();
        $current = self::getCustomerPoints($customerId);

        if ($points < $settings['min_redeem']) {
            return ['success' => false, 'error' => 'Minimum ' . $settings['min_redeem'] . ' points required'];
        }

        if ($points > $current->points) {
            return ['success' => false, 'error' => 'Insufficient points'];
        }

        $points_table = DB::prefix() . 'instinctor_loyalty_points';
        $trans_table = DB::prefix() . 'instinctor_loyalty_transactions';

        // Deduct points
        DB::query(DB::prepare(
            "UPDATE $points_table SET points = points - %d WHERE customer_id = %d",
            $points, $customerId
        ));

        // Log transaction
        DB::insert($trans_table, [
            'customer_id' => $customerId,
            'points' => -$points,
            'type' => 'redeem',
            'source' => 'checkout',
            'reference_id' => $orderId,
            'description' => $orderId ? 'Redeemed for order #' . $orderId : 'Points redeemed',
        ]);

        $discountAmount = $points * $settings['points_value'];

        return [
            'success' => true,
            'points_used' => $points,
            'discount_amount' => $discountAmount,
            'remaining_points' => $current->points - $points,
        ];
    }

    /**
     * Award points for order
     */
    public static function awardForOrder(int $customerId, int $orderId, float $orderTotal): int
    {
        $settings = self::getSettings();
        
        if (!$settings['enabled']) {
            return 0;
        }

        $points = (int) floor($orderTotal * $settings['points_per_dollar']);
        
        if ($points > 0) {
            self::awardPoints($customerId, $points, 'earn', 'order', $orderId, 'Purchase points for order #' . $orderId);
        }

        return $points;
    }

    /**
     * Award points for signup
     */
    public static function awardForSignup(int $customerId): int
    {
        $p = DB::prefix();
        $settings = self::getSettings();
        
        if (!$settings['enabled'] || $settings['points_for_signup'] <= 0) {
            return 0;
        }

        self::awardPoints($customerId, $settings['points_for_signup'], 'bonus', 'signup', $customerId, 'Welcome bonus for signing up');

        // Send loyalty welcome email
        $customer = DB::get_row(DB::prepare(
            "SELECT email, first_name, last_name FROM {$p}instinctor_customers WHERE id = %d",
            $customerId
        ));
        
        if ($customer && $customer->email) {
            EmailService::send('loyalty_welcome', $customer->email, [
                'customer_name' => trim($customer->first_name . ' ' . $customer->last_name),
                'customer_first_name' => $customer->first_name,
                'points' => $settings['points_for_signup'],
            ]);
        }

        return $settings['points_for_signup'];
    }

    /**
     * Award points for referral
     */
    public static function awardForReferral(int $referrerId, int $newCustomerId): int
    {
        $settings = self::getSettings();
        
        if (!$settings['enabled'] || $settings['points_for_referral'] <= 0) {
            return 0;
        }

        self::awardPoints($referrerId, $settings['points_for_referral'], 'bonus', 'referral', $newCustomerId, 'Referral bonus');

        return $settings['points_for_referral'];
    }

    /**
     * Update customer tier
     */
    private static function updateTier(int $customerId): void
    {
        $p = DB::prefix();
        $table = DB::prefix() . 'instinctor_loyalty_points';

        $record = DB::get_row(DB::prepare(
            "SELECT lifetime_points, tier FROM $table WHERE customer_id = %d",
            $customerId
        ));

        if (!$record) {
            return;
        }

        $oldTier = $record->tier;
        $newTier = 'bronze';
        foreach (self::$tiers as $key => $tier) {
            if ($record->lifetime_points >= $tier['min_points']) {
                $newTier = $key;
            }
        }

        // Only update if tier changed
        if ($newTier !== $oldTier) {
            DB::update($table, ['tier' => $newTier], ['customer_id' => $customerId]);
            
            // Send tier upgrade email (only if upgrading, not downgrading)
            $tierOrder = array_keys(self::$tiers);
            $oldIndex = array_search($oldTier, $tierOrder);
            $newIndex = array_search($newTier, $tierOrder);
            
            if ($newIndex > $oldIndex) {
                $customer = DB::get_row(DB::prepare(
                    "SELECT email, first_name, last_name FROM {$p}instinctor_customers WHERE id = %d",
                    $customerId
                ));
                
                if ($customer && $customer->email) {
                    $newTierInfo = self::$tiers[$newTier];
                    $benefits = self::getTierBenefits($newTier);
                    
                    EmailService::send('loyalty_tier_upgrade', $customer->email, [
                        'customer_name' => trim($customer->first_name . ' ' . $customer->last_name),
                        'customer_first_name' => $customer->first_name,
                        'tier' => $oldTier,
                        'new_tier' => $newTierInfo['name'],
                        'tier_benefits' => $benefits,
                    ]);
                }
            }
        }
    }
    
    /**
     * Get tier benefits description
     */
    private static function getTierBenefits(string $tier): string
    {
        $tierInfo = self::$tiers[$tier] ?? self::$tiers['bronze'];
        $multiplier = $tierInfo['multiplier'];
        
        $benefits = [];
        $benefits[] = ($multiplier * 100) . '% points earning rate';
        
        if ($tier === 'silver') {
            $benefits[] = 'Early access to sales';
        } elseif ($tier === 'gold') {
            $benefits[] = 'Early access to sales';
            $benefits[] = 'Free shipping on orders over $50';
        } elseif ($tier === 'platinum') {
            $benefits[] = 'Early access to sales';
            $benefits[] = 'Free shipping on all orders';
            $benefits[] = 'Exclusive member-only products';
        }
        
        return implode("\n• ", array_merge([''], $benefits));
    }

    /**
     * Get transaction history
     */
    public static function getTransactions(int $customerId, int $limit = 20): array
    {
        $table = DB::prefix() . 'instinctor_loyalty_transactions';

        return DB::get_results(DB::prepare(
            "SELECT * FROM $table WHERE customer_id = %d ORDER BY created_at DESC LIMIT %d",
            $customerId, $limit
        ));
    }

    /**
     * Calculate points value in currency
     */
    public static function getPointsValue(int $points): float
    {
        $settings = self::getSettings();
        return $points * $settings['points_value'];
    }

    /**
     * Get available tiers
     */
    public static function getTiers(): array
    {
        return self::$tiers;
    }

    /**
     * Expire old points (cron job)
     */
    public static function expirePoints(): int
    {
        $trans_table = DB::prefix() . 'instinctor_loyalty_transactions';
        $points_table = DB::prefix() . 'instinctor_loyalty_points';

        // Get expired points
        // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table name is from $wpdb->prefix, no user input
        $expired = DB::get_results(
            "SELECT customer_id, SUM(points) as total_points
             FROM $trans_table 
             WHERE type = 'earn' 
             AND expires_at IS NOT NULL 
             AND expires_at < NOW()
             AND points > 0
             GROUP BY customer_id"
        );

        $count = 0;
        foreach ($expired as $row) {
            // Deduct from balance
            DB::query(DB::prepare(
                "UPDATE $points_table SET points = GREATEST(0, points - %d) WHERE customer_id = %d",
                $row->total_points, $row->customer_id
            ));

            // Log expiry
            DB::insert($trans_table, [
                'customer_id' => $row->customer_id,
                'points' => -$row->total_points,
                'type' => 'expire',
                'description' => 'Points expired',
            ]);

            $count++;
        }

        // Mark as processed
        DB::query(
            "UPDATE $trans_table SET expires_at = NULL WHERE expires_at IS NOT NULL AND expires_at < NOW()"
        );

        return $count;
    }

    /**
     * Get leaderboard
     */
    public static function getLeaderboard(int $limit = 10): array
    {
        $points_table = DB::prefix() . 'instinctor_loyalty_points';
        $customers_table = DB::prefix() . 'instinctor_customers';

        return DB::get_results(DB::prepare(
            "SELECT lp.*, c.first_name, c.last_name
             FROM $points_table lp
             JOIN $customers_table c ON lp.customer_id = c.id
             ORDER BY lp.lifetime_points DESC
             LIMIT %d",
            $limit
        ));
    }
}
