package com.gxwebsoft.app.service.impl;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gxwebsoft.app.entity.AppTicket;
import com.gxwebsoft.app.entity.AppTicketReply;
import com.gxwebsoft.app.entity.AppUser;
import com.gxwebsoft.app.mapper.AppTicketMapper;
import com.gxwebsoft.app.mapper.AppTicketReplyMapper;
import com.gxwebsoft.app.param.AppTicketParam;
import com.gxwebsoft.app.service.AppTicketService;
import com.gxwebsoft.app.service.AppUserService;
import com.gxwebsoft.common.core.web.PageResult;
import com.gxwebsoft.common.system.entity.User;
import com.gxwebsoft.common.system.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 应用工单 Service 实现
 *
 * @author 科技小王子
 * @since 2026-03-30
 */
@Slf4j
@Service
public class AppTicketServiceImpl extends ServiceImpl<AppTicketMapper, AppTicket> implements AppTicketService {

    /** 企业微信群机器人 Webhook，留空则不发送 */
    @Value("${notify.wecom-webhook:}")
    private String wecomWebhook;

    /** 飞书群机器人 Webhook，留空则不发送 */
    @Value("${notify.feishu-webhook:}")
    private String feishuWebhook;

    @Resource
    private AppTicketReplyMapper replyMapper;

    @Resource
    private AppUserService appUserService;

    @Resource
    private UserService userService;

    // ─── 生成工单编号 ─────────────────────────────────────────────
    private String generateTicketNo() {
        String ts = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
        String rand = String.format("%04d", new Random().nextInt(10000));
        return "TK-" + ts + rand;
    }

    // ─── 客户端：查自己的工单 ─────────────────────────────────────
    @Override
    public PageResult<AppTicket> myPage(AppTicketParam param, Integer userId) {
        // 用 PageParam 包装分页参数（getCurrent/getSize 来自父类 Page<T>）
        Page<AppTicket> page = new Page<>(param.getCurrent(), param.getSize() > 0 ? param.getSize() : 15);

        LambdaQueryWrapper<AppTicket> wrapper = new LambdaQueryWrapper<AppTicket>()
                .eq(AppTicket::getDeleted, 0)
                .eq(AppTicket::getSubmitUserId, userId)
                .eq(ObjectUtil.isNotNull(param.getWebsiteId()), AppTicket::getWebsiteId, param.getWebsiteId())
                .eq(ObjectUtil.isNotEmpty(param.getStatus()), AppTicket::getStatus, param.getStatus())
                .eq(ObjectUtil.isNotEmpty(param.getCategory()), AppTicket::getCategory, param.getCategory())
                .and(ObjectUtil.isNotEmpty(param.getKeywords()), q ->
                        q.like(AppTicket::getTitle, param.getKeywords())
                         .or().like(AppTicket::getTicketNo, param.getKeywords()))
                // 状态优先级：pending > assigned > processing > resolved > closed；同状态内最新的排前
                .last("ORDER BY FIELD(status,'pending','assigned','processing','resolved','closed','rejected'), create_time DESC");

        baseMapper.selectPage(page, wrapper);
        return new PageResult<>(page.getRecords(), page.getTotal());
    }

