<?php
declare(strict_types=1);

namespace app\dice\service;

use app\dice\helper\AdminScopeHelper;
use app\dice\logic\reward\DiceRewardLogic;
use app\dice\model\lottery_pool_config\DiceLotteryPoolConfig;
use app\dice\model\reward\DiceReward;
use app\dice\model\reward_config\DiceRewardConfig;
use plugin\saiadmin\app\model\system\SystemDept;
use plugin\saiadmin\app\model\system\SystemUser;
use plugin\saiadmin\exception\ApiException;
use support\think\Db;

/**
 * 渠道默认配置复制、补齐与关联删除
 * 默认配置：dept_id = 0（与超管「默认配置模板」一致）
 */
class DiceChannelConfigService
{
    /** 需 (dept_id, id) 复合唯一的配置表 */
    private const COMPOSITE_KEY_TABLES = [
        'dice_config',
        'dice_reward_config',
    ];
    /** 从默认模板复制的配置表 */
    private const CONFIG_TABLES = [
        'dice_config',
        'dice_ante_config',
        'dice_lottery_pool_config',
        'dice_reward_config',
        'dice_game',
    ];

    /** 复制时必须保留主键 id（非自增或固定 0-25） */
    private const TABLES_KEEP_ID = [
        'dice_config',
        'dice_reward_config',
    ];

    /** 可关联删除的业务表 */
    private const RELATION_TABLES = [
        'dice_config' => ['label' => '游戏键值配置', 'group' => 'configs'],
        'dice_ante_config' => ['label' => '底注配置', 'group' => 'configs'],
        'dice_lottery_pool_config' => ['label' => '彩金池配置', 'group' => 'configs'],
        'dice_reward_config' => ['label' => '奖励索引配置', 'group' => 'configs'],
        'dice_reward' => ['label' => '中奖概率(奖励对照)', 'group' => 'configs'],
        'dice_game' => ['label' => '游戏管理', 'group' => 'configs'],
        'dice_player' => ['label' => '玩家', 'group' => 'players'],
        'dice_play_record' => ['label' => '抽奖记录', 'group' => 'records'],
        'dice_play_record_test' => ['label' => '测试抽奖记录', 'group' => 'records'],
        'dice_player_wallet_record' => ['label' => '钱包流水', 'group' => 'records'],
        'dice_player_ticket_record' => ['label' => '票券记录', 'group' => 'records'],
        'dice_reward_config_record' => ['label' => '权重测试记录', 'group' => 'records'],
    ];

    /** 关联数据删除顺序：先删流水/记录，再删玩家，最后删配置 */
    private const DELETE_TABLE_ORDER = [
        'dice_play_record',
        'dice_play_record_test',
        'dice_player_wallet_record',
        'dice_player_ticket_record',
        'dice_reward_config_record',
        'dice_player',
        'dice_reward',
        'dice_reward_config',
        'dice_config',
        'dice_ante_config',
        'dice_lottery_pool_config',
        'dice_game',
    ];

    /**
     * 默认模板 dept_id 统一为 0，并为固定 id 的配置表建立 (dept_id, id) 唯一约束
     */
    public function ensureConfigCompositeKeys(): void
    {
        foreach (array_merge(self::CONFIG_TABLES, ['dice_reward']) as $table) {
            if ($this->tableHasColumn($table, 'dept_id')) {
                Db::table($table)->whereNull('dept_id')->update(['dept_id' => AdminScopeHelper::DEFAULT_TEMPLATE_DEPT]);
            }
        }
        foreach (self::COMPOSITE_KEY_TABLES as $table) {
            if (!$this->tableHasColumn($table, 'dept_id')) {
                continue;
            }
            if (!$this->tableHasColumn($table, 'row_id')) {
                if ($table === 'dice_reward_config') {
                    Db::execute(
                        'ALTER TABLE `dice_reward_config`'
                        . ' MODIFY `id` int(11) NOT NULL COMMENT \'ID\','
                        . ' ADD COLUMN `row_id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT FIRST,'
                        . ' DROP PRIMARY KEY,'
                        . ' ADD PRIMARY KEY (`row_id`)'
                    );
                } else {
                    Db::execute(
                        'ALTER TABLE `dice_config`'
                        . ' ADD COLUMN `row_id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT FIRST,'
                        . ' DROP PRIMARY KEY,'
                        . ' ADD PRIMARY KEY (`row_id`)'
                    );
                }
            }
            $indexes = Db::query("SHOW INDEX FROM `{$table}` WHERE Key_name = 'uk_dept_config'");
            if (empty($indexes)) {
                Db::execute("ALTER TABLE `{$table}` ADD UNIQUE KEY `uk_dept_config` (`dept_id`, `id`)");
            }
        }
        $this->ensureDeptScopedUniqueIndexes();
    }

