<?php
declare(strict_types=1);

namespace plugin\saiadmin\app\service;

use plugin\saiadmin\app\model\system\SystemDept;
use plugin\saiadmin\app\model\system\SystemRole;
use plugin\saiadmin\exception\ApiException;
use support\think\Db;

/**
 * 渠道角色：从默认模板复制指定角色、同步与删除
 */
class SystemRoleChannelService
{
    /** 全局超级管理员角色，不参与渠道复制 */
    public const SUPER_ADMIN_ROLE_ID = 1;

    /**
     * 为渠道从默认模板复制三个代理角色（缺失则补齐，不整包跳过）
     */
    public function copyDefaultRolesToDept(int $deptId, bool $pruneExtra = false): array
    {
        if ($deptId <= 0) {
            throw new ApiException('Invalid channel id');
        }
        if (!$this->tableHasColumn('sa_system_role', 'dept_id')) {
            return ['dept_id' => $deptId, 'copied' => 0, 'skipped' => 0, 'pruned' => 0, 'message' => 'dept_id column missing'];
        }

        $templates = $this->defaultTemplateRoles();
        if (empty($templates)) {
            return ['dept_id' => $deptId, 'copied' => 0, 'skipped' => 0, 'pruned' => 0, 'message' => 'no template roles'];
        }

        $copied = 0;
        $skipped = 0;
        foreach ($templates as $template) {
            $template = (array) $template;
            $templateId = (int) ($template['id'] ?? 0);
            $code = (string) ($template['code'] ?? '');
            if ($templateId <= 0 || $code === '') {
                continue;
            }
            if ($this->roleExists($deptId, $code)) {
                $skipped++;
                continue;
            }

            $newId = $this->insertRoleFromTemplate($template, $deptId);
            if ($newId > 0) {
                $this->copyRoleMenus($templateId, $newId);
                $copied++;
            }
        }

        $pruned = 0;
        if ($pruneExtra) {
            $pruned = $this->pruneExtraChannelRoles($deptId);
        }

        return ['dept_id' => $deptId, 'copied' => $copied, 'skipped' => $skipped, 'pruned' => $pruned];
    }

    /**
     * 为所有已启用渠道补齐三个默认角色，并移除多余历史角色
     */
    public function syncAllChannelsFromDefault(): array
    {
        $deptIds = SystemDept::where('status', 1)->where('id', '>', 0)->column('id');
        $result = [];
        foreach ($deptIds as $deptId) {
            $result[(int) $deptId] = $this->copyDefaultRolesToDept((int) $deptId, true);
        }
        return $result;
    }

    /**
     * 删除渠道下全部角色及菜单关联
     */
    public function deleteRolesByDept(int $deptId): int
    {
        if ($deptId <= 0) {
            return 0;
        }
        $roleIds = SystemRole::where('dept_id', $deptId)->column('id');
        if (empty($roleIds)) {
            return 0;
        }
        Db::name('sa_system_user_role')->whereIn('role_id', $roleIds)->delete();
        Db::name('sa_system_role_menu')->whereIn('role_id', $roleIds)->delete();
        Db::name('sa_system_role_dept')->whereIn('role_id', $roleIds)->delete();
        return SystemRole::destroy($roleIds);
    }

    /**
     * 将用户已绑定的模板角色映射到其渠道对应角色（仅三个默认 code）
     */
    public function remapUserRolesToChannelRoles(): int
    {
        if (!$this->tableHasColumn('sa_system_role', 'dept_id')) {
            return 0;
        }
        $codes = $this->getDefaultChannelRoleCodes();
        if (empty($codes)) {
            return 0;
        }
        $codeList = "'" . implode("','", array_map('addslashes', $codes)) . "'";
        return Db::execute(
            'UPDATE `sa_system_user_role` ur
             INNER JOIN `sa_system_user` u ON ur.user_id = u.id
             INNER JOIN `sa_system_role` r_old ON ur.role_id = r_old.id
             INNER JOIN `sa_system_role` r_new ON r_new.dept_id = u.dept_id AND r_new.code = r_old.code
             SET ur.role_id = r_new.id
             WHERE u.dept_id > 0
               AND r_old.dept_id = 0
               AND r_old.code IN (' . $codeList . ')
               AND r_old.id <> ' . self::SUPER_ADMIN_ROLE_ID
        );
    }

