package com.yaoyuan.jiscuss.controller;

import com.yaoyuan.jiscuss.entity.Discussion;
import com.yaoyuan.jiscuss.entity.Post;
import com.yaoyuan.jiscuss.entity.Role;
import com.yaoyuan.jiscuss.entity.UpgradeLog;
import com.yaoyuan.jiscuss.entity.User;
import com.yaoyuan.jiscuss.entity.UserInfo;
import com.yaoyuan.jiscuss.entity.UserRole;
import com.yaoyuan.jiscuss.entity.AuditLog;
import com.yaoyuan.jiscuss.repository.DiscussionsRepository;
import com.yaoyuan.jiscuss.repository.DiscussionsTagsRepository;
import com.yaoyuan.jiscuss.repository.PostsRepository;
import com.yaoyuan.jiscuss.repository.RoleRepository;
import com.yaoyuan.jiscuss.repository.UpgradeLogRepository;
import com.yaoyuan.jiscuss.repository.UserRoleRepository;
import com.yaoyuan.jiscuss.repository.UsersRepository;
import com.yaoyuan.jiscuss.repository.AuditLogRepository;
import com.yaoyuan.jiscuss.service.AuditLogService;
import org.springframework.boot.SpringBootVersion;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

import jakarta.servlet.http.HttpServletRequest;

import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author Chuyaoyuan
 * 后台系统控制器
 */
@Controller
public class AdminSystemController extends BaseController {

    private final UsersRepository usersRepository;
    private final DiscussionsRepository discussionsRepository;
    private final PostsRepository postsRepository;
    private final DiscussionsTagsRepository discussionsTagsRepository;
    private final RoleRepository roleRepository;
    private final UserRoleRepository userRoleRepository;
    private final UpgradeLogRepository upgradeLogRepository;
    private final AuditLogRepository auditLogRepository;
    private final AuditLogService auditLogService;

    public AdminSystemController(
            UsersRepository usersRepository,
            DiscussionsRepository discussionsRepository,
            PostsRepository postsRepository,
            DiscussionsTagsRepository discussionsTagsRepository,
            RoleRepository roleRepository,
            UserRoleRepository userRoleRepository,
            UpgradeLogRepository upgradeLogRepository,
            AuditLogRepository auditLogRepository,
            AuditLogService auditLogService) {
        this.usersRepository = usersRepository;
        this.discussionsRepository = discussionsRepository;
        this.postsRepository = postsRepository;
        this.discussionsTagsRepository = discussionsTagsRepository;
        this.roleRepository = roleRepository;
        this.userRoleRepository = userRoleRepository;
        this.upgradeLogRepository = upgradeLogRepository;
        this.auditLogRepository = auditLogRepository;
        this.auditLogService = auditLogService;
    }

    @GetMapping("/admin/home")
    public String home(HttpServletRequest request, ModelMap map) {
        fillCommon(request, map, "home");

        map.put("userCount", usersRepository.count());
        map.put("discussionCount", discussionsRepository.count());
        map.put("postCount", postsRepository.count());
        map.put("roleCount", roleRepository.count());

        map.put("sys", buildSystemMetrics());
        map.put("deps", buildDependencyVersions());

        return "admin/home";
    }

    @GetMapping("/admin/users")
    public String users(HttpServletRequest request, ModelMap map) {
        fillCommon(request, map, "users");

        List<User> users = usersRepository.findAll(Sort.by(Sort.Direction.ASC, "id"));
        List<Role> roles = roleRepository.findAllByOrderByIdAsc();
        Map<String, Set<Integer>> userRoleIds = new HashMap<>();
        for (UserRole ur : userRoleRepository.findAll()) {
            userRoleIds.computeIfAbsent(String.valueOf(ur.getUserId()), key -> new HashSet<>()).add(ur.getRoleId());
        }

        map.put("users", users);
        map.put("roles", roles);
        map.put("userRoleIds", userRoleIds);
        return "admin/users";
    }

    @PostMapping("/admin/users/{id}/roles")
    @Transactional
    public String updateUserRoles(HttpServletRequest request,
                                  @PathVariable("id") Integer id,
                                  @RequestParam(value = "roleIds", required = false) List<Integer> roleIds) {
        UserInfo currentAdmin = getUserInfo(request);
        userRoleRepository.deleteByUserId(id);
        if (roleIds != null) {
            Date now = new Date();
            Set<Integer> uniqueRoleIds = new HashSet<>(roleIds);
            Set<Integer> validEnabledRoleIds = roleRepository.findAllById(uniqueRoleIds).stream()
                    .filter(role -> role.getEnabled() != null && role.getEnabled() == 1)
                    .map(Role::getId)
                    .collect(Collectors.toSet());

            for (Integer roleId : validEnabledRoleIds) {
                UserRole ur = new UserRole();
                ur.setUserId(id);
                ur.setRoleId(roleId);
                ur.setCreateTime(now);
                userRoleRepository.save(ur);
            }
        }
        auditLogService.log(currentAdmin, "update_user_roles", "user", id, 
                "Updated user role assignments. Role count: " + (roleIds == null ? 0 : roleIds.size()));
        return "redirect:/admin/users";
    }

