<?php

declare(strict_types=1);

namespace app\admin\controller\config;

use app\common\controller\Backend;
use app\common\library\game\DepositChannel as DepositChannelLib;
use app\common\library\game\DepositTier as DepositTierLib;
use app\common\library\game\FinanceCashierConfig as FinanceCashierConfigLib;
use app\common\service\GameHotDataCoordinator;
use app\common\service\GameHotDataLock;
use InvalidArgumentException;
use support\think\Db;
use support\Response;
use Throwable;
use Webman\Http\Request as WebmanRequest;

/**
 * 支付/收款配置（game_config.finance_cashier，含充值渠道）
 */
class FinanceCashierConfig extends Backend
{
    protected bool $modelValidate = false;

    protected array $noNeedPermission = ['index', 'save', 'tierList', 'tierSave'];

    private function hasNodePermission(WebmanRequest $request, string $action): bool
    {
        if (!$this->auth) {
            return false;
        }
        $controllerPath = get_controller_path($request);
        if (!$controllerPath) {
            return false;
        }
        $paths = [];
        $paths[] = $controllerPath . '/' . $action;
        $parts = explode('/', $controllerPath);
        foreach ($parts as &$part) {
            if (str_contains($part, '_')) {
                $part = lcfirst(str_replace(' ', '', ucwords(str_replace('_', ' ', $part))));
            }
        }
        $paths[] = implode('/', $parts) . '/' . $action;
        foreach (array_values(array_unique($paths)) as $path) {
            if ($this->auth->check($path)) {
                return true;
            }
        }

        return false;
    }

    protected function initController(WebmanRequest $request): ?Response
    {
        return null;
    }

    public function index(WebmanRequest $request): Response
    {
        $response = $this->initializeBackend($request);
        if ($response !== null) {
            return $response;
        }
        if (!$this->hasNodePermission($request, 'index')) {
            return $this->error(__('You have no permission'), [], 401);
        }
        if ($request->method() !== 'GET') {
            return $this->error(__('Parameter error'));
        }
        $row = Db::name('game_config')->where('config_key', FinanceCashierConfigLib::CONFIG_KEY)->find();
        $form = FinanceCashierConfigLib::parseFromConfigValue($row['config_value'] ?? null);

        $decoded = null;
        if (is_array($row) && isset($row['config_value']) && is_string($row['config_value']) && trim($row['config_value']) !== '') {
            $tmp = json_decode($row['config_value'], true);
            if (is_array($tmp)) {
                $decoded = $tmp;
            }
        }
        $channelsKeyPresent = is_array($decoded) && array_key_exists('channels', $decoded);
        if (!$channelsKeyPresent) {
            $depRow = Db::name('game_config')->where('config_key', DepositChannelLib::CONFIG_KEY)->find();
            $legacy = DepositChannelLib::parseOverridesFromConfigValue(is_array($depRow) ? ($depRow['config_value'] ?? null) : null);
            $form['channels'] = DepositChannelLib::expandRowsForAdmin($legacy);
        } else {
            $form['channels'] = DepositChannelLib::expandRowsForAdmin($form['channels'] ?? []);
        }

        return $this->success('', [
            'form'          => $form,
            'registry'      => $this->buildRegistryOut(),
        ]);
    }

