<?php

declare(strict_types=1);

namespace app\common\library\game;

use app\common\library\finance\MockPay;
use InvalidArgumentException;
use support\think\Db;

/**
 * 充值支付渠道：优先读取 game_config.finance_cashier.channels；无此键时回退 game_config.deposit_channel（迁移期镜像）
 *
 * 每项：code（须在代码/环境注册表内）、sort、status(0/1)。内置 `ddpay`（DDPay）、`mock`（模拟支付，见 FINANCE_MOCK_PAY_ENABLED）。
 *
 * 渠道展示名以代码注册表为准；运营只配置开关、排序与支持币种，默认兼容全部充值档位。
 */
final class DepositChannel
{
    public const CONFIG_KEY = 'deposit_channel';

    /**
     * @return array<string, array{name: string, name_en: string, sort: int}>
     */
    public static function codeRegistry(): array
    {
        $base = [
            'ddpay' => ['name' => 'DDPay', 'name_en' => 'DDPay', 'sort' => 10],
            'mock'  => ['name' => '模拟支付', 'name_en' => 'Mock Pay', 'sort' => 5],
        ];
        $extra = self::registryFromEnv();
        foreach ($extra as $code => $meta) {
            if (!is_string($code) || trim($code) === '' || !is_array($meta)) {
                continue;
            }
            $normCode = strtolower(trim($code));
            if (!preg_match('/^[a-z0-9_\-]{1,24}$/', $normCode)) {
                continue;
            }
            $name = isset($meta['name']) && is_string($meta['name']) ? trim($meta['name']) : '';
            $nameEn = isset($meta['name_en']) && is_string($meta['name_en']) ? trim($meta['name_en']) : '';
            $sort = isset($meta['sort']) && is_numeric($meta['sort']) ? intval($meta['sort']) : 50;
            if ($name === '') {
                continue;
            }
            $base[$normCode] = [
                'name'    => $name,
                'name_en' => $nameEn !== '' ? $nameEn : $name,
                'sort'    => $sort,
            ];
        }
        return $base;
    }

    /**
     * @return array<string, mixed>
     */
    private static function registryFromEnv(): array
    {
        $raw = getenv('DEPOSIT_CHANNELS_REGISTRY_JSON');
        if (!is_string($raw) || trim($raw) === '') {
            return [];
        }
        $decoded = json_decode($raw, true);
        if (!is_array($decoded)) {
            return [];
        }
        $out = [];
        foreach ($decoded as $item) {
            if (!is_array($item)) {
                continue;
            }
            $code = isset($item['code']) && is_string($item['code']) ? strtolower(trim($item['code'])) : '';
            if ($code === '') {
                continue;
            }
            $out[$code] = $item;
        }
        return $out;
    }

    public static function parseOverridesFromConfigValue($raw): array
    {
        if (!is_string($raw) || trim($raw) === '') {
            return [];
        }
        $decoded = json_decode($raw, true);
        if (!is_array($decoded)) {
            return [];
        }
        $list = isset($decoded['channels']) && is_array($decoded['channels']) ? $decoded['channels'] : $decoded;

        return self::normalizeOverrides($list);
    }

    /**
     * 库中「运营覆盖」原始列表（未与注册表合并）：finance_cashier 含 channels 键时用其值；否则读 deposit_channel
     *
     * @return list<array{code: string, sort: int, status: int, tier_ids: list<string>}>
     */
    public static function parseStoredOverridesFromDb(): array
    {
        $fcRow = Db::name('game_config')->where('config_key', FinanceCashierConfig::CONFIG_KEY)->find();
        $rawFc = is_array($fcRow) ? ($fcRow['config_value'] ?? null) : null;
        $decoded = null;
        if (is_string($rawFc) && trim($rawFc) !== '') {
            $tmp = json_decode($rawFc, true);
            if (is_array($tmp)) {
                $decoded = $tmp;
            }
        }
        $channelsKeyPresent = is_array($decoded) && array_key_exists('channels', $decoded);
        if ($channelsKeyPresent) {
            $list = isset($decoded['channels']) && is_array($decoded['channels']) ? $decoded['channels'] : [];

            return self::normalizeOverrides($list);
        }
        $depRow = Db::name('game_config')->where('config_key', self::CONFIG_KEY)->find();
        $depRaw = is_array($depRow) ? ($depRow['config_value'] ?? null) : null;

        return self::parseOverridesFromConfigValue($depRaw);
    }