    /**
     * 将全局唯一键改为按渠道 (dept_id, 业务键) 唯一，便于复制默认模板
     */
    private function ensureDeptScopedUniqueIndexes(): void
    {
        if ($this->tableHasColumn('dice_lottery_pool_config', 'dept_id')) {
            $old = Db::query("SHOW INDEX FROM `dice_lottery_pool_config` WHERE Key_name = 'dice_lottery_poll_config_unique'");
            if (!empty($old)) {
                Db::execute('ALTER TABLE `dice_lottery_pool_config` DROP INDEX `dice_lottery_poll_config_unique`');
            }
            $uk = Db::query("SHOW INDEX FROM `dice_lottery_pool_config` WHERE Key_name = 'uk_dept_name'");
            if (empty($uk)) {
                Db::execute('ALTER TABLE `dice_lottery_pool_config` ADD UNIQUE KEY `uk_dept_name` (`dept_id`, `name`)');
            }
        }
        if ($this->tableHasColumn('dice_game', 'dept_id')) {
            foreach (['uk_dice_game_code', 'uk_dice_game_key'] as $idx) {
                $exists = Db::query("SHOW INDEX FROM `dice_game` WHERE Key_name = '{$idx}'");
                if (!empty($exists)) {
                    Db::execute("ALTER TABLE `dice_game` DROP INDEX `{$idx}`");
                }
            }
            $ukCode = Db::query("SHOW INDEX FROM `dice_game` WHERE Key_name = 'uk_dept_game_code'");
            if (empty($ukCode)) {
                Db::execute('ALTER TABLE `dice_game` ADD UNIQUE KEY `uk_dept_game_code` (`dept_id`, `game_code`)');
            }
            $ukKey = Db::query("SHOW INDEX FROM `dice_game` WHERE Key_name = 'uk_dept_game_key'");
            if (empty($ukKey)) {
                Db::execute('ALTER TABLE `dice_game` ADD UNIQUE KEY `uk_dept_game_key` (`dept_id`, `game_key`)');
            }
        }
        if ($this->tableHasColumn('dice_reward', 'dept_id')) {
            $old = Db::query("SHOW INDEX FROM `dice_reward` WHERE Key_name = 'uk_direction_grid_number'");
            if (!empty($old)) {
                Db::execute('ALTER TABLE `dice_reward` DROP INDEX `uk_direction_grid_number`');
            }
            $uk = Db::query("SHOW INDEX FROM `dice_reward` WHERE Key_name = 'uk_dept_direction_grid'");
            if (empty($uk)) {
                Db::execute('ALTER TABLE `dice_reward` ADD UNIQUE KEY `uk_dept_direction_grid` (`dept_id`, `direction`, `grid_number`)');
            }
        }
    }

    /**
     * 将当前无 dept_id 的配置标记为默认模板（仅执行一次迁移）
     */
    public function markLegacyConfigAsDefault(): int
    {
        $this->ensureConfigCompositeKeys();
        $total = 0;
        foreach (self::CONFIG_TABLES as $table) {
            if (!$this->tableHasColumn($table, 'dept_id')) {
                continue;
            }
            $total += $this->countByDept($table, AdminScopeHelper::DEFAULT_TEMPLATE_DEPT);
        }
        return $total;
    }

