package cn.nianxx.thhotel.platform.identity.service.impl;

import cn.nianxx.thhotel.platform.access.common.enums.PlatformPermissionCode;
import cn.nianxx.thhotel.platform.access.common.enums.PlatformPermissionStatus;
import cn.nianxx.thhotel.platform.access.common.enums.PlatformRoleCode;
import cn.nianxx.thhotel.platform.access.common.enums.PlatformRoleStatus;
import cn.nianxx.thhotel.platform.access.domain.PlatformPermissionEntity;
import cn.nianxx.thhotel.platform.access.domain.PlatformRoleEntity;
import cn.nianxx.thhotel.platform.access.repository.PlatformAccessRepository;
import cn.nianxx.thhotel.platform.hotel.common.enums.PlatformHotelStatus;
import cn.nianxx.thhotel.platform.hotel.domain.PlatformHotelEntity;
import cn.nianxx.thhotel.platform.hotel.repository.PlatformHotelRepository;
import cn.nianxx.thhotel.platform.identity.common.enums.PlatformUserStatus;
import cn.nianxx.thhotel.platform.identity.domain.PlatformUserEntity;
import cn.nianxx.thhotel.platform.identity.repository.PlatformIdentityRepository;
import cn.nianxx.thhotel.platform.navigation.common.enums.PlatformMenuCode;
import cn.nianxx.thhotel.platform.navigation.common.enums.PlatformMenuStatus;
import cn.nianxx.thhotel.platform.navigation.common.enums.PlatformMenuType;
import cn.nianxx.thhotel.platform.navigation.domain.PlatformMenuEntity;
import cn.nianxx.thhotel.platform.navigation.repository.PlatformNavigationRepository;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

/**
 * M003 登录权限底座启动初始化。只写入系统内置数据和首个超级管理员。
 */
@Component
public class PlatformIdentityBootstrapRunner implements ApplicationRunner {

    private final AuthProperties authProperties;
    private final AuthPasswordService passwordService;
    private final PlatformIdentityRepository identityRepository;
    private final PlatformAccessRepository accessRepository;
    private final PlatformHotelRepository hotelRepository;
    private final PlatformNavigationRepository navigationRepository;

    /**
     * 注入启动初始化所需配置、密码工具和平台仓储。
     */
    public PlatformIdentityBootstrapRunner(
            AuthProperties authProperties,
            AuthPasswordService passwordService,
            PlatformIdentityRepository identityRepository,
            PlatformAccessRepository accessRepository,
            PlatformHotelRepository hotelRepository,
            PlatformNavigationRepository navigationRepository) {
        this.authProperties = authProperties;
        this.passwordService = passwordService;
        this.identityRepository = identityRepository;
        this.accessRepository = accessRepository;
        this.hotelRepository = hotelRepository;
        this.navigationRepository = navigationRepository;
    }

    /**
     * 应用启动时初始化内置权限、角色、菜单、默认酒店和首个超级管理员。
     */
    @Override
    @Transactional
    public void run(ApplicationArguments args) {
        seedPermissions();
        seedRoles();
        seedRolePermissions();
        seedMenus();
        seedDefaultHotel();
        seedBootstrapAdmin();
    }

    /**
     * 初始化内置权限码；权限码以枚举为权威来源，启动时同步展示名和分组。
     */
    private void seedPermissions() {
        for (PlatformPermissionCode code : PlatformPermissionCode.values()) {
            PlatformPermissionEntity permission = accessRepository.findPermissionByCode(code.name())
                    .orElseGet(PlatformPermissionEntity::new);
            boolean creating = permission.getId() == null;
            permission.setPermissionCode(code.name());
            permission.setPermissionName(permissionName(code));
            permission.setPermissionGroup(permissionGroup(code));
            permission.setPermissionStatus(PlatformPermissionStatus.ACTIVE.name());
            permission.setSystemBuiltin(true);
            setAuditTime(permission, creating);
            if (creating) {
                accessRepository.insertPermission(permission);
            } else {
                accessRepository.updatePermission(permission);
            }
        }
    }

    /**
     * 初始化内置角色；第一版角色定义由代码维护，后续管理后台再扩展维护边界。
     */
    private void seedRoles() {
        for (PlatformRoleCode code : PlatformRoleCode.values()) {
            PlatformRoleEntity role = accessRepository.findRoleByCode(code.name())
                    .orElseGet(PlatformRoleEntity::new);
            boolean creating = role.getId() == null;
            role.setRoleCode(code.name());
            role.setRoleName(roleName(code));
            role.setRoleStatus(PlatformRoleStatus.ACTIVE.name());
            role.setSystemBuiltin(true);
            setAuditTime(role, creating);
            if (creating) {
                accessRepository.insertRole(role);
            } else {
                accessRepository.updateRole(role);
            }
        }
    }