    // ─── 技术端：查所有工单 ───────────────────────────────────────
    @Override
    public PageResult<AppTicket> allPage(AppTicketParam param) {
        Page<AppTicket> page = new Page<>(param.getCurrent(), param.getSize() > 0 ? param.getSize() : 20);

        LambdaQueryWrapper<AppTicket> wrapper = new LambdaQueryWrapper<AppTicket>()
                .eq(AppTicket::getDeleted, 0)
                .eq(ObjectUtil.isNotNull(param.getWebsiteId()), AppTicket::getWebsiteId, param.getWebsiteId())
                .eq(ObjectUtil.isNotEmpty(param.getStatus()), AppTicket::getStatus, param.getStatus())
                .eq(ObjectUtil.isNotEmpty(param.getCategory()), AppTicket::getCategory, param.getCategory())
                .eq(ObjectUtil.isNotEmpty(param.getPriority()), AppTicket::getPriority, param.getPriority())
                .and(ObjectUtil.isNotNull(param.getAssigneeId()) && param.getAssigneeId() == 0,
                        q -> q.isNull(AppTicket::getAssigneeId))
                .eq(ObjectUtil.isNotNull(param.getAssigneeId()) && param.getAssigneeId() > 0,
                        AppTicket::getAssigneeId, param.getAssigneeId())
                .and(ObjectUtil.isNotEmpty(param.getKeywords()), q ->
                        q.like(AppTicket::getTitle, param.getKeywords())
                         .or().like(AppTicket::getTicketNo, param.getKeywords()))
                // 状态优先级 > 紧急程度 > 最新提交时间
                .last("ORDER BY FIELD(status,'pending','assigned','processing','resolved','closed','rejected'), FIELD(priority,'urgent','high','normal','low'), create_time DESC");

        baseMapper.selectPage(page, wrapper);
        return new PageResult<>(page.getRecords(), page.getTotal());
    }

    // ─── 提交工单（自动分配） ─────────────────────────────────────
    @Override
    @Transactional(rollbackFor = Exception.class)
    public AppTicket submit(AppTicket ticket, Integer userId) {
        // 补充提交人信息
        ticket.setSubmitUserId(userId);
        ticket.setTicketNo(generateTicketNo());
        ticket.setStatus("pending");
        ticket.setReplyCount(0);
        ticket.setDeleted(0);
        ticket.setCreateTime(LocalDateTime.now());
        ticket.setUpdateTime(LocalDateTime.now());

        // 从用户服务取昵称/头像冗余存储（@InterceptorIgnore 绕过租户拦截器跨库查询）
        try {
            User user = userService.getByIdIgnoreTenant(userId);
            if (user != null) {
                ticket.setSubmitUserName(user.getNickname() != null ? user.getNickname() : user.getUsername());
                ticket.setSubmitUserAvatar(user.getAvatar());
            }
        } catch (Exception e) {
            log.warn("获取提交人信息失败", e);
        }

        // 自动分配：查找该应用的 admin/developer 成员，随机分配
        autoAssign(ticket);

        save(ticket);

        // 异步推送：新工单通知（通知分配到的技术人员）
        sendTicketCreatedAsync(ticket);

        return ticket;
    }

    /** 自动分配工单给应用的技术成员 */
    private void autoAssign(AppTicket ticket) {
        if (ticket.getWebsiteId() == null) return;
        try {
            List<AppUser> members = appUserService.list(
                    new LambdaQueryWrapper<AppUser>()
                            .eq(AppUser::getWebsiteId, ticket.getWebsiteId())
                            .eq(AppUser::getStatus, 0)
                            .in(AppUser::getRole, "admin", "developer", "owner"));
            if (!members.isEmpty()) {
                // 简单轮询：按工单数最少分配（此处随机）
                AppUser assigned = members.get(new Random().nextInt(members.size()));
                ticket.setAssigneeId(assigned.getUserId());
                ticket.setAssigneeName(assigned.getNickname() != null ? assigned.getNickname() : assigned.getUsername());
                ticket.setAssigneeAvatar(assigned.getAvatar());
                ticket.setStatus("assigned");
            }
        } catch (Exception e) {
            log.warn("自动分配工单失败，保持 pending 状态", e);
        }
    }

    // ─── 更新状态 ─────────────────────────────────────────────────
    @Override
    public void updateStatus(Long ticketId, String status, Integer operatorId) {
        LambdaUpdateWrapper<AppTicket> wrapper = new LambdaUpdateWrapper<AppTicket>()
                .eq(AppTicket::getTicketId, ticketId)
                .set(AppTicket::getStatus, status)
                .set(AppTicket::getUpdateTime, LocalDateTime.now());
        if ("resolved".equals(status)) {
            wrapper.set(AppTicket::getResolvedTime, LocalDateTime.now());
            // 如果没有分配人，自动将操作人设为处理人
            AppTicket t = getById(ticketId);
            if (t != null && t.getAssigneeId() == null) {
                wrapper.set(AppTicket::getAssigneeId, operatorId);
            }
        }
        update(wrapper);

        // 异步推送：状态变更通知提交人（已解决/已关闭）
        if ("resolved".equals(status) || "closed".equals(status)) {
            AppTicket t = getById(ticketId);
            if (t != null) sendStatusChangedAsync(t, status);
        }
    }

