package com.writeoff.module.audit.repository;

import com.writeoff.module.audit.model.AuditNode;
import com.writeoff.module.audit.model.AuditTask;
import com.writeoff.module.audit.model.AuditTaskStatus;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
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.Repository;
import com.writeoff.security.AuthContext;

import java.sql.PreparedStatement;
import java.sql.Statement;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

@Repository
@ConditionalOnProperty(prefix = "app.repository", name = "mode", havingValue = "jdbc", matchIfMissing = true)
public class JdbcAuditTaskRepository implements AuditTaskRepository {
    private final JdbcTemplate jdbcTemplate;
    private static final RowMapper<AuditTask> ROW_MAPPER = (rs, n) -> new AuditTask(
            rs.getLong("id"),
            rs.getLong("meeting_id"),
            AuditNode.valueOf(rs.getString("audit_node")),
            rs.getObject("assignee_user_id") == null ? null : rs.getLong("assignee_user_id"),
            AuditTaskStatus.valueOf(rs.getString("status")),
            rs.getString("opinion"),
            rs.getString("sla_deadline_at"),
            rs.getInt("timeout_level"),
            rs.getInt("overtime_hours"),
            rs.getInt("is_overtime") == 1,
            rs.getObject("transfer_from_user_id") == null ? null : rs.getLong("transfer_from_user_id"),
            rs.getString("transfer_reason"),
            rs.getString("return_reason"),
            rs.getInt("reject_count"),
            rs.getString("last_reject_reason"),
            rs.getString("last_action_at")
    );

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

    @Override
    public AuditTask save(AuditTask task) {
        if (task.getId() == null) {
            KeyHolder keyHolder = new GeneratedKeyHolder();
            jdbcTemplate.update(connection -> {
                PreparedStatement ps = connection.prepareStatement(
                        "INSERT INTO audit_task (tenant_id, meeting_id, audit_node, assignee_user_id, status, opinion, sla_deadline_at, timeout_level, overtime_hours, is_overtime, " +
                                "transfer_from_user_id, transfer_reason, return_reason, reject_count, last_reject_reason, last_action_at, created_by, updated_by) " +
                                "VALUES (?, ?, ?, ?, ?, ?, DATE_ADD(NOW(), INTERVAL 24 HOUR), 0, 0, 0, NULL, NULL, NULL, 0, NULL, NOW(), 0, 0)",
                        Statement.RETURN_GENERATED_KEYS
                );
                ps.setLong(1, tenantId());
                ps.setLong(2, task.getMeetingId());
                ps.setString(3, task.getNode().name());
                ps.setObject(4, task.getAssigneeUserId());
                ps.setString(5, task.getStatus().name());
                ps.setString(6, task.getOpinion());
                return ps;
            }, keyHolder);
            Number key = keyHolder.getKey();
            Long id = key == null ? null : key.longValue();
            return new AuditTask(id, task.getMeetingId(), task.getNode(), task.getAssigneeUserId(), task.getStatus(), task.getOpinion());
        }
        jdbcTemplate.update(
                "UPDATE audit_task SET status=?, opinion=?, assignee_user_id=?, transfer_from_user_id=?, transfer_reason=?, return_reason=?, reject_count=?, " +
                        "last_reject_reason=?, last_action_at=NOW(), updated_by=0 WHERE tenant_id=? AND id=?",
                task.getStatus().name(),
                task.getOpinion(),
                task.getAssigneeUserId(),
                task.getTransferFromUserId(),
                task.getTransferReason(),
                task.getReturnReason(),
                task.getRejectCount() == null ? 0 : task.getRejectCount(),
                task.getLastRejectReason(),
                tenantId(),
                task.getId()
        );
        return task;
    }