    public function save(WebmanRequest $request): Response
    {
        $response = $this->initializeBackend($request);
        if ($response !== null) {
            return $response;
        }
        if (!$this->hasNodePermission($request, 'save')) {
            return $this->error(__('You have no permission'), [], 401);
        }
        if ($request->method() !== 'POST') {
            return $this->error(__('Parameter error'));
        }
        $payload = $request->post();
        if (!is_array($payload)) {
            return $this->error(__('Parameter %s can not be empty', ['']));
        }
        $pruneTierCurrencies = [];
        if (isset($payload['prune_tier_currency_codes']) && is_array($payload['prune_tier_currency_codes'])) {
            foreach ($payload['prune_tier_currency_codes'] as $c) {
                if (!is_string($c)) {
                    continue;
                }
                $s = strtoupper(trim($c));
                if ($s !== '') {
                    $pruneTierCurrencies[] = $s;
                }
            }
            $pruneTierCurrencies = array_values(array_unique($pruneTierCurrencies));
        }
        unset($payload['prune_tier_currency_codes']);

        try {
            $json = FinanceCashierConfigLib::encodeForDb($payload);
        } catch (InvalidArgumentException $e) {
            return $this->error($e->getMessage());
        }

        $now = time();
        $resourceKey = GameHotDataLock::safeResourceKeyForConfig(FinanceCashierConfigLib::CONFIG_KEY);
        $lock = GameHotDataLock::tryAcquire(GameHotDataLock::TYPE_GAME_CONFIG, $resourceKey);
        if (!$lock['acquired']) {
            return $this->error(__('This config is locked by another operation, please try again later'));
        }
        try {
            try {
                $exists = Db::name('game_config')->where('config_key', FinanceCashierConfigLib::CONFIG_KEY)->find();
                if ($exists) {
                    Db::name('game_config')->where('config_key', FinanceCashierConfigLib::CONFIG_KEY)->update([
                        'config_value' => $json,
                        'value_type'   => 'json',
                        'update_time'  => $now,
                    ]);
                } else {
                    Db::name('game_config')->insert([
                        'config_key'   => FinanceCashierConfigLib::CONFIG_KEY,
                        'config_value' => $json,
                        'value_type'   => 'json',
                        'remark'       => '支付/收款配置（表单维护，含充值渠道）',
                        'create_time'  => $now,
                        'update_time'  => $now,
                    ]);
                }
                $decodedSave = json_decode($json, true);
                $chList = is_array($decodedSave) && isset($decodedSave['channels']) && is_array($decodedSave['channels'])
                    ? $decodedSave['channels']
                    : [];
                $mirrorJson = DepositChannelLib::encodeForDb($chList);
                $depExists = Db::name('game_config')->where('config_key', DepositChannelLib::CONFIG_KEY)->find();
                if ($depExists) {
                    Db::name('game_config')->where('config_key', DepositChannelLib::CONFIG_KEY)->update([
                        'config_value' => $mirrorJson,
                        'value_type'   => 'json',
                        'update_time'  => $now,
                    ]);
                } else {
                    Db::name('game_config')->insert([
                        'config_key'   => DepositChannelLib::CONFIG_KEY,
                        'config_value' => $mirrorJson,
                        'value_type'   => 'json',
                        'remark'       => '充值渠道（与 finance_cashier.channels 同步）',
                        'create_time'  => $now,
                        'update_time'  => $now,
                    ]);
                }
                if ($pruneTierCurrencies !== []) {
                    $this->pruneDepositTiersByCurrencies($pruneTierCurrencies, $now);
                }
            } catch (Throwable $e) {
                return $this->error($e->getMessage());
            }
            GameHotDataCoordinator::afterGameConfigKeyCommitted(FinanceCashierConfigLib::CONFIG_KEY);
            GameHotDataCoordinator::afterGameConfigKeyCommitted(DepositChannelLib::CONFIG_KEY);
            if ($pruneTierCurrencies !== []) {
                GameHotDataCoordinator::afterGameConfigKeyCommitted(DepositTierLib::CONFIG_KEY);
            }

            return $this->success(__('Saved successfully'));
        } finally {
            GameHotDataLock::release(GameHotDataLock::TYPE_GAME_CONFIG, $resourceKey, $lock['token'], $lock['redis_lock']);
        }
    }

    public function tierList(WebmanRequest $request): Response
    {
        $response = $this->initializeBackend($request);
        if ($response !== null) {
            return $response;
        }
        if (!$this->hasNodePermission($request, 'tierList')) {
            return $this->error(__('You have no permission'), [], 401);
        }
        if ($request->method() !== 'GET') {
            return $this->error(__('Parameter error'));
        }
        $list = $this->loadDepositTierItems();

        return $this->success('', ['list' => $list]);
    }