    /**
     * 为单个渠道从默认模板复制配置（已存在则跳过）
     */
    public function copyDefaultConfigToDept(int $deptId): array
    {
        if ($deptId <= 0) {
            throw new ApiException('Invalid channel id');
        }
        $result = ['dept_id' => $deptId, 'copied' => [], 'skipped' => [], 'merged' => []];
        foreach (self::CONFIG_TABLES as $table) {
            if (!$this->tableHasColumn($table, 'dept_id')) {
                continue;
            }
            if (in_array($table, self::TABLES_KEEP_ID, true)) {
                $merged = $this->syncCompositeIdTableFromDefault($table, $deptId);
                if ($merged > 0) {
                    $result['merged'][$table] = $merged;
                } elseif ($this->countByDept($table, $deptId) > 0) {
                    $result['skipped'][] = $table;
                }
                continue;
            }
            if ($this->countByDept($table, $deptId) > 0) {
                $result['skipped'][] = $table;
                continue;
            }
            $rows = $this->defaultTemplateRows($table);
            if (empty($rows)) {
                continue;
            }
            foreach ($rows as $row) {
                $row = (array) $row;
                unset($row['id'], $row['row_id'], $row['create_time'], $row['update_time'], $row['delete_time']);
                $row['dept_id'] = $deptId;
                Db::table($table)->insert($row);
            }
            $result['copied'][] = $table;
        }
        $this->ensureRewardReferenceForDept($deptId);
        DiceRewardConfig::refreshCache($deptId);
        $this->ensurePlayerDefaultPoolForDept($deptId);
        $this->migratePlayersDefaultPoolToPlayerDefault($deptId);
        return $result;
    }

    /**
     * 为渠道补齐玩家默认彩金池 name=playerDefault（权重复制自 default）
     */
    public function ensurePlayerDefaultPoolForDept(int $deptId): ?int
    {
        if (!$this->tableHasColumn('dice_lottery_pool_config', 'dept_id')) {
            return null;
        }
        $exists = Db::table('dice_lottery_pool_config')
            ->where('dept_id', $deptId)
            ->where('name', DiceLotteryPoolConfig::NAME_PLAYER_DEFAULT)
            ->count();
        if ($exists > 0) {
            return (int) Db::table('dice_lottery_pool_config')
                ->where('dept_id', $deptId)
                ->where('name', DiceLotteryPoolConfig::NAME_PLAYER_DEFAULT)
                ->value('id');
        }
        $defaultRow = Db::table('dice_lottery_pool_config')
            ->where('dept_id', $deptId)
            ->where('name', 'default')
            ->find();
        if (!$defaultRow) {
            return null;
        }
        $defaultRow = (array) $defaultRow;
        unset($defaultRow['id'], $defaultRow['row_id'], $defaultRow['create_time'], $defaultRow['update_time'], $defaultRow['delete_time']);
        $defaultRow['name'] = DiceLotteryPoolConfig::NAME_PLAYER_DEFAULT;
        $defaultRow['remark'] = '默认';
        $defaultRow['safety_line'] = 0;
        $defaultRow['kill_enabled'] = 0;
        $defaultRow['profit_amount'] = 0;
        $defaultRow['dept_id'] = $deptId;
        return (int) Db::table('dice_lottery_pool_config')->insertGetId($defaultRow);
    }

    /**
     * 将仍关联 name=default 的玩家改为关联 playerDefault（杀分逻辑仍用 default 池）
     */
    public function migratePlayersDefaultPoolToPlayerDefault(int $deptId): int
    {
        if (!$this->tableHasColumn('dice_player', 'dept_id')
            || !$this->tableHasColumn('dice_player', 'lottery_config_id')) {
            return 0;
        }
        $playerDefaultId = Db::table('dice_lottery_pool_config')
            ->where('dept_id', $deptId)
            ->where('name', DiceLotteryPoolConfig::NAME_PLAYER_DEFAULT)
            ->value('id');
        if (!$playerDefaultId) {
            return 0;
        }
        $defaultPoolId = Db::table('dice_lottery_pool_config')
            ->where('dept_id', $deptId)
            ->where('name', 'default')
            ->value('id');
        if (!$defaultPoolId) {
            return 0;
        }
        return Db::table('dice_player')
            ->where('dept_id', $deptId)
            ->where('lottery_config_id', (int) $defaultPoolId)
            ->update(['lottery_config_id' => (int) $playerDefaultId]);
    }