    /**
     * 与注册表合并并排序后的有效渠道行（业务侧统一入口）
     *
     * @return list<array{code: string, sort: int, status: int, tier_ids: list<string>}>
     */
    public static function effectiveRowsFromDb(): array
    {
        $stored = self::parseStoredOverridesFromDb();

        return self::effectiveOverrides(self::expandRowsForAdmin($stored));
    }

    /**
     * @param list<mixed> $items
     *
     * @return list<array{code: string, sort: int, status: int, tier_ids: list<string>}>
     */
    public static function normalizeOverrides(array $items): array
    {
        $out = [];
        foreach ($items as $row) {
            if (!is_array($row)) {
                continue;
            }
            $code = isset($row['code']) && is_string($row['code']) ? strtolower(trim($row['code'])) : '';
            if ($code === '') {
                continue;
            }
            $sort = isset($row['sort']) && is_numeric($row['sort']) ? intval($row['sort']) : 0;
            $status = isset($row['status']) && is_numeric($row['status']) ? intval($row['status']) : 1;
            $status = $status === 1 ? 1 : 0;
            $currencyCodes = null;
            if (array_key_exists('currency_codes', $row)) {
                if (is_array($row['currency_codes'])) {
                    $currencyCodes = self::normalizeCurrencyCodes($row['currency_codes']);
                } else {
                    $currencyCodes = null;
                }
            }
            $out[] = [
                'code'     => $code,
                'sort'     => $sort,
                'status'   => $status,
                'tier_ids' => [],
                // null 表示“兼容全部充值币种”（历史配置默认行为）
                // 空数组 [] 表示“不支持任何充值币种”（运营可用作显式禁用某币种）
                'currency_codes' => $currencyCodes,
            ];
        }

        return $out;
    }

    /**
     * 归一化为小写/去空并返回大写币种码列表（允许为空数组）。
     *
     * @param mixed $raw
     * @return list<string>
     */
    private static function normalizeCurrencyCodes(mixed $raw): array
    {
        if (!is_array($raw)) {
            return [];
        }
        $out = [];
        foreach ($raw as $c) {
            if (!is_string($c) && !is_numeric($c)) {
                continue;
            }
            $s = is_string($c) ? trim($c) : strval($c);
            $s = strtoupper($s);
            if (!preg_match('/^[A-Z0-9]{2,12}$/', $s)) {
                continue;
            }
            $out[] = $s;
        }
        $out = array_values(array_unique($out));
        return $out;
    }

    /**
     * 合并注册表与运营覆盖；若库中无覆盖则对注册表内全部渠道启用默认行
     *
     * @param list<array{code: string, sort: int, status: int, tier_ids: list<string>}> $overrides
     *
     * @return list<array{code: string, sort: int, status: int, tier_ids: list<string>}>
     */
    public static function effectiveOverrides(array $overrides): array
    {
        $registry = self::codeRegistry();
        $byCode = [];
        foreach ($overrides as $row) {
            if (!isset($registry[$row['code']])) {
                continue;
            }
            $byCode[$row['code']] = $row;
        }
        if ($byCode === []) {
            foreach ($registry as $code => $meta) {
                $sortMeta = $meta['sort'] ?? 10;
                $sortVal = is_numeric($sortMeta) ? intval($sortMeta) : 10;
                $byCode[$code] = [
                    'code'     => $code,
                    'sort'     => $sortVal,
                    'status'   => 1,
                    'tier_ids' => [],
                    'currency_codes' => null, // 默认兼容全部币种（历史行为）
                ];
            }
        }
        $list = array_values($byCode);
        usort($list, static function (array $a, array $b): int {
            if ($a['sort'] !== $b['sort']) {
                return $a['sort'] <=> $b['sort'];
            }

            return strcmp($a['code'], $b['code']);
        });

        return $list;
    }

    /**
     * @param array{code: string, sort: int, status: int, tier_ids: list<string>} $overrideRow
     */
    public static function isTierAllowed(array $overrideRow, string $tierId): bool
    {
        // 渠道不再配置档位白名单：默认兼容全部充值档位。
        return true;
    }

    /**
     * @param list<array<string, mixed>> $effectiveRows
     */
    public static function findMergedByCode(array $effectiveRows, string $code): ?array
    {
        $norm = strtolower(trim($code));
        foreach ($effectiveRows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $c = isset($row['code']) && is_string($row['code']) ? strtolower(trim($row['code'])) : '';
            if ($c === $norm) {
                return $row;
            }
        }

        return null;
    }

