<?php

declare(strict_types=1);

namespace app\common\library;

use app\common\model\MallBusinessConfig;
use app\common\model\MallDailyPush;
use app\common\model\MallUserAsset;
use support\think\Db;

/**
 * 积分来源：昨日净输赢 / 终身总输赢 折算与领取上限
 */
final class MallPointsConversion
{
    /**
     * @return array{
     *     return_ratio: float,
     *     unlock_ratio: float,
     *     points_to_cash_ratio: float,
     *     daily_points_enabled: bool,
     *     lifetime_points_enabled: bool,
     *     lifetime_points_ratio: float,
     *     mall_open_date: ?string,
     *     mall_open_date_previous: ?string,
     *     mall_open_date_effective_on: ?string,
     *     active_mall_open_date: ?string
     * }
     */
    public static function configFromRow(MallBusinessConfig $row): array
    {
        return [
            'return_ratio'                  => floatval($row->return_ratio ?? 0),
            'unlock_ratio'                  => floatval($row->unlock_ratio ?? 0),
            'points_to_cash_ratio'          => floatval($row->points_to_cash_ratio ?? 0),
            'daily_points_enabled'          => intval($row->daily_points_enabled ?? 1) === 1,
            'lifetime_points_enabled'       => intval($row->lifetime_points_enabled ?? 0) === 1,
            'lifetime_points_ratio'         => floatval($row->lifetime_points_ratio ?? 0),
            'mall_open_date'                => self::normalizeDate($row->mall_open_date ?? null),
            'mall_open_date_previous'       => self::normalizeDate($row->mall_open_date_previous ?? null),
            'mall_open_date_effective_on' => self::normalizeDate($row->mall_open_date_effective_on ?? null),
            'active_mall_open_date'         => self::resolveActiveMallOpenDate($row),
        ];
    }

    public static function lossToPoints(float $winLossNet, float $ratio): int
    {
        if ($winLossNet >= 0 || $ratio <= 0) {
            return 0;
        }

        return self::lossIntegerToPoints($winLossNet, $ratio);
    }

    /**
     * 仅统计输钱：取净输赢绝对值的整数部分，再按比例折算为正整数积分
     */
    public static function lossIntegerToPoints(float $winLossNet, float $ratio): int
    {
        if ($winLossNet >= 0 || $ratio <= 0) {
            return 0;
        }
        $lossInteger = intval(floor(abs($winLossNet)));
        if ($lossInteger <= 0) {
            return 0;
        }

        return intval(round($lossInteger * $ratio));
    }

    /**
     * 每日推送：昨日净输赢折算积分（受开关、商城开放日约束）
     *
     * @param array<string, mixed> $config
     */
    public static function dailyConvertedPoints(string $businessDate, float $yesterdayWinLossNet, array $config): int
    {
        if (empty($config['daily_points_enabled'])) {
            return 0;
        }
        $openDate = $config['active_mall_open_date'] ?? null;
        if ($openDate !== null && $openDate !== '' && $businessDate < $openDate) {
            return 0;
        }
        $ratio = floatval($config['return_ratio'] ?? 0);

        return self::lossIntegerToPoints($yesterdayWinLossNet, $ratio);
    }

    /**
     * @param array<string, mixed> $config
     * @deprecated 使用 dailyConvertedPoints
     */
    public static function dailyLockedIncrement(string $businessDate, float $yesterdayWinLossNet, array $config): int
    {
        return self::dailyConvertedPoints($businessDate, $yesterdayWinLossNet, $config);
    }

    /**
     * @return array<string, mixed>
     */
    public static function configForConversion(): array
    {
        return self::configFromRow(MallBusinessConfig::ensureRow());
    }

    public static function syncClaimCap(MallUserAsset $asset): void
    {
        $lifetime = max(0, intval($asset->lifetime_converted_points ?? 0));
        $daily = max(0, intval($asset->daily_converted_points ?? 0));
        $asset->points_claim_cap = $lifetime + $daily;
    }

    public static function applyDailyConversionDelta(MallUserAsset $asset, int $delta): void
    {
        if ($delta === 0) {
            return;
        }
        $asset->daily_converted_points = max(0, intval($asset->daily_converted_points ?? 0) + $delta);
        self::syncClaimCap($asset);
        $newLocked = intval($asset->locked_points ?? 0) + $delta;
        $asset->locked_points = max(0, $newLocked);
    }