    /**
     * 为全部渠道与默认模板补齐 playerDefault 奖池并迁移玩家关联
     */
    public function ensurePlayerDefaultPoolsAllChannels(): array
    {
        $deptIds = [AdminScopeHelper::DEFAULT_TEMPLATE_DEPT];
        foreach (SystemDept::column('id') as $id) {
            $id = (int) $id;
            if ($id > 0) {
                $deptIds[] = $id;
            }
        }
        $deptIds = array_values(array_unique($deptIds));
        $summary = [];
        foreach ($deptIds as $deptId) {
            $summary[$deptId] = [
                'pool_id' => $this->ensurePlayerDefaultPoolForDept($deptId),
                'players_migrated' => $this->migratePlayersDefaultPoolToPlayerDefault($deptId),
            ];
        }
        return $summary;
    }

    /**
     * 按业务 id 从默认模板补齐配置（dice_config / dice_reward_config）
     */
    private function syncCompositeIdTableFromDefault(string $table, int $deptId): int
    {
        $templateRows = $this->defaultTemplateRows($table);
        if (empty($templateRows)) {
            return 0;
        }
        $inserted = 0;
        foreach ($templateRows as $row) {
            $row = (array) $row;
            if (!isset($row['id'])) {
                continue;
            }
            $businessId = $row['id'];
            $exists = Db::table($table)->where('dept_id', $deptId)->where('id', $businessId)->count();
            if ($exists > 0) {
                continue;
            }
            unset($row['row_id'], $row['create_time'], $row['update_time'], $row['delete_time']);
            $row['dept_id'] = $deptId;
            Db::table($table)->insert($row);
            $inserted++;
        }
        return $inserted;
    }

    /**
     * 渠道已有奖励索引时，自动生成 dice_reward 对照表
     */
    public function ensureRewardReferenceForDept(int $deptId): void
    {
        if ($deptId <= 0 || !$this->tableHasColumn('dice_reward', 'dept_id')) {
            return;
        }
        if ($this->countByDept('dice_reward_config', $deptId) <= 0) {
            return;
        }
        if ($this->countByDept('dice_reward', $deptId) > 0) {
            return;
        }
        $logic = new DiceRewardLogic();
        $logic->createRewardReferenceFromConfig($deptId);
    }

    /**
     * 复制默认 dice_reward 到渠道
     */
    public function copyDefaultRewardsToDept(int $deptId): void
    {
        $this->ensureRewardReferenceForDept($deptId);
        if (!$this->tableHasColumn('dice_reward', 'dept_id')) {
            return;
        }
        if ($this->countByDept('dice_reward', $deptId) > 0) {
            return;
        }
        if ($this->countByDept('dice_reward_config', $deptId) > 0) {
            return;
        }
        $rows = $this->defaultTemplateRows('dice_reward');
        foreach ($rows as $row) {
            $row = (array) $row;
            unset($row['id'], $row['row_id']);
            unset($row['create_time'], $row['update_time'], $row['delete_time']);
            $row['dept_id'] = $deptId;
            Db::table('dice_reward')->insert($row);
        }
    }

    /**
     * 为所有已有渠道补齐缺失配置
     */
    public function syncAllChannelsFromDefault(): array
    {
        $deptIds = SystemDept::column('id');
        $summary = [];
        foreach ($deptIds as $deptId) {
            $deptId = (int) $deptId;
            if ($deptId <= 0) {
                continue;
            }
            $summary[$deptId] = $this->copyDefaultConfigToDept($deptId);
        }
        $summary['_player_default_pools'] = $this->ensurePlayerDefaultPoolsAllChannels();
        return $summary;
    }

    /**
     * 修复已删除渠道 ID、无管理员关联的遗留数据，归并到首个顶级渠道
     */
    public function repairOrphanDeptReferences(): array
    {
        $validDeptIds = array_map('intval', SystemDept::column('id') ?: []);
        if (empty($validDeptIds)) {
            return [];
        }
        $rootDeptId = min($validDeptIds);
        $stats = [];
        $inList = implode(',', $validDeptIds);

        $stats['sa_system_user'] = Db::execute(
            "UPDATE sa_system_user SET dept_id = {$rootDeptId}
             WHERE dept_id IS NOT NULL AND dept_id > 0 AND dept_id NOT IN ({$inList})"
        );

        $bizTables = [
            'dice_player',
            'dice_play_record',
            'dice_play_record_test',
            'dice_player_wallet_record',
            'dice_player_ticket_record',
            'dice_reward_config_record',
        ];
        foreach ($bizTables as $table) {
            if (!$this->tableHasColumn($table, 'dept_id')) {
                continue;
            }
            $stats[$table . '_invalid_dept'] = Db::execute(
                "UPDATE `{$table}` SET dept_id = {$rootDeptId}
                 WHERE dept_id IS NOT NULL AND dept_id > 0 AND dept_id NOT IN ({$inList})"
            );
        }

        return $stats;
    }