    // ─── 分配处理人 ───────────────────────────────────────────────
    @Override
    public void assign(Long ticketId, Integer assigneeId) {
        User user = userService.getByIdIgnoreTenant(assigneeId);
        LambdaUpdateWrapper<AppTicket> wrapper = new LambdaUpdateWrapper<AppTicket>()
                .eq(AppTicket::getTicketId, ticketId)
                .set(AppTicket::getAssigneeId, assigneeId)
                .set(user != null, AppTicket::getAssigneeName,
                        user != null ? (user.getNickname() != null ? user.getNickname() : user.getUsername()) : null)
                .set(user != null, AppTicket::getAssigneeAvatar, user != null ? user.getAvatar() : null)
                .set(AppTicket::getStatus, "assigned")
                .set(AppTicket::getUpdateTime, LocalDateTime.now());
        update(wrapper);

        // 异步推送：通知新分配到的技术人员
        AppTicket t = getById(ticketId);
        if (t != null) sendTicketAssignedAsync(t);
    }

    // ─── 用户关闭工单 ─────────────────────────────────────────────
    @Override
    public void closeByUser(Long ticketId, Integer userId) {
        AppTicket ticket = getById(ticketId);
        if (ticket == null || !ticket.getSubmitUserId().equals(userId)) {
            throw new RuntimeException("无权操作该工单");
        }
        update(new LambdaUpdateWrapper<AppTicket>()
                .eq(AppTicket::getTicketId, ticketId)
                .set(AppTicket::getStatus, "closed")
                .set(AppTicket::getClosedTime, LocalDateTime.now())
                .set(AppTicket::getUpdateTime, LocalDateTime.now()));
    }

    // ─── 获取回复列表 ─────────────────────────────────────────────
    @Override
    public List<AppTicketReply> getReplies(Long ticketId) {
        return replyMapper.selectList(
                new LambdaQueryWrapper<AppTicketReply>()
                        .eq(AppTicketReply::getTicketId, ticketId)
                        .eq(AppTicketReply::getDeleted, 0)
                        .orderByAsc(AppTicketReply::getCreateTime));
    }

    // ─── 添加回复 ─────────────────────────────────────────────────
    @Override
    @Transactional(rollbackFor = Exception.class)
    public AppTicketReply addReply(AppTicketReply reply, Integer userId) {
        reply.setUserId(userId);
        reply.setDeleted(0);
        reply.setCreateTime(LocalDateTime.now());

        // 补充用户信息（@InterceptorIgnore 绕过租户拦截器跨库查询）
        try {
            User user = userService.getByIdIgnoreTenant(userId);
            if (user != null) {
                reply.setUserName(user.getNickname() != null ? user.getNickname() : user.getUsername());
                reply.setUserAvatar(user.getAvatar());
            }
        } catch (Exception e) {
            log.warn("获取回复人信息失败", e);
        }

        // 判断是否是技术人员（该应用的 admin/developer/owner）
        AppTicket ticket = getById(reply.getTicketId());
        if (ticket != null) {
            boolean isStaff = appUserService.count(new LambdaQueryWrapper<AppUser>()
                    .eq(AppUser::getWebsiteId, ticket.getWebsiteId())
                    .eq(AppUser::getUserId, userId)
                    .in(AppUser::getRole, "owner", "admin", "developer")
                    .eq(AppUser::getStatus, 0)) > 0;
            reply.setIsStaff(isStaff ? 1 : 0);
        } else {
            reply.setIsStaff(0);
        }

        replyMapper.insert(reply);

        // 更新工单回复数 & 更新时间，若状态是 assigned 则推进为 processing
        update(new LambdaUpdateWrapper<AppTicket>()
                .eq(AppTicket::getTicketId, reply.getTicketId())
                .setSql("reply_count = reply_count + 1")
                .set(AppTicket::getUpdateTime, LocalDateTime.now())
                .eq(AppTicket::getStatus, "assigned")
                .set(AppTicket::getStatus, "processing"));

        // 异步推送：有新回复时通知对方
        if (ticket != null) sendReplyNotifyAsync(ticket, reply);

        return reply;
    }

