package com.writeoff.module.audit.service;

import com.writeoff.common.api.PageResult;
import com.writeoff.common.exception.BusinessException;
import com.writeoff.module.audit.dto.AuditFlowNodeRequest;
import com.writeoff.module.audit.dto.CreateAuditFlowRequest;
import com.writeoff.module.audit.dto.UpdateAuditFlowRequest;
import com.writeoff.module.audit.model.AuditFlowInfo;
import com.writeoff.module.audit.model.AuditFlowNodeInfo;
import com.writeoff.security.AuthContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.sql.PreparedStatement;
import java.sql.Statement;
import java.util.List;

@Service
public class AuditFlowManageService {
    private final JdbcTemplate jdbcTemplate;

    private static final RowMapper<AuditFlowNodeInfo> NODE_ROW_MAPPER = (rs, n) -> new AuditFlowNodeInfo(
            rs.getLong("id"),
            rs.getString("node_code"),
            rs.getString("node_name"),
            rs.getInt("sort_no"),
            rs.getString("status"),
            rs.getString("assignee_type"),
            rs.getObject("assignee_ref_id") == null ? null : rs.getLong("assignee_ref_id")
    );

    public AuditFlowManageService(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public PageResult<AuditFlowInfo> list(int pageNo, int pageSize) {
        int safePage = Math.max(pageNo, 1);
        int safeSize = Math.min(Math.max(pageSize, 1), 100);
        int offset = (safePage - 1) * safeSize;

        Integer total = jdbcTemplate.queryForObject(
                "SELECT COUNT(1) FROM audit_flow WHERE tenant_id=? AND is_deleted=0",
                Integer.class,
                tenantId()
        );
        long totalCount = total == null ? 0 : total;

        List<AuditFlowInfo> list = jdbcTemplate.query(
                "SELECT id, flow_code, flow_name, status, is_default, " +
                        "DATE_FORMAT(effective_start_at, '%Y-%m-%d %H:%i:%s') AS effective_start_at, " +
                        "DATE_FORMAT(effective_end_at, '%Y-%m-%d %H:%i:%s') AS effective_end_at " +
                        "FROM audit_flow WHERE tenant_id=? AND is_deleted=0 ORDER BY id DESC LIMIT ? OFFSET ?",
                (rs, n) -> {
                    Long id = rs.getLong("id");
                    return new AuditFlowInfo(
                            id,
                            rs.getString("flow_code"),
                            rs.getString("flow_name"),
                            rs.getString("status"),
                            rs.getInt("is_default") == 1,
                            rs.getString("effective_start_at"),
                            rs.getString("effective_end_at"),
                            loadNodes(id)
                    );
                },
                tenantId(),
                safeSize,
                offset
        );
        return new PageResult<>(list, totalCount, safePage, safeSize);
    }

    @Transactional
    public AuditFlowInfo create(CreateAuditFlowRequest request) {
        Integer exists = jdbcTemplate.queryForObject(
                "SELECT COUNT(1) FROM audit_flow WHERE tenant_id=? AND flow_code=?",
                Integer.class,
                tenantId(),
                request.getFlowCode()
        );
        if (exists != null && exists > 0) {
            throw new BusinessException(10001, "流程编码已存在");
        }
        KeyHolder keyHolder = new GeneratedKeyHolder();
        jdbcTemplate.update(connection -> {
            PreparedStatement ps = connection.prepareStatement(
                    "INSERT INTO audit_flow (tenant_id, flow_code, flow_name, status, is_default, effective_start_at, effective_end_at) " +
                            "VALUES (?, ?, ?, 'ENABLED', 0, ?, ?)",
                    Statement.RETURN_GENERATED_KEYS
            );
            ps.setLong(1, tenantId());
            ps.setString(2, request.getFlowCode());
            ps.setString(3, request.getFlowName());
            ps.setString(4, request.getEffectiveStartAt());
            ps.setString(5, request.getEffectiveEndAt());
            return ps;
        }, keyHolder);
        Long flowId = keyHolder.getKey() == null ? null : keyHolder.getKey().longValue();
        if (flowId == null) {
            throw new BusinessException(10001, "流程创建失败");
        }
        saveNodes(flowId, request.getNodes());
        return findById(flowId);
    }

    @Transactional
    public AuditFlowInfo update(Long flowId, UpdateAuditFlowRequest request) {
        assertFlowExists(flowId);
        jdbcTemplate.update(
                "UPDATE audit_flow SET flow_name=?, effective_start_at=?, effective_end_at=?, updated_at=CURRENT_TIMESTAMP WHERE id=? AND tenant_id=?",
                request.getFlowName(),
                request.getEffectiveStartAt(),
                request.getEffectiveEndAt(),
                flowId,
                tenantId()
        );
        jdbcTemplate.update(
                "DELETE a FROM audit_flow_node_assignee a JOIN audit_flow_node n ON a.flow_node_id=n.id WHERE n.flow_id=?",
                flowId
        );
        jdbcTemplate.update("DELETE FROM audit_flow_node WHERE flow_id=?", flowId);
        saveNodes(flowId, request.getNodes());
        return findById(flowId);
    }

    @Transactional
    public AuditFlowInfo copy(Long flowId) {
        AuditFlowInfo source = findById(flowId);
        String newCode = source.getFlowCode() + "_COPY_" + System.currentTimeMillis();
        CreateAuditFlowRequest request = new CreateAuditFlowRequest();
        request.setFlowCode(newCode);
        request.setFlowName(source.getFlowName() + " - 复制");
        request.setEffectiveStartAt(source.getEffectiveStartAt());
        request.setEffectiveEndAt(source.getEffectiveEndAt());
        List<AuditFlowNodeRequest> nodes = loadNodes(flowId).stream().map(node -> {
            AuditFlowNodeRequest item = new AuditFlowNodeRequest();
            item.setNodeCode(node.getNodeCode());
            item.setNodeName(node.getNodeName());
            item.setSortNo(node.getSortNo());
            item.setAssigneeType(node.getAssigneeType());
            item.setAssigneeRefId(node.getAssigneeRefId());
            return item;
        }).collect(java.util.stream.Collectors.toList());
        request.setNodes(nodes);
        return create(request);
    }

    @Transactional
    public void setDefault(Long flowId) {
        assertFlowExists(flowId);
        jdbcTemplate.update("UPDATE audit_flow SET is_default=0 WHERE tenant_id=?", tenantId());
        jdbcTemplate.update("UPDATE audit_flow SET is_default=1 WHERE tenant_id=? AND id=?", tenantId(), flowId);
    }

    public void enable(Long flowId) {
        updateStatus(flowId, "ENABLED");
    }

    public void disable(Long flowId) {
        updateStatus(flowId, "DISABLED");
    }

    @Transactional
    public void softDelete(Long flowId) {
        assertFlowExists(flowId);
        // 不允许删除默认流程
        Integer isDefault = jdbcTemplate.queryForObject(
                "SELECT is_default FROM audit_flow WHERE id=? AND tenant_id=? AND is_deleted=0",
                Integer.class, flowId, tenantId());
        if (isDefault != null && isDefault == 1) {
            throw new BusinessException(10001, "默认审核流不能删除");
        }
        // 不允许删除启用状态的流程
        String status = jdbcTemplate.queryForObject(
                "SELECT status FROM audit_flow WHERE id=? AND tenant_id=? AND is_deleted=0",
                String.class, flowId, tenantId());
        if ("ENABLED".equals(status)) {
            throw new BusinessException(10001, "请先停用审核流再删除");
        }
        jdbcTemplate.update(
                "UPDATE audit_flow SET is_deleted=1, updated_at=CURRENT_TIMESTAMP WHERE id=? AND tenant_id=?",
                flowId, tenantId());
    }

    private void updateStatus(Long flowId, String status) {
        assertFlowExists(flowId);
        jdbcTemplate.update("UPDATE audit_flow SET status=?, updated_at=CURRENT_TIMESTAMP WHERE id=? AND tenant_id=?", status, flowId, tenantId());
    }

    private void assertFlowExists(Long flowId) {
        Integer count = jdbcTemplate.queryForObject(
                "SELECT COUNT(1) FROM audit_flow WHERE id=? AND tenant_id=? AND is_deleted=0",
                Integer.class,
                flowId,
                tenantId()
        );
        if (count == null || count == 0) {
            throw new BusinessException(10003, "审核流不存在");
        }
    }

    private AuditFlowInfo findById(Long flowId) {
        List<AuditFlowInfo> list = jdbcTemplate.query(
                "SELECT id, flow_code, flow_name, status, is_default, " +
                        "DATE_FORMAT(effective_start_at, '%Y-%m-%d %H:%i:%s') AS effective_start_at, " +
                        "DATE_FORMAT(effective_end_at, '%Y-%m-%d %H:%i:%s') AS effective_end_at " +
                        "FROM audit_flow WHERE tenant_id=? AND id=? AND is_deleted=0",
                (rs, n) -> new AuditFlowInfo(
                        rs.getLong("id"),
                        rs.getString("flow_code"),
                        rs.getString("flow_name"),
                        rs.getString("status"),
                        rs.getInt("is_default") == 1,
                        rs.getString("effective_start_at"),
                        rs.getString("effective_end_at"),
                        loadNodes(rs.getLong("id"))
                ),
                tenantId(),
                flowId
        );
        if (list.isEmpty()) {
            throw new BusinessException(10003, "审核流不存在");
        }
        return list.get(0);
    }

    private List<AuditFlowNodeInfo> loadNodes(Long flowId) {
        return jdbcTemplate.query(
                "SELECT n.id, n.node_code, n.node_name, n.sort_no, n.status, a.assignee_type, a.assignee_ref_id " +
                        "FROM audit_flow_node n " +
                        "LEFT JOIN audit_flow_node_assignee a ON n.id=a.flow_node_id " +
                        "WHERE n.flow_id=? ORDER BY n.sort_no ASC",
                NODE_ROW_MAPPER,
                flowId
        );
    }

    private void saveNodes(Long flowId, List<AuditFlowNodeRequest> nodes) {
        for (AuditFlowNodeRequest node : nodes) {
            KeyHolder keyHolder = new GeneratedKeyHolder();
            jdbcTemplate.update(connection -> {
                PreparedStatement ps = connection.prepareStatement(
                        "INSERT INTO audit_flow_node (flow_id, node_code, node_name, sort_no, status) VALUES (?, ?, ?, ?, 'ENABLED')",
                        Statement.RETURN_GENERATED_KEYS
                );
                ps.setLong(1, flowId);
                ps.setString(2, node.getNodeCode());
                ps.setString(3, node.getNodeName());
                ps.setInt(4, node.getSortNo());
                return ps;
            }, keyHolder);
            Number key = keyHolder.getKey();
            Long flowNodeId = key == null ? null : key.longValue();
            if (flowNodeId != null && node.getAssigneeType() != null && !node.getAssigneeType().trim().isEmpty()) {
                jdbcTemplate.update(
                        "INSERT INTO audit_flow_node_assignee (flow_node_id, assignee_type, assignee_ref_id) VALUES (?, ?, ?)",
                        flowNodeId,
                        node.getAssigneeType(),
                        node.getAssigneeRefId()
                );
            }
        }
    }

    private Long tenantId() {
        return AuthContext.requireTenantId();
    }
}