    /**
     * 根据管理员/玩家回填 dept_id
     */
    public function backfillDataDeptId(): array
    {
        $stats = $this->repairOrphanDeptReferences();
        if ($this->tableHasColumn('dice_player', 'dept_id') && $this->tableHasColumn('dice_player', 'admin_id')) {
            $stats['dice_player'] = Db::execute(
                'UPDATE dice_player p INNER JOIN sa_system_user u ON p.admin_id = u.id
                 SET p.dept_id = u.dept_id WHERE (p.dept_id IS NULL OR p.dept_id = 0) AND u.dept_id IS NOT NULL AND u.dept_id > 0'
            );
        }
        $validDeptIds = SystemDept::column('id') ?: [];
        if (!empty($validDeptIds) && $this->tableHasColumn('dice_player', 'dept_id')) {
            $rootDeptId = (int) min($validDeptIds);
            $stats['dice_player_legacy'] = Db::table('dice_player')
                ->where(function ($q) {
                    $q->whereNull('dept_id')->whereOr('dept_id', 0);
                })
                ->update(['dept_id' => $rootDeptId]);
        }
        $stats = array_merge($stats, $this->backfillRecordDeptIdByPlayer('dice_play_record'));
        $stats = array_merge($stats, $this->backfillRecordDeptIdByAdmin('dice_play_record'));
        $stats = array_merge($stats, $this->backfillRecordDeptIdByPlayer('dice_player_wallet_record'));
        $stats = array_merge($stats, $this->backfillRecordDeptIdByPlayer('dice_player_ticket_record'));
        $stats = array_merge($stats, $this->backfillRecordDeptIdByAdmin('dice_play_record_test'));
        if (!empty($validDeptIds) && $this->tableHasColumn('dice_play_record_test', 'dept_id')) {
            $rootDeptId = (int) min($validDeptIds);
            $stats['dice_play_record_test_legacy'] = Db::table('dice_play_record_test')
                ->where(function ($q) {
                    $q->whereNull('dept_id')->whereOr('dept_id', 0);
                })
                ->update(['dept_id' => $rootDeptId]);
        }
        if ($this->tableHasColumn('dice_reward_config_record', 'dept_id')) {
            $stats['dice_reward_config_record'] = Db::execute(
                'UPDATE dice_reward_config_record r INNER JOIN sa_system_user u ON r.admin_id = u.id
                 SET r.dept_id = u.dept_id WHERE (r.dept_id IS NULL OR r.dept_id = 0) AND u.dept_id IS NOT NULL AND u.dept_id > 0'
            );
        }
        return $stats;
    }

    /**
     * 删除渠道前关联数据统计
     */
    public function getDestroyPreview(array $deptIds): array
    {
        $items = [];
        foreach ($deptIds as $deptId) {
            $deptId = (int) $deptId;
            if ($deptId <= 0) {
                continue;
            }
            $dept = SystemDept::find($deptId);
            $row = [
                'dept_id' => $deptId,
                'dept_name' => $dept ? $dept->name : '',
                'user_count' => SystemUser::where('dept_id', $deptId)->count(),
                'relations' => [],
            ];
            foreach (self::RELATION_TABLES as $table => $meta) {
                if (!$this->tableHasColumn($table, 'dept_id')) {
                    continue;
                }
                $count = $this->countByDept($table, $deptId);
                if ($count > 0) {
                    $row['relations'][] = [
                        'table' => $table,
                        'label' => $meta['label'],
                        'group' => $meta['group'],
                        'count' => $count,
                    ];
                }
            }
            $items[] = $row;
        }
        return $items;
    }