    @PostMapping("/admin/roles")
    public String createRole(HttpServletRequest request,
                             @RequestParam("code") String code,
                             @RequestParam("name") String name,
                             @RequestParam(value = "description", required = false) String description) {
        UserInfo currentAdmin = getUserInfo(request);
        String normalizedCode = code == null ? "" : code.trim().toUpperCase();
        if (normalizedCode.isEmpty()) {
            return "redirect:/admin/users";
        }

        Role exists = roleRepository.findByCodeIgnoreCase(normalizedCode);
        if (exists == null) {
            Date now = new Date();
            Role role = new Role();
            role.setCode(normalizedCode);
            role.setName(name == null ? "" : name.trim());
            role.setDescription(description);
            role.setEnabled(1);
            role.setCreateTime(now);
            role.setUpdateTime(now);
            roleRepository.save(role);
            auditLogService.log(currentAdmin, "create_role", "role", role.getId(), 
                    "Created role: " + normalizedCode);
        } else {
            auditLogService.log(currentAdmin, "create_role_duplicate", "role", exists.getId(), 
                    "Attempted to create duplicate role: " + normalizedCode, "ignored");
        }
        return "redirect:/admin/users";
    }

    @PostMapping("/admin/roles/{id}/toggle")
    public String toggleRole(HttpServletRequest request,
                             @PathVariable("id") Integer id) {
        UserInfo currentAdmin = getUserInfo(request);
        Role role = roleRepository.findById(id).orElse(null);
        if (role != null) {
            int nextStatus = role.getEnabled() != null && role.getEnabled() == 1 ? 0 : 1;
            // Keep built-in ADMIN role enabled to avoid locking out management access.
            if ("ADMIN".equalsIgnoreCase(role.getCode()) && nextStatus == 0) {
                auditLogService.log(currentAdmin, "toggle_role_blocked", "role", id, 
                        "Attempted to disable built-in ADMIN role", "blocked");
                return "redirect:/admin/users";
            }
            role.setEnabled(nextStatus);
            role.setUpdateTime(new Date());
            roleRepository.save(role);
            auditLogService.log(currentAdmin, "toggle_role", "role", id, 
                    "Toggled role status to " + (nextStatus == 1 ? "enabled" : "disabled"));
        }
        return "redirect:/admin/users";
    }

    @GetMapping("/admin/discussions")
    public String discussions(HttpServletRequest request, ModelMap map) {
        fillCommon(request, map, "discussions");
        List<Discussion> discussions = discussionsRepository.findAll(Sort.by(Sort.Direction.DESC, "createTime"));
        map.put("discussions", discussions);
        return "admin/discussions";
    }

    @PostMapping("/admin/discussions/{id}/delete")
    @Transactional
    public String deleteDiscussion(HttpServletRequest request,
                                   @PathVariable("id") Integer id) {
        UserInfo currentAdmin = getUserInfo(request);
        Discussion discussion = discussionsRepository.findById(id).orElse(null);
        String title = discussion == null ? "unknown" : discussion.getTitle();
        discussionsTagsRepository.deleteByDiscussionId(id);
        postsRepository.deleteByDiscussionId(id);
        discussionsRepository.deleteById(id);
        auditLogService.log(currentAdmin, "delete_discussion", "discussion", id, 
                "Deleted discussion: " + title);
        return "redirect:/admin/discussions";
    }

    @GetMapping("/admin/posts")
    public String posts(HttpServletRequest request, ModelMap map) {
        fillCommon(request, map, "posts");
        List<Post> posts = postsRepository.findAll(Sort.by(Sort.Direction.DESC, "createTime"));
        map.put("posts", posts);

        Set<Integer> userIds = posts.stream().map(Post::getCreateId).filter(v -> v != null).collect(Collectors.toSet());
        Set<Integer> discussionIds = posts.stream().map(Post::getDiscussionId).filter(v -> v != null).collect(Collectors.toSet());

        Map<String, String> userNames = usersRepository.findAllById(userIds).stream()
            .collect(Collectors.toMap(user -> String.valueOf(user.getId()), User::getUsername));
        Map<String, String> discussionNames = discussionsRepository.findAllById(discussionIds).stream()
            .collect(Collectors.toMap(discussion -> String.valueOf(discussion.getId()), Discussion::getTitle));

        map.put("userNames", userNames);
        map.put("discussionNames", discussionNames);
        return "admin/posts";
    }