    // ─── 统计 ─────────────────────────────────────────────────────
    @Override
    public Map<String, Long> stats(Long websiteId, Integer userId) {
        LambdaQueryWrapper<AppTicket> base = new LambdaQueryWrapper<AppTicket>()
                .eq(AppTicket::getDeleted, 0)
                .eq(ObjectUtil.isNotNull(websiteId), AppTicket::getWebsiteId, websiteId)
                .eq(ObjectUtil.isNotNull(userId), AppTicket::getSubmitUserId, userId);

        Map<String, Long> result = new HashMap<>();
        result.put("total", (long) count(base.clone()));
        result.put("pending", (long) count(base.clone().in(AppTicket::getStatus, "pending", "assigned")));
        result.put("processing", (long) count(base.clone().eq(AppTicket::getStatus, "processing")));
        result.put("resolved", (long) count(base.clone().eq(AppTicket::getStatus, "resolved")));
        result.put("closed", (long) count(base.clone().eq(AppTicket::getStatus, "closed")));
        return result;
    }

    // ─── 获取技术人员列表 ─────────────────────────────────────────
    @Override
    public List<Map<String, Object>> getTechStaffList() {
        List<AppUser> members = appUserService.list(
                new LambdaQueryWrapper<AppUser>()
                        .eq(AppUser::getStatus, 0)
                        .in(AppUser::getRole, "owner", "admin", "developer")
                        .select(AppUser::getUserId, AppUser::getNickname, AppUser::getAvatar));

        // 去重（一个用户可能在多个应用里）
        Map<Integer, AppUser> dedupMap = new LinkedHashMap<>();
        for (AppUser m : members) {
            dedupMap.put(m.getUserId(), m);
        }

        return dedupMap.values().stream().map(m -> {
            Map<String, Object> map = new HashMap<>();
            map.put("userId", m.getUserId());
            map.put("nickname", m.getNickname() != null ? m.getNickname() : "用户" + m.getUserId());
            map.put("avatar", m.getAvatar());
            return map;
        }).collect(Collectors.toList());
    }

    // ════════════════════════════════════════════════════════════
    //  异步消息推送
    // ════════════════════════════════════════════════════════════

    /**
     * 场景1：新工单提交 → 通知分配到的技术人员
     */
    @Async
    public void sendTicketCreatedAsync(AppTicket ticket) {
        String now = now();
        String title = String.format("🎫 新工单待处理（%s）", now);

        String assigneeLine = StrUtil.isNotBlank(ticket.getAssigneeName())
                ? "**处理人：** " + ticket.getAssigneeName()
                : "**处理人：** 待分配";

        String wecomContent = String.format(
                "## %s\n" +
                "> **工单号：** %s\n" +
                "> **标题：** %s\n" +
                "> **分类：** %s  **优先级：** %s\n" +
                "> **提交人：** %s\n" +
                "> %s\n" +
                "> **描述：** %s",
                title,
                nullSafe(ticket.getTicketNo()),
                nullSafe(ticket.getTitle()),
                categoryLabel(ticket.getCategory()), priorityLabel(ticket.getPriority()),
                nullSafe(ticket.getSubmitUserName()),
                assigneeLine,
                truncate(ticket.getContent(), 120)
        );

        List<String[]> feishuLines = Arrays.asList(
                new String[]{"工单号：" + nullSafe(ticket.getTicketNo())},
                new String[]{"标　题：" + nullSafe(ticket.getTitle())},
                new String[]{"分　类：" + categoryLabel(ticket.getCategory()) + "  优先级：" + priorityLabel(ticket.getPriority())},
                new String[]{"提交人：" + nullSafe(ticket.getSubmitUserName())},
                new String[]{assigneeLine.replace("**", "")},
                new String[]{"描　述：" + truncate(ticket.getContent(), 120)}
        );

        doSend(title, wecomContent, feishuLines, "ticket_created");
    }