    /**
     * 删除渠道及勾选的关联数据
     *
     * @param array $deleteTables 要删除的表名列表
     */
    public function destroyDeptWithRelations(int $deptId, array $deleteTables): void
    {
        if ($deptId <= 0) {
            throw new ApiException('Invalid channel id');
        }
        $userCount = SystemUser::where('dept_id', $deptId)->count();
        if ($userCount > 0) {
            throw new ApiException('This channel has users, please delete or transfer them first');
        }
        $tablesToDelete = $this->sortTablesForDelete($deleteTables);
        Db::startTrans();
        try {
            foreach ($tablesToDelete as $table) {
                if (!$this->tableHasColumn($table, 'dept_id')) {
                    continue;
                }
                Db::table($table)->where('dept_id', $deptId)->delete();
            }
            Db::name('sa_system_role_dept')->where('dept_id', $deptId)->delete();
            SystemDept::destroy($deptId, true);
            Db::commit();
        } catch (\Throwable $e) {
            Db::rollback();
            throw new ApiException('Channel delete failed: ' . $e->getMessage());
        }
        DiceRewardConfig::refreshCache($deptId);
        DiceReward::refreshCache($deptId);
    }

    /**
     * 按依赖顺序排列待删表（勾选顺序无关）
     *
     * @param array<int, string> $deleteTables
     * @return array<int, string>
     */
    private function sortTablesForDelete(array $deleteTables): array
    {
        $allowed = array_keys(self::RELATION_TABLES);
        $picked = [];
        foreach ($deleteTables as $table) {
            if (is_string($table) && in_array($table, $allowed, true)) {
                $picked[$table] = true;
            }
        }
        $ordered = [];
        foreach (self::DELETE_TABLE_ORDER as $table) {
            if (isset($picked[$table])) {
                $ordered[] = $table;
                unset($picked[$table]);
            }
        }
        foreach (array_keys($picked) as $table) {
            $ordered[] = $table;
        }
        return $ordered;
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    private function defaultTemplateRows(string $table): array
    {
        $templateId = AdminScopeHelper::DEFAULT_TEMPLATE_DEPT;
        $rows = Db::table($table)->where('dept_id', $templateId)->select()->toArray();
        if (!empty($rows)) {
            return $rows;
        }
        return Db::table($table)->whereNull('dept_id')->select()->toArray();
    }

    private function backfillRecordDeptIdByPlayer(string $table): array
    {
        if (!$this->tableHasColumn($table, 'dept_id') || !$this->tableHasColumn($table, 'player_id')) {
            return [];
        }
        return [
            $table => Db::execute(
                "UPDATE `{$table}` r INNER JOIN dice_player p ON r.player_id = p.id
                 SET r.dept_id = p.dept_id WHERE (r.dept_id IS NULL OR r.dept_id = 0) AND p.dept_id IS NOT NULL AND p.dept_id > 0"
            ),
        ];
    }

    private function backfillRecordDeptIdByAdmin(string $table): array
    {
        if (!$this->tableHasColumn($table, 'dept_id') || !$this->tableHasColumn($table, 'admin_id')) {
            return [];
        }
        return [
            $table => Db::execute(
                "UPDATE `{$table}` r INNER JOIN sa_system_user u ON r.admin_id = u.id
                 SET r.dept_id = u.dept_id WHERE (r.dept_id IS NULL OR r.dept_id = 0) AND u.dept_id IS NOT NULL AND u.dept_id > 0"
            ),
        ];
    }

    private function countByDept(string $table, ?int $deptId): int
    {
        $query = Db::table($table);
        if ($deptId === null || $deptId === AdminScopeHelper::DEFAULT_TEMPLATE_DEPT) {
            $templateId = AdminScopeHelper::DEFAULT_TEMPLATE_DEPT;
            $query->where(function ($q) use ($templateId) {
                $q->where('dept_id', $templateId)->whereOr(function ($sub) {
                    $sub->whereNull('dept_id');
                });
            });
        } else {
            $query->where('dept_id', $deptId);
        }
        return $query->count();
    }

    private function tableHasColumn(string $table, string $column): bool
    {
        try {
            $fields = Db::getFields($table);
            return isset($fields[$column]);
        } catch (\Throwable $e) {
            return false;
        }
    }
}