    /**
     * @param list<array{code: string, sort: int, status: int, tier_ids: list<string>}> $overrideRows
     *
     * @return list<array{code: string, name: string, sort: int}>
     */
    public static function channelsForTier(string $tierId, array $overrideRows, string $lang, string $fiatCurrencyCode = ''): array
    {
        $registry = self::codeRegistry();
        $out = [];
        foreach ($overrideRows as $row) {
            if (($row['status'] ?? 0) !== 1) {
                continue;
            }
            $code = $row['code'];
            if (!isset($registry[$code])) {
                continue;
            }
            if ($code === MockPay::CHANNEL_CODE && !MockPay::isEnabled()) {
                continue;
            }
            if ($fiatCurrencyCode !== '' && !self::isCurrencyAllowedForRow($row, $fiatCurrencyCode)) {
                continue;
            }
            $meta = $registry[$code];
            $name = self::pickLangName($meta, $lang);
            $sortRaw = $row['sort'] ?? 0;
            $sortVal = is_numeric($sortRaw) ? intval($sortRaw) : 0;
            $out[] = [
                'code' => $code,
                'name' => $name,
                'sort' => $sortVal,
            ];
        }
        usort($out, static function (array $a, array $b): int {
            if ($a['sort'] !== $b['sort']) {
                return $a['sort'] <=> $b['sort'];
            }

            return strcmp($a['code'], $b['code']);
        });

        return $out;
    }

    private static function isCurrencyAllowedForRow(array $row, string $fiatCurrencyCode): bool
    {
        $normCurrency = strtoupper(trim($fiatCurrencyCode));
        if ($normCurrency === '') {
            return true;
        }
        $cc = $row['currency_codes'] ?? null;
        if ($cc === null) {
            // 历史/默认配置：不填则表示兼容全部币种
            return true;
        }
        if (!is_array($cc)) {
            return true;
        }
        // 显式空数组：不支持任何币种
        if ($cc === []) {
            return false;
        }
        return in_array($normCurrency, $cc, true);
    }

    /**
     * @param array<string, string> $meta
     */
    private static function pickLangName(array $meta, string $lang): string
    {
        $name = isset($meta['name']) && is_string($meta['name']) ? $meta['name'] : '';
        $nameEn = isset($meta['name_en']) && is_string($meta['name_en']) ? $meta['name_en'] : '';
        $normalized = strtolower(str_replace('_', '-', trim($lang)));
        $isEn = $normalized === 'en' || str_starts_with($normalized, 'en-');
        if ($isEn) {
            return $nameEn !== '' ? $nameEn : $name;
        }

        return $name !== '' ? $name : $nameEn;
    }

    /**
     * @param list<array{code: string, sort: int, status: int, tier_ids: list<string>}> $effectiveRows
     */
    public static function assertChannelAllowsTier(string $channelCode, string $tierId, array $effectiveRows): bool
    {
        $row = self::findMergedByCode($effectiveRows, $channelCode);
        if ($row === null) {
            return false;
        }
        if (($row['status'] ?? 0) !== 1) {
            return false;
        }

        return self::isTierAllowed($row, $tierId);
    }

    /**
     * @param array<array{code: string, sort: int, status: int, tier_ids: list<string>, currency_codes: (list<string>|null)}> $effectiveRows
     */
    public static function assertChannelAllowsCurrency(string $channelCode, string $fiatCurrencyCode, array $effectiveRows): bool
    {
        $row = self::findMergedByCode($effectiveRows, $channelCode);
        if ($row === null) {
            return false;
        }
        if (($row['status'] ?? 0) !== 1) {
            return false;
        }

        return self::isCurrencyAllowedForRow($row, $fiatCurrencyCode);
    }

    /**
     * @param list<array{code: string, sort: int, status: int, tier_ids: list<string>}> $effectiveRows
     *
     * @return list<string>
     */
    public static function enabledPayChannelCodes(array $effectiveRows): array
    {
        $registry = self::codeRegistry();
        $codes = [];
        foreach ($effectiveRows as $row) {
            if (($row['status'] ?? 0) !== 1) {
                continue;
            }
            $c = isset($row['code']) && is_string($row['code']) ? $row['code'] : '';
            if ($c !== '' && isset($registry[$c])) {
                $codes[] = $c;
            }
        }

        return array_values(array_unique($codes));
    }

    /**
     * 当前服务端已对接自动出金的渠道（与 withdrawCreate 校验一致）
     *
     * @return list<string>
     */
    public static function withdrawPayoutChannelCodes(): array
    {
        $codes = ['ddpay'];
        if (MockPay::isEnabled()) {
            $codes[] = MockPay::CHANNEL_CODE;
        }

        return $codes;
    }