    @Override
    public Optional<AuditTask> findById(Long id) {
        List<AuditTask> list = jdbcTemplate.query(
                "SELECT id, meeting_id, audit_node, assignee_user_id, status, opinion, " +
                        "DATE_FORMAT(sla_deadline_at, '%Y-%m-%d %H:%i:%s') AS sla_deadline_at, IFNULL(timeout_level, 0) AS timeout_level, " +
                        "IFNULL(overtime_hours, 0) AS overtime_hours, IFNULL(is_overtime, 0) AS is_overtime, " +
                        "transfer_from_user_id, transfer_reason, return_reason, IFNULL(reject_count, 0) AS reject_count, last_reject_reason, " +
                        "DATE_FORMAT(last_action_at, '%Y-%m-%d %H:%i:%s') AS last_action_at " +
                        "FROM audit_task WHERE tenant_id=? AND id=? AND is_deleted=0",
                ROW_MAPPER, tenantId(), id
        );
        return list.stream().findFirst();
    }

    @Override
    public List<AuditTask> findAll() {
        refreshTimeoutLevels();
        return jdbcTemplate.query(
                "SELECT id, meeting_id, audit_node, assignee_user_id, status, opinion, " +
                        "DATE_FORMAT(sla_deadline_at, '%Y-%m-%d %H:%i:%s') AS sla_deadline_at, IFNULL(timeout_level, 0) AS timeout_level, " +
                        "IFNULL(overtime_hours, 0) AS overtime_hours, IFNULL(is_overtime, 0) AS is_overtime, " +
                        "transfer_from_user_id, transfer_reason, return_reason, IFNULL(reject_count, 0) AS reject_count, last_reject_reason, " +
                        "DATE_FORMAT(last_action_at, '%Y-%m-%d %H:%i:%s') AS last_action_at " +
                        "FROM audit_task WHERE tenant_id=? AND is_deleted=0 ORDER BY id DESC",
                ROW_MAPPER, tenantId()
        );
    }

    @Override
    public Optional<AuditTask> findLatestByMeetingId(Long meetingId) {
        List<AuditTask> list = jdbcTemplate.query(
                "SELECT id, meeting_id, audit_node, assignee_user_id, status, opinion, " +
                        "DATE_FORMAT(sla_deadline_at, '%Y-%m-%d %H:%i:%s') AS sla_deadline_at, IFNULL(timeout_level, 0) AS timeout_level, " +
                        "IFNULL(overtime_hours, 0) AS overtime_hours, IFNULL(is_overtime, 0) AS is_overtime, " +
                        "transfer_from_user_id, transfer_reason, return_reason, IFNULL(reject_count, 0) AS reject_count, last_reject_reason, " +
                        "DATE_FORMAT(last_action_at, '%Y-%m-%d %H:%i:%s') AS last_action_at " +
                        "FROM audit_task WHERE tenant_id=? AND meeting_id=? AND is_deleted=0 ORDER BY id DESC LIMIT 1",
                ROW_MAPPER, tenantId(), meetingId
        );
        return list.stream().findFirst();
    }

    @Override
    public int withdrawPendingByMeetingId(Long meetingId, String reason, Long operatorUserId) {
        return jdbcTemplate.update(
                "UPDATE audit_task SET is_deleted=1, opinion=?, updated_by=?, updated_at=CURRENT_TIMESTAMP " +
                        "WHERE tenant_id=? AND meeting_id=? AND status='PENDING' AND is_deleted=0",
                reason == null ? "会议撤回提交" : ("会议撤回提交:" + reason),
                operatorUserId == null ? 0L : operatorUserId,
                tenantId(),
                meetingId
        );
    }

    @Override
    public void transfer(Long taskId, Long toUserId, String reason, Long operatorUserId) {
        AuditTask task = findById(taskId).orElseThrow(() -> new IllegalArgumentException("task not found"));
        if (task.getStatus() != AuditTaskStatus.PENDING) {
            throw new IllegalStateException("task is not pending");
        }
        jdbcTemplate.update(
                "UPDATE audit_task SET assignee_user_id=?, opinion=?, sla_deadline_at=DATE_ADD(NOW(), INTERVAL 24 HOUR), timeout_level=0, updated_by=?, updated_at=CURRENT_TIMESTAMP " +
                        ", overtime_hours=0, is_overtime=0, transfer_from_user_id=?, transfer_reason=?, last_action_at=NOW() " +
                        "WHERE tenant_id=? AND id=?",
                toUserId,
                reason == null ? task.getOpinion() : ("转审:" + reason),
                operatorUserId == null ? 0L : operatorUserId,
                task.getAssigneeUserId(),
                reason,
                tenantId(),
                taskId
        );
        jdbcTemplate.update(
                "INSERT INTO audit_transfer_log (tenant_id, task_id, from_user_id, to_user_id, reason, created_by) VALUES (?, ?, ?, ?, ?, ?)",
                tenantId(),
                taskId,
                task.getAssigneeUserId(),
                toUserId,
                reason,
                operatorUserId == null ? 0L : operatorUserId
        );
    }