    public static function applyLifetimeConversion(MallUserAsset $asset, int $targetLifetimePoints): void
    {
        $current = max(0, intval($asset->lifetime_converted_points ?? 0));
        $delta = $targetLifetimePoints - $current;
        if ($delta === 0) {
            return;
        }
        $asset->lifetime_converted_points = $targetLifetimePoints;
        self::syncClaimCap($asset);
        $newLocked = intval($asset->locked_points ?? 0) + $delta;
        $asset->locked_points = max(0, $newLocked);
    }

    /**
     * @return array{processed: int, updated: int, skipped: int}
     */
    public static function recalculateAllLifetimePoints(MallBusinessConfig $row): array
    {
        if (intval($row->lifetime_points_enabled ?? 0) !== 1) {
            throw new \RuntimeException('Lifetime points source is disabled');
        }

        $config = self::configFromRow($row);
        $ratio = floatval($config['lifetime_points_ratio'] ?? 0);
        if ($ratio < 0) {
            throw new \RuntimeException('Parameter error');
        }

        $stats = ['processed' => 0, 'updated' => 0, 'skipped' => 0];

        $rows = Db::query(
            'SELECT d.* FROM mall_daily_push d INNER JOIN (
                SELECT user_id, MAX(`date`) AS max_date FROM mall_daily_push GROUP BY user_id
            ) latest ON d.user_id = latest.user_id AND d.date = latest.max_date'
        );

        foreach ($rows as $pushRow) {
            $stats['processed']++;
            $playxUserId = trim(strval($pushRow['user_id'] ?? ''));
            if ($playxUserId === '') {
                $stats['skipped']++;
                continue;
            }

            $lifetimeWl = $pushRow['lifetime_win_loss_net'] ?? null;
            if ($lifetimeWl === null || $lifetimeWl === '') {
                $stats['skipped']++;
                continue;
            }

            $target = self::lossToPoints(floatval($lifetimeWl), $ratio);
            $asset = MallUserAsset::where('playx_user_id', $playxUserId)->lock(true)->find();
            if (!$asset) {
                $stats['skipped']++;
                continue;
            }

            $before = intval($asset->lifetime_converted_points ?? 0);
            if ($before === $target) {
                $stats['skipped']++;
                continue;
            }

            self::applyLifetimeConversion($asset, $target);
            $asset->save();
            $stats['updated']++;
        }

        return $stats;
    }

    public static function remainingClaimable(MallUserAsset $asset): int
    {
        $cap = max(0, intval($asset->points_claim_cap ?? 0));
        $claimed = max(0, intval($asset->total_points_claimed ?? 0));

        return max(0, $cap - $claimed);
    }

    public static function resolveActiveMallOpenDate(MallBusinessConfig $row): ?string
    {
        $pending = self::normalizeDate($row->mall_open_date ?? null);
        $previous = self::normalizeDate($row->mall_open_date_previous ?? null);
        $effectiveOn = self::normalizeDate($row->mall_open_date_effective_on ?? null);
        $today = date('Y-m-d');

        if ($pending === null) {
            return $previous;
        }
        if ($effectiveOn === null || $today >= $effectiveOn) {
            return $pending;
        }

        return $previous;
    }

    public static function normalizeDate(mixed $value): ?string
    {
        if ($value === null || $value === '') {
            return null;
        }
        $str = trim(strval($value));
        if ($str === '') {
            return null;
        }
        if (strlen($str) >= 10) {
            $str = substr($str, 0, 10);
        }
        $parsed = \DateTime::createFromFormat('Y-m-d', $str);

        return $parsed && $parsed->format('Y-m-d') === $str ? $str : null;
    }

    public static function nextCalendarDate(string $fromDate = ''): string
    {
        $base = $fromDate !== '' ? $fromDate : date('Y-m-d');
        $dt = \DateTime::createFromFormat('Y-m-d', $base);
        if (!$dt) {
            $dt = new \DateTime();
        }
        $dt->modify('+1 day');

        return $dt->format('Y-m-d');
    }

    public static function parseLifetimeWinLossFromMember(array $member): ?float
    {
        if (!array_key_exists('ltv_wl', $member) && !array_key_exists('lifetime_total_wl', $member) && !array_key_exists('ltv_total_wl', $member)) {
            return null;
        }
        $raw = $member['ltv_wl'] ?? ($member['lifetime_total_wl'] ?? ($member['ltv_total_wl'] ?? null));
        if ($raw === null || $raw === '') {
            return null;
        }
        if (!is_numeric($raw)) {
            return null;
        }

        return floatval($raw);
    }
}