    /**
     * @param list<array{code: string, sort: int, status: int, tier_ids: list<string>}> $effectiveRows
     */
    public static function assertChannelEnabled(string $channelCode, array $effectiveRows): bool
    {
        $row = self::findMergedByCode($effectiveRows, $channelCode);
        if ($row === null) {
            return false;
        }

        return ($row['status'] ?? 0) === 1;
    }

    /**
     * 提现页可选支付渠道（仅返回已启用且已对接出金的渠道）
     *
     * @param list<array{code: string, sort: int, status: int, tier_ids: list<string>}> $effectiveRows
     *
     * @return list<array{code: string, name: string, sort: int}>
     */
    public static function channelsForWithdraw(array $effectiveRows, string $lang): array
    {
        $registry = self::codeRegistry();
        $allowed = self::withdrawPayoutChannelCodes();
        $out = [];
        foreach ($effectiveRows as $row) {
            if (($row['status'] ?? 0) !== 1) {
                continue;
            }
            $code = isset($row['code']) && is_string($row['code']) ? $row['code'] : '';
            if ($code === '' || !in_array($code, $allowed, true)) {
                continue;
            }
            if (!isset($registry[$code])) {
                continue;
            }
            $meta = $registry[$code];
            $sortRaw = $row['sort'] ?? 0;
            $sortVal = is_numeric($sortRaw) ? intval($sortRaw) : 0;
            $out[] = [
                'code' => $code,
                'name' => self::pickLangName($meta, $lang),
                'sort' => $sortVal,
            ];
        }
        usort($out, static function (array $a, array $b): int {
            if ($a['sort'] !== $b['sort']) {
                return $a['sort'] <=> $b['sort'];
            }

            return strcmp($a['code'], $b['code']);
        });

        return $out;
    }

    /**
     * @param list<array<string, mixed>> $items
     *
     * @return list<array{code: string, sort: int, status: int, tier_ids: list<string>}>
     */
    public static function prepareOverridesForSave(array $items): array
    {
        $registry = self::codeRegistry();
        $seen = [];
        $out = [];
        foreach ($items as $idx => $row) {
            if (!is_array($row)) {
                throw new InvalidArgumentException('Row format error');
            }
            $code = isset($row['code']) && is_string($row['code']) ? strtolower(trim($row['code'])) : '';
            if ($code === '' || !isset($registry[$code])) {
                throw new InvalidArgumentException('Channel code is not registered');
            }
            if (isset($seen[$code])) {
                throw new InvalidArgumentException('Duplicate channel code');
            }
            $seen[$code] = true;
            $norm = self::normalizeOverrides([array_merge($row, ['code' => $code])]);
            if ($norm === []) {
                throw new InvalidArgumentException('Invalid channel row data');
            }
            $out[] = $norm[0];
        }
        usort($out, static function (array $a, array $b): int {
            if ($a['sort'] !== $b['sort']) {
                return $a['sort'] <=> $b['sort'];
            }

            return strcmp($a['code'], $b['code']);
        });

        return $out;
    }

    /**
     * @param list<array{code: string, sort: int, status: int, tier_ids: list<string>}> $items
     */
    public static function encodeForDb(array $items): string
    {
        $wrapped = ['channels' => $items];
        $encoded = json_encode($wrapped, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        if ($encoded === false) {
            throw new InvalidArgumentException('JSON encode failed');
        }

        return $encoded;
    }

    /**
     * 后台编辑用：为注册表内每个 code 补齐一行（合并库内覆盖）
     *
     * @param list<array{code: string, sort: int, status: int, tier_ids: list<string>}> $storedOverrides
     *
     * @return list<array{code: string, sort: int, status: int, tier_ids: list<string>}>
     */
    public static function expandRowsForAdmin(array $storedOverrides): array
    {
        $registry = self::codeRegistry();
        $effective = self::effectiveOverrides($storedOverrides);
        $byCode = [];
        foreach ($effective as $r) {
            $byCode[$r['code']] = $r;
        }
        $items = [];
        foreach ($registry as $code => $meta) {
            $sortMeta = $meta['sort'] ?? 10;
            $sortDefault = is_numeric($sortMeta) ? intval($sortMeta) : 10;
            $items[] = $byCode[$code] ?? [
                'code'     => $code,
                'sort'     => $sortDefault,
                'status'   => 1,
                'tier_ids' => [],
                'currency_codes' => null,
            ];
        }
        usort($items, static function (array $a, array $b): int {
            if ($a['sort'] !== $b['sort']) {
                return $a['sort'] <=> $b['sort'];
            }

            return strcmp($a['code'], $b['code']);
        });

        return $items;
    }
}