    @Override
    public int batchRemind(List<Long> taskIds, Long operatorUserId) {
        if (taskIds == null || taskIds.isEmpty()) {
            return 0;
        }
        int count = 0;
        for (Long taskId : taskIds) {
            Integer updated = jdbcTemplate.queryForObject(
                    "SELECT COUNT(1) FROM audit_task WHERE tenant_id=? AND id=? AND status='PENDING' AND is_deleted=0",
                    Integer.class,
                    tenantId(),
                    taskId
            );
            if (updated != null && updated > 0) {
                count++;
            }
        }
        return count;
    }

    @Override
    public Map<String, Object> slaStat() {
        refreshTimeoutLevels();
        Integer pending = jdbcTemplate.queryForObject(
                "SELECT COUNT(1) FROM audit_task WHERE tenant_id=? AND status='PENDING' AND is_deleted=0",
                Integer.class,
                tenantId()
        );
        Integer timeout4h = jdbcTemplate.queryForObject(
                "SELECT COUNT(1) FROM audit_task WHERE tenant_id=? AND status='PENDING' AND timeout_level>=1 AND is_deleted=0",
                Integer.class,
                tenantId()
        );
        Integer timeout12h = jdbcTemplate.queryForObject(
                "SELECT COUNT(1) FROM audit_task WHERE tenant_id=? AND status='PENDING' AND timeout_level>=2 AND is_deleted=0",
                Integer.class,
                tenantId()
        );
        Integer timeout24h = jdbcTemplate.queryForObject(
                "SELECT COUNT(1) FROM audit_task WHERE tenant_id=? AND status='PENDING' AND timeout_level>=3 AND is_deleted=0",
                Integer.class,
                tenantId()
        );
        Map<String, Object> data = new LinkedHashMap<>();
        data.put("pendingTotal", pending == null ? 0 : pending);
        data.put("timeout4h", timeout4h == null ? 0 : timeout4h);
        data.put("timeout12h", timeout12h == null ? 0 : timeout12h);
        data.put("timeout24h", timeout24h == null ? 0 : timeout24h);
        return data;
    }

    private void refreshTimeoutLevels() {
        jdbcTemplate.update(
                "UPDATE audit_task SET timeout_level = CASE " +
                        "WHEN status='PENDING' AND sla_deadline_at IS NOT NULL AND TIMESTAMPDIFF(HOUR, sla_deadline_at, NOW()) >= 24 THEN 3 " +
                        "WHEN status='PENDING' AND sla_deadline_at IS NOT NULL AND TIMESTAMPDIFF(HOUR, sla_deadline_at, NOW()) >= 12 THEN 2 " +
                        "WHEN status='PENDING' AND sla_deadline_at IS NOT NULL AND TIMESTAMPDIFF(HOUR, sla_deadline_at, NOW()) >= 4 THEN 1 " +
                        "ELSE 0 END, " +
                        "overtime_hours = CASE WHEN status='PENDING' AND sla_deadline_at IS NOT NULL AND TIMESTAMPDIFF(HOUR, sla_deadline_at, NOW()) > 0 " +
                        "THEN TIMESTAMPDIFF(HOUR, sla_deadline_at, NOW()) ELSE 0 END, " +
                        "is_overtime = CASE WHEN status='PENDING' AND sla_deadline_at IS NOT NULL AND TIMESTAMPDIFF(HOUR, sla_deadline_at, NOW()) > 0 THEN 1 ELSE 0 END " +
                        "WHERE tenant_id=? AND is_deleted=0",
                tenantId()
        );
    }

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