    /**
     * 同步内置角色权限矩阵；矩阵中移除的权限关系会在启动时清理。
     */
    private void seedRolePermissions() {
        Map<PlatformRoleCode, List<PlatformPermissionCode>> matrix = rolePermissionMatrix();
        for (Map.Entry<PlatformRoleCode, List<PlatformPermissionCode>> entry : matrix.entrySet()) {
            PlatformRoleEntity role = accessRepository.findRoleByCode(entry.getKey().name())
                    .orElseThrow(() -> new IllegalStateException("role not seeded: " + entry.getKey()));
            List<Long> permissionIds = entry.getValue().stream()
                    .map(permissionCode -> accessRepository.findPermissionByCode(permissionCode.name())
                            .orElseThrow(() -> new IllegalStateException("permission not seeded: " + permissionCode))
                            .getId())
                    .toList();
            accessRepository.syncRolePermissions(role.getId(), permissionIds);
        }
    }

    /**
     * 初始化前端可见菜单和隐藏占位菜单，保持后端菜单契约与当前前端路由一致。
     */
    private void seedMenus() {
        upsertMenu(
                PlatformMenuCode.RESERVATION_ORDERS,
                "订单列表",
                "/reservation/orders",
                "ReservationOrders",
                "pi pi-list",
                PlatformPermissionCode.RESERVATION_ORDER_READ.name(),
                10,
                true);
        upsertMenu(
                PlatformMenuCode.RESERVATION_TASKS,
                "任务队列",
                "/reservation/tasks",
                "ReservationTasks",
                "pi pi-check-square",
                PlatformPermissionCode.RESERVATION_TASK_READ.name(),
                20,
                true);
        upsertMenu(
                PlatformMenuCode.DEBUG_EML_SUPERAGENT,
                "Debug EML",
                "/debug/eml-superagent",
                "DebugEmlSuperAgent",
                "pi pi-upload",
                PlatformPermissionCode.SYSTEM_DEBUG_EML_RUN.name(),
                30,
                true);
        upsertMenu(
                PlatformMenuCode.SOURCE_MESSAGES,
                "邮件消息",
                "/source-messages",
                "SourceMessages",
                "pi pi-envelope",
                PlatformPermissionCode.SOURCE_MESSAGE_READ.name(),
                90,
                false);
        upsertMenu(
                PlatformMenuCode.SYSTEM_SETTINGS,
                "系统设置",
                "/system",
                "SystemSettings",
                "pi pi-cog",
                PlatformPermissionCode.SYSTEM_ADMIN_CONSOLE_ACCESS.name(),
                100,
                true);
    }

    /**
     * 初始化默认酒店基础资料，供首个管理员和本地测试环境使用。
     */
    private void seedDefaultHotel() {
        String defaultHotelId = authProperties.getBootstrap().getDefaultHotelId();
        if (isBlank(defaultHotelId)) {
            return;
        }
        PlatformHotelEntity hotel = hotelRepository.findHotelByHotelId(defaultHotelId)
                .orElseGet(PlatformHotelEntity::new);
        boolean creating = hotel.getId() == null;
        hotel.setHotelId(defaultHotelId);
        hotel.setHotelName(defaultString(authProperties.getBootstrap().getDefaultHotelName(), defaultHotelId));
        hotel.setHotelStatus(PlatformHotelStatus.ACTIVE.name());
        hotel.setTimeZone(defaultString(authProperties.getBootstrap().getDefaultHotelTimeZone(), "Asia/Bangkok"));
        hotel.setSortOrder(10);
        setAuditTime(hotel, creating);
        if (creating) {
            hotelRepository.insertHotel(hotel);
        } else {
            hotelRepository.updateHotel(hotel);
        }
    }