    public function tierSave(WebmanRequest $request): Response
    {
        $response = $this->initializeBackend($request);
        if ($response !== null) {
            return $response;
        }
        if (!$this->hasNodePermission($request, 'tierSave')) {
            return $this->error(__('You have no permission'), [], 401);
        }
        if ($request->method() !== 'POST') {
            return $this->error(__('Parameter error'));
        }
        $payload = $request->post();
        if (!is_array($payload)) {
            return $this->error(__('Parameter error'));
        }
        $items = $payload['items'] ?? null;
        if (!is_array($items)) {
            return $this->error(__('Items must be an array'));
        }
        try {
            $clean = DepositTierLib::prepareItemsForSave(array_values($items));
            $json = DepositTierLib::encodeForDb($clean);
        } catch (InvalidArgumentException $e) {
            return $this->error($e->getMessage());
        }
        $now = time();
        $resourceKey = GameHotDataLock::safeResourceKeyForConfig(DepositTierLib::CONFIG_KEY);
        $lock = GameHotDataLock::tryAcquire(GameHotDataLock::TYPE_GAME_CONFIG, $resourceKey);
        if (!$lock['acquired']) {
            return $this->error(__('This config is locked by another operation, please try again later'));
        }
        try {
            $exists = Db::name('game_config')->where('config_key', DepositTierLib::CONFIG_KEY)->find();
            if ($exists) {
                Db::name('game_config')->where('config_key', DepositTierLib::CONFIG_KEY)->update([
                    'config_value' => $json,
                    'value_type'   => 'json',
                    'update_time'  => $now,
                ]);
            } else {
                Db::name('game_config')->insert([
                    'config_key'   => DepositTierLib::CONFIG_KEY,
                    'config_value' => $json,
                    'value_type'   => 'json',
                    'remark'       => '充值档位 JSON 数组（financeCashierConfig 统一维护）',
                    'create_time'  => $now,
                    'update_time'  => $now,
                ]);
            }
            GameHotDataCoordinator::afterGameConfigKeyCommitted(DepositTierLib::CONFIG_KEY);

            return $this->success(__('Saved successfully'));
        } catch (Throwable $e) {
            return $this->error($e->getMessage());
        } finally {
            GameHotDataLock::release(GameHotDataLock::TYPE_GAME_CONFIG, $resourceKey, $lock['token'], $lock['redis_lock']);
        }
    }

    /**
     * @return array<string, array{name: string, name_en: string, sort: int}>
     */
    private function buildRegistryOut(): array
    {
        $registry = DepositChannelLib::codeRegistry();
        $registryOut = [];
        foreach ($registry as $code => $meta) {
            if (!is_array($meta)) {
                continue;
            }
            $registryOut[$code] = [
                'name'    => isset($meta['name']) && is_string($meta['name']) ? $meta['name'] : '',
                'name_en' => isset($meta['name_en']) && is_string($meta['name_en']) ? $meta['name_en'] : '',
                'sort'    => isset($meta['sort']) && is_numeric($meta['sort']) ? intval($meta['sort']) : 10,
            ];
        }

        return $registryOut;
    }

    /**
     * @return list<array<string, mixed>>
     */
    private function loadDepositTierItems(): array
    {
        $row = Db::name('game_config')->where('config_key', DepositTierLib::CONFIG_KEY)->find();

        return DepositTierLib::parseFromConfigValue(is_array($row) ? ($row['config_value'] ?? null) : null);
    }

    /**
     * @param list<string> $currencyCodes
     */
    private function pruneDepositTiersByCurrencies(array $currencyCodes, int $now): void
    {
        if ($currencyCodes === []) {
            return;
        }
        $exists = Db::name('game_config')->where('config_key', DepositTierLib::CONFIG_KEY)->find();
        $items = $this->loadDepositTierItems();
        $filtered = [];
        foreach ($items as $row) {
            if (!is_array($row)) {
                continue;
            }
            $currency = isset($row['currency']) && is_string($row['currency']) ? strtoupper(trim($row['currency'])) : '';
            if ($currency !== '' && in_array($currency, $currencyCodes, true)) {
                continue;
            }
            $filtered[] = $row;
        }
        $json = DepositTierLib::encodeForDb(DepositTierLib::prepareItemsForSave(array_values($filtered)));
        if ($exists) {
            Db::name('game_config')->where('config_key', DepositTierLib::CONFIG_KEY)->update([
                'config_value' => $json,
                'value_type'   => 'json',
                'update_time'  => $now,
            ]);
        } else {
            Db::name('game_config')->insert([
                'config_key'   => DepositTierLib::CONFIG_KEY,
                'config_value' => $json,
                'value_type'   => 'json',
                'remark'       => '充值档位 JSON 数组（financeCashierConfig 统一维护）',
                'create_time'  => $now,
                'update_time'  => $now,
            ]);
        }
    }
}