    @PostMapping("/admin/posts/{id}/delete")
    public String deletePost(HttpServletRequest request,
                             @PathVariable("id") Integer id) {
        UserInfo currentAdmin = getUserInfo(request);
        Post post = postsRepository.findById(id).orElse(null);
        String content = post == null ? "unknown" : (post.getContent() == null ? "" : post.getContent());
        if (content.length() > 50) {
            content = content.substring(0, 50) + "...";
        }
        postsRepository.deleteById(id);
        auditLogService.log(currentAdmin, "delete_post", "post", id, 
                "Deleted post: " + content);
        return "redirect:/admin/posts";
    }

    @GetMapping("/admin/upgrade-logs")
    public String upgradeLogs(HttpServletRequest request, ModelMap map) {
        fillCommon(request, map, "upgradeLogs");
        map.put("logs", upgradeLogRepository.findAllByOrderByCreateTimeDesc());
        return "admin/upgrade-logs";
    }

    @PostMapping("/admin/upgrade-logs")
    public String createUpgradeLog(HttpServletRequest request,
                                   @RequestParam("version") String version,
                                   @RequestParam("title") String title,
                                   @RequestParam(value = "type", defaultValue = "feature") String type,
                                   @RequestParam("content") String content) {
        UserInfo user = getUserInfo(request);
        UpgradeLog log = new UpgradeLog();
        log.setVersion(version);
        log.setTitle(title);
        log.setType(type);
        log.setContent(content);
        log.setCreatedBy(user == null ? null : user.getId());
        log.setCreateTime(new Date());
        upgradeLogRepository.save(log);
        auditLogService.log(user, "create_upgrade_log", "upgrade_log", log.getId(), 
                "Created upgrade log: " + version + " - " + title);
        return "redirect:/admin/upgrade-logs";
    }

    @GetMapping("/admin/plugins")
    public String plugins(HttpServletRequest request, ModelMap map) {
        fillCommon(request, map, "plugins");
        return "admin/plugins";
    }

    @GetMapping("/admin/audit-logs")
    public String auditLogs(HttpServletRequest request, ModelMap map) {
        fillCommon(request, map, "auditLogs");
        List<AuditLog> logs = auditLogRepository.findAllByOrderByCreateTimeDesc();
        map.put("logs", logs);
        return "admin/audit-logs";
    }

    @GetMapping("/admin/themes")
    public String themes(HttpServletRequest request, ModelMap map) {
        fillCommon(request, map, "themes");
        return "admin/themes";
    }

    private void fillCommon(HttpServletRequest request, ModelMap map, String active) {
        UserInfo user = getUserInfo(request);
        map.put("adminName", user == null ? "admin" : user.getUsername());
        map.put("active", active);
    }

    private Map<String, Object> buildSystemMetrics() {
        Map<String, Object> result = new HashMap<>();
        Runtime rt = Runtime.getRuntime();
        MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();

        long totalMb = bytesToMb(rt.totalMemory());
        long freeMb = bytesToMb(rt.freeMemory());
        long usedMb = totalMb - freeMb;
        long maxMb = bytesToMb(rt.maxMemory());
        long heapUsedMb = bytesToMb(memoryMXBean.getHeapMemoryUsage().getUsed());

        result.put("totalMb", totalMb);
        result.put("freeMb", freeMb);
        result.put("usedMb", usedMb);
        result.put("maxMb", maxMb);
        result.put("heapUsedMb", heapUsedMb);
        result.put("uptimeMs", ManagementFactory.getRuntimeMXBean().getUptime());
        result.put("cpuLoad", readCpuLoadPercent());
        return result;
    }

    private Map<String, String> buildDependencyVersions() {
        Map<String, String> result = new HashMap<>();
        result.put("springBoot", SpringBootVersion.getVersion());
        result.put("java", System.getProperty("java.version"));
        result.put("spring", implVersion("org.springframework.core.SpringVersion"));
        result.put("hibernate", implVersion("org.hibernate.Version"));
        result.put("flyway", implVersion("org.flywaydb.core.Flyway"));
        result.put("druid", implVersion("com.alibaba.druid.pool.DruidDataSource"));
        result.put("ehcache", implVersion("org.ehcache.CacheManager"));
        result.put("springdoc", implVersion("org.springdoc.core.configuration.SpringDocConfiguration"));
        return result;
    }

    private String implVersion(String className) {
        try {
            Class<?> clazz = Class.forName(className);
            Package pkg = clazz.getPackage();
            String version = pkg == null ? null : pkg.getImplementationVersion();
            return version == null ? "unknown" : version;
        } catch (ClassNotFoundException e) {
            return "unknown";
        }
    }

    private String readCpuLoadPercent() {
        try {
            java.lang.management.OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean();
            if (osBean instanceof com.sun.management.OperatingSystemMXBean sunOsBean) {
                double value = sunOsBean.getCpuLoad();
                if (value >= 0) {
                    return String.format("%.2f%%", value * 100);
                }
            }
        } catch (Exception ignored) {
        }
        return "N/A";
    }

    private long bytesToMb(long bytes) {
        return bytes / 1024 / 1024;
    }
}