    /**
     * 初始化首个可登录超级管理员；只有不存在启用超级管理员时才使用环境变量。
     */
    private void seedBootstrapAdmin() {
        AuthProperties.Admin admin = authProperties.getBootstrap().getAdmin();
        if (isBlank(admin.getUsername()) || isBlank(admin.getPassword()) || identityRepository.countSuperAdminUsers() > 0) {
            return;
        }
        LocalDateTime now = nowUtc();
        PlatformUserEntity user = identityRepository.findUserByUsername(admin.getUsername().trim())
                .orElseGet(PlatformUserEntity::new);
        boolean creating = user.getId() == null;
        user.setUsername(admin.getUsername().trim());
        user.setPasswordHash(passwordService.hash(admin.getPassword()));
        user.setDisplayName(defaultString(admin.getDisplayName(), admin.getUsername().trim()));
        user.setUserStatus(PlatformUserStatus.ACTIVE.name());
        user.setSuperAdmin(true);
        user.setPasswordChangedAt(now);
        setAuditTime(user, creating);
        if (creating) {
            identityRepository.insertUser(user);
        } else {
            identityRepository.updateUser(user);
        }

        PlatformRoleEntity adminRole = accessRepository.findRoleByCode(PlatformRoleCode.SYSTEM_ADMIN.name())
                .orElseThrow(() -> new IllegalStateException("SYSTEM_ADMIN role not seeded"));
        accessRepository.ensureUserRole(user.getId(), adminRole.getId());

        String defaultHotelId = authProperties.getBootstrap().getDefaultHotelId();
        if (!isBlank(defaultHotelId)) {
            hotelRepository.ensureUserHotel(user.getId(), defaultHotelId, true);
        }
    }

    /**
     * 新增或更新单个菜单定义，菜单代码为稳定唯一标识。
     */
    private void upsertMenu(
            PlatformMenuCode code,
            String name,
            String routePath,
            String componentKey,
            String iconKey,
            String permissionCode,
            int sortOrder,
            boolean visible) {
        PlatformMenuEntity menu = navigationRepository.findMenuByCode(code.name())
                .orElseGet(PlatformMenuEntity::new);
        boolean creating = menu.getId() == null;
        menu.setMenuCode(code.name());
        menu.setMenuName(name);
        menu.setMenuType(PlatformMenuType.PAGE.name());
        menu.setRoutePath(routePath);
        menu.setComponentKey(componentKey);
        menu.setIconKey(iconKey);
        menu.setPermissionCode(permissionCode);
        menu.setSortOrder(sortOrder);
        menu.setVisible(visible);
        menu.setMenuStatus(PlatformMenuStatus.ACTIVE.name());
        setAuditTime(menu, creating);
        if (creating) {
            navigationRepository.insertMenu(menu);
        } else {
            navigationRepository.updateMenu(menu);
        }
    }

    /**
     * 返回第一版内置角色权限矩阵，启动同步时以该矩阵为准。
     */
    private Map<PlatformRoleCode, List<PlatformPermissionCode>> rolePermissionMatrix() {
        Map<PlatformRoleCode, List<PlatformPermissionCode>> matrix = new EnumMap<>(PlatformRoleCode.class);
        matrix.put(PlatformRoleCode.SYSTEM_ADMIN, List.of(PlatformPermissionCode.values()));
        matrix.put(PlatformRoleCode.RESERVATION_OPERATOR, List.of(
                PlatformPermissionCode.SYSTEM_AUTH_READ,
                PlatformPermissionCode.HOTEL_SWITCH,
                PlatformPermissionCode.RESERVATION_ORDER_READ,
                PlatformPermissionCode.RESERVATION_TASK_READ,
                PlatformPermissionCode.RESERVATION_TASK_EDIT,
                PlatformPermissionCode.RESERVATION_TASK_CONFIRM,
                PlatformPermissionCode.RESERVATION_OPERA_SIM_EXECUTE,
                PlatformPermissionCode.RESERVATION_AUDIT_READ,
                PlatformPermissionCode.SOURCE_MESSAGE_READ,
                PlatformPermissionCode.SOURCE_MESSAGE_ORIGINAL_READ));
        matrix.put(PlatformRoleCode.RESERVATION_VIEWER, List.of(
                PlatformPermissionCode.SYSTEM_AUTH_READ,
                PlatformPermissionCode.HOTEL_SWITCH,
                PlatformPermissionCode.RESERVATION_ORDER_READ,
                PlatformPermissionCode.RESERVATION_TASK_READ,
                PlatformPermissionCode.RESERVATION_AUDIT_READ,
                PlatformPermissionCode.SOURCE_MESSAGE_READ));
        return matrix;
    }