    /**
     * 场景2：工单被重新分配 → 通知新处理人
     */
    @Async
    public void sendTicketAssignedAsync(AppTicket ticket) {
        String now = now();
        String title = String.format("📋 工单已分配给您（%s）", now);

        String wecomContent = String.format(
                "## %s\n" +
                "> **工单号：** %s\n" +
                "> **标题：** %s\n" +
                "> **分配给：** <font color=\"warning\">%s</font>\n" +
                "> **分类：** %s  **优先级：** %s\n" +
                "> **描述：** %s",
                title,
                nullSafe(ticket.getTicketNo()),
                nullSafe(ticket.getTitle()),
                nullSafe(ticket.getAssigneeName()),
                categoryLabel(ticket.getCategory()), priorityLabel(ticket.getPriority()),
                truncate(ticket.getContent(), 100)
        );

        List<String[]> feishuLines = Arrays.asList(
                new String[]{"工单号：" + nullSafe(ticket.getTicketNo())},
                new String[]{"标　题：" + nullSafe(ticket.getTitle())},
                new String[]{"分配给：" + nullSafe(ticket.getAssigneeName())},
                new String[]{"分　类：" + categoryLabel(ticket.getCategory()) + "  优先级：" + priorityLabel(ticket.getPriority())},
                new String[]{"描　述：" + truncate(ticket.getContent(), 100)}
        );

        doSend(title, wecomContent, feishuLines, "ticket_assigned");
    }

    /**
     * 场景3：有新回复 → 技术人员回复则通知客户，客户回复则通知技术人员
     */
    @Async
    public void sendReplyNotifyAsync(AppTicket ticket, AppTicketReply reply) {
        boolean isStaff = Integer.valueOf(1).equals(reply.getIsStaff());
        String now = now();
        String who = isStaff ? "技术人员" : "用户";
        String notifyRole = isStaff ? "您的工单有新回复" : "工单有用户新回复";
        String title = String.format("💬 %s（%s）", notifyRole, now);

        String wecomContent = String.format(
                "## %s\n" +
                "> **工单号：** %s\n" +
                "> **标题：** %s\n" +
                "> **回复人：** %s（%s）\n" +
                "> **回复内容：** %s",
                title,
                nullSafe(ticket.getTicketNo()),
                nullSafe(ticket.getTitle()),
                nullSafe(reply.getUserName()), who,
                truncate(reply.getContent(), 200)
        );

        List<String[]> feishuLines = Arrays.asList(
                new String[]{"工单号：" + nullSafe(ticket.getTicketNo())},
                new String[]{"标　题：" + nullSafe(ticket.getTitle())},
                new String[]{"回复人：" + nullSafe(reply.getUserName()) + "（" + who + "）"},
                new String[]{"回复内容：" + truncate(reply.getContent(), 200)}
        );

        doSend(title, wecomContent, feishuLines, "ticket_reply");
    }

    /**
     * 场景4：状态变更（已解决/已关闭）→ 通知提交人
     */
    @Async
    public void sendStatusChangedAsync(AppTicket ticket, String status) {
        String now = now();
        String emoji = "resolved".equals(status) ? "✅" : "🔒";
        String statusLabel = "resolved".equals(status) ? "已解决" : "已关闭";
        String title = String.format("%s 工单%s（%s）", emoji, statusLabel, now);

        String wecomContent = String.format(
                "## %s\n" +
                "> **工单号：** %s\n" +
                "> **标题：** %s\n" +
                "> **提交人：** %s\n" +
                "> **处理人：** %s\n" +
                "> **状态：** <font color=\"%s\">%s</font>",
                title,
                nullSafe(ticket.getTicketNo()),
                nullSafe(ticket.getTitle()),
                nullSafe(ticket.getSubmitUserName()),
                nullSafe(ticket.getAssigneeName()),
                "resolved".equals(status) ? "info" : "comment",
                statusLabel
        );

        List<String[]> feishuLines = Arrays.asList(
                new String[]{"工单号：" + nullSafe(ticket.getTicketNo())},
                new String[]{"标　题：" + nullSafe(ticket.getTitle())},
                new String[]{"提交人：" + nullSafe(ticket.getSubmitUserName())},
                new String[]{"处理人：" + nullSafe(ticket.getAssigneeName())},
                new String[]{"状　态：" + statusLabel}
        );

        doSend(title, wecomContent, feishuLines, "ticket_status");
    }