    /**
     * @return string[]
     */
    public function getDefaultChannelRoleCodes(): array
    {
        $codes = config('plugin.saiadmin.saithink.channel_default_role_codes', []);
        if (!is_array($codes) || $codes === []) {
            return ['yijidaili', 'erjidaili', 'sanjidaili'];
        }
        $out = [];
        foreach ($codes as $code) {
            $code = trim((string) $code);
            if ($code !== '') {
                $out[] = $code;
            }
        }
        return $out;
    }

    /**
     * 删除渠道下不在默认三个 code 内、且未被用户绑定的角色
     */
    public function pruneExtraChannelRoles(int $deptId): int
    {
        if ($deptId <= 0) {
            return 0;
        }
        $allowed = $this->getDefaultChannelRoleCodes();
        if (empty($allowed)) {
            return 0;
        }

        $query = SystemRole::where('dept_id', $deptId)->whereNotIn('code', $allowed);
        $roleIds = $query->column('id');
        if (empty($roleIds)) {
            return 0;
        }

        $usedIds = Db::name('sa_system_user_role')->whereIn('role_id', $roleIds)->column('role_id');
        $usedMap = array_flip($usedIds ?: []);
        $pruned = 0;
        foreach ($roleIds as $roleId) {
            if (isset($usedMap[$roleId])) {
                continue;
            }
            Db::name('sa_system_role_menu')->where('role_id', $roleId)->delete();
            Db::name('sa_system_role_dept')->where('role_id', $roleId)->delete();
            SystemRole::destroy($roleId);
            $pruned++;
        }

        return $pruned;
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    private function defaultTemplateRoles(): array
    {
        $codes = $this->getDefaultChannelRoleCodes();
        if (empty($codes)) {
            return [];
        }

        $query = Db::table('sa_system_role')
            ->where('id', '<>', self::SUPER_ADMIN_ROLE_ID)
            ->whereIn('code', $codes);
        if ($this->tableHasColumn('sa_system_role', 'dept_id')) {
            $query->where('dept_id', 0);
        }
        $rows = $query->select()->toArray();
        if (empty($rows)) {
            return [];
        }

        if (count($rows) === count($codes)) {
            return $this->sortRolesByLevelDesc($rows);
        }

        // 按配置 code 补齐，缺失的 code 跳过，最终按角色级别从大到小排序
        $byCode = [];
        foreach ($rows as $row) {
            $byCode[(string) ($row['code'] ?? '')] = $row;
        }
        $ordered = [];
        foreach ($codes as $code) {
            if (isset($byCode[$code])) {
                $ordered[] = $byCode[$code];
            }
        }
        return $this->sortRolesByLevelDesc($ordered);
    }

    /**
     * 按角色级别从大到小排序（同级别按 sort 降序）
     *
     * @param array<int, array<string, mixed>> $rows
     * @return array<int, array<string, mixed>>
     */
    private function sortRolesByLevelDesc(array $rows): array
    {
        usort($rows, static function (array $a, array $b): int {
            $levelCompare = (int) ($b['level'] ?? 0) <=> (int) ($a['level'] ?? 0);
            if ($levelCompare !== 0) {
                return $levelCompare;
            }
            return (int) ($b['sort'] ?? 0) <=> (int) ($a['sort'] ?? 0);
        });
        return $rows;
    }

    private function insertRoleFromTemplate(array $template, int $deptId): int
    {
        unset(
            $template['id'],
            $template['create_time'],
            $template['update_time'],
            $template['delete_time']
        );
        $template['dept_id'] = $deptId;
        $now = date('Y-m-d H:i:s');
        if (!isset($template['create_time'])) {
            $template['create_time'] = $now;
        }
        if (!isset($template['update_time'])) {
            $template['update_time'] = $now;
        }
        return (int) Db::table('sa_system_role')->insertGetId($template);
    }

    private function copyRoleMenus(int $fromRoleId, int $toRoleId): void
    {
        $menuIds = Db::name('sa_system_role_menu')->where('role_id', $fromRoleId)->column('menu_id');
        if (empty($menuIds)) {
            return;
        }
        $rows = [];
        foreach ($menuIds as $menuId) {
            $rows[] = ['role_id' => $toRoleId, 'menu_id' => $menuId];
        }
        Db::name('sa_system_role_menu')->limit(100)->insertAll($rows);
    }

    private function roleExists(int $deptId, string $code): bool
    {
        return SystemRole::where('dept_id', $deptId)->where('code', $code)->count() > 0;
    }

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