    /**
     * 解析权限码中文展示名；业务判断仍只使用稳定权限码。
     */
    private String permissionName(PlatformPermissionCode code) {
        return switch (code) {
            case SOURCE_MESSAGE_READ -> "读取邮件消息";
            case SOURCE_MESSAGE_ORIGINAL_READ -> "读取邮件原文";
            case RESERVATION_ORDER_READ -> "读取订单";
            case RESERVATION_TASK_READ -> "读取任务";
            case RESERVATION_TASK_EDIT -> "编辑任务草稿";
            case RESERVATION_TASK_CONFIRM -> "确认任务";
            case RESERVATION_OPERA_SIM_EXECUTE -> "执行 OPERA 模拟";
            case RESERVATION_AUDIT_READ -> "读取任务审计";
            case HOTEL_SWITCH -> "切换酒店";
            case SYSTEM_AUTH_READ -> "读取当前登录上下文";
            case SYSTEM_USER_MANAGE -> "管理用户";
            case SYSTEM_ROLE_MANAGE -> "管理角色";
            case SYSTEM_MENU_MANAGE -> "管理菜单";
            case HOTEL_MANAGE -> "管理酒店";
            case SYSTEM_ADMIN_CONSOLE_ACCESS -> "进入系统管理后台";
            case SYSTEM_DEBUG_EML_RUN -> "运行 Debug EML";
        };
    }

    /**
     * 解析权限所属分组，便于后续管理后台按业务域展示。
     */
    private String permissionGroup(PlatformPermissionCode code) {
        return switch (code) {
            case SOURCE_MESSAGE_READ, SOURCE_MESSAGE_ORIGINAL_READ -> "SOURCE_MESSAGE";
            case RESERVATION_ORDER_READ, RESERVATION_TASK_READ, RESERVATION_TASK_EDIT,
                    RESERVATION_TASK_CONFIRM, RESERVATION_OPERA_SIM_EXECUTE,
                    RESERVATION_AUDIT_READ -> "RESERVATION";
            case HOTEL_SWITCH, HOTEL_MANAGE -> "HOTEL";
            default -> "SYSTEM";
        };
    }

    /**
     * 解析内置角色中文展示名。
     */
    private String roleName(PlatformRoleCode code) {
        return switch (code) {
            case SYSTEM_ADMIN -> "系统管理员";
            case RESERVATION_OPERATOR -> "预订操作员";
            case RESERVATION_VIEWER -> "预订只读用户";
        };
    }

    /**
     * 设置权限记录创建和更新 UTC 时间。
     */
    private void setAuditTime(PlatformPermissionEntity entity, boolean creating) {
        LocalDateTime now = nowUtc();
        if (creating) {
            entity.setCreatedAt(now);
        }
        entity.setUpdatedAt(now);
    }

    /**
     * 设置角色记录创建和更新 UTC 时间。
     */
    private void setAuditTime(PlatformRoleEntity entity, boolean creating) {
        LocalDateTime now = nowUtc();
        if (creating) {
            entity.setCreatedAt(now);
        }
        entity.setUpdatedAt(now);
    }

    /**
     * 设置酒店记录创建和更新 UTC 时间。
     */
    private void setAuditTime(PlatformHotelEntity entity, boolean creating) {
        LocalDateTime now = nowUtc();
        if (creating) {
            entity.setCreatedAt(now);
        }
        entity.setUpdatedAt(now);
    }

    /**
     * 设置菜单记录创建和更新 UTC 时间。
     */
    private void setAuditTime(PlatformMenuEntity entity, boolean creating) {
        LocalDateTime now = nowUtc();
        if (creating) {
            entity.setCreatedAt(now);
        }
        entity.setUpdatedAt(now);
    }

    /**
     * 设置用户记录创建和更新 UTC 时间。
     */
    private void setAuditTime(PlatformUserEntity entity, boolean creating) {
        LocalDateTime now = nowUtc();
        if (creating) {
            entity.setCreatedAt(now);
        }
        entity.setUpdatedAt(now);
    }

    /**
     * 获取 UTC 当前时间，保证启动数据时间语义统一。
     */
    private LocalDateTime nowUtc() {
        return LocalDateTime.now(ZoneOffset.UTC);
    }

    /**
     * 判断配置字符串是否为空白。
     */
    private boolean isBlank(String value) {
        return value == null || value.isBlank();
    }

    /**
     * 返回非空配置值，否则使用兜底值。
     */
    private String defaultString(String value, String fallback) {
        return isBlank(value) ? fallback : value;
    }
}