    /**
     * 统一发送入口：企业微信 + 飞书
     */
    private void doSend(String title, String wecomContent, List<String[]> feishuLines, String scene) {
        if (StrUtil.isNotBlank(wecomWebhook)) {
            try {
                sendWecom(wecomContent);
            } catch (Exception e) {
                log.warn("[工单通知][{}] 企业微信发送失败: {}", scene, e.getMessage());
            }
        }
        if (StrUtil.isNotBlank(feishuWebhook)) {
            try {
                sendFeishu(title, feishuLines);
            } catch (Exception e) {
                log.warn("[工单通知][{}] 飞书发送失败: {}", scene, e.getMessage());
            }
        }
    }

    /**
     * 企业微信群机器人 - markdown 消息
     * 文档：https://developer.work.weixin.qq.com/document/path/91770
     */
    private void sendWecom(String content) {
        Map<String, Object> textMap = new HashMap<>();
        textMap.put("content", content);
        Map<String, Object> payload = new HashMap<>();
        payload.put("msgtype", "markdown");
        payload.put("markdown", textMap);
        String resp = HttpUtil.post(wecomWebhook, JSON.toJSONString(payload));
        log.info("[工单通知] 企业微信推送结果: {}", resp);
    }

    /**
     * 飞书群机器人 - 富文本（post）消息
     * 文档：https://open.feishu.cn/document/client-docs/bot-v3/add-custom-bot
     */
    private void sendFeishu(String title, List<String[]> lines) {
        List<List<Map<String, String>>> content = new ArrayList<>();
        for (String[] line : lines) {
            Map<String, String> node = new HashMap<>();
            node.put("tag", "text");
            node.put("text", line[0]);
            content.add(Collections.singletonList(node));
        }

        Map<String, Object> zhCn = new HashMap<>();
        zhCn.put("title", title);
        zhCn.put("content", content);
        Map<String, Object> postContent = new HashMap<>();
        postContent.put("zh_cn", zhCn);
        Map<String, Object> post = new HashMap<>();
        post.put("content", postContent);
        Map<String, Object> payload = new HashMap<>();
        payload.put("msg_type", "post");
        payload.put("content", post);

        String resp = HttpUtil.post(feishuWebhook, JSON.toJSONString(payload));
        log.info("[工单通知] 飞书推送结果: {}", resp);
    }

    // ════════════════════════════════════════════════════════════
    //  工具方法
    // ════════════════════════════════════════════════════════════

    private String now() {
        return LocalDateTime.now().format(DateTimeFormatter.ofPattern("MM-dd HH:mm"));
    }

    private String nullSafe(String s) {
        return StrUtil.isBlank(s) ? "—" : s;
    }

    private String truncate(String s, int max) {
        if (StrUtil.isBlank(s)) return "—";
        return s.length() > max ? s.substring(0, max) + "…" : s;
    }

    private String categoryLabel(String category) {
        if (category == null) return "其他";
        return switch (category) {
            case "bug" -> "Bug反馈";
            case "feature" -> "功能需求";
            case "config" -> "配置问题";
            case "performance" -> "性能问题";
            case "security" -> "安全问题";
            default -> category;
        };
    }

    private String priorityLabel(String priority) {
        if (priority == null) return "普通";
        return switch (priority) {
            case "urgent" -> "🔴 紧急";
            case "high" -> "🟠 高";
            case "normal" -> "🟡 普通";
            case "low" -> "🟢 低";
            default -> priority;
        };
    }
}
