package com.writeoff.module.meeting.repository;

import com.writeoff.module.meeting.model.Meeting;
import com.writeoff.module.meeting.model.MeetingAuditStatus;
import com.writeoff.module.meeting.model.MeetingStatus;
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.List;
import java.util.Optional;

@Repository
@ConditionalOnProperty(prefix = "app.repository", name = "mode", havingValue = "jdbc", matchIfMissing = true)
public class JdbcMeetingRepository implements MeetingRepository {
    private final JdbcTemplate jdbcTemplate;
    private static final RowMapper<Meeting> ROW_MAPPER = (rs, n) -> {
        Meeting meeting = new Meeting(
                rs.getLong("id"),
                rs.getLong("project_id"),
                rs.getString("topic"),
                rs.getString("meeting_category"),
                rs.getString("meeting_form"),
                rs.getString("location"),
                rs.getString("start_time"),
                rs.getString("end_time"),
                rs.getLong("budget_cent"),
                rs.getBigDecimal("labor_ratio") == null ? 0d : rs.getBigDecimal("labor_ratio").doubleValue(),
                rs.getBigDecimal("catering_ratio") == null ? 0d : rs.getBigDecimal("catering_ratio").doubleValue(),
                MeetingStatus.valueOf(rs.getString("meeting_status")),
                MeetingAuditStatus.valueOf(rs.getString("audit_status")),
                rs.getString("current_audit_node"),
                rs.getString("last_submit_at"),
                rs.getString("last_reject_reason"),
                rs.getInt("overdue_days"),
                rs.getString("risk_flags_json"),
                rs.getInt("is_frozen") == 1,
                rs.getString("freeze_reason"),
                rs.getObject("current_auditor_user_id") == null ? null : rs.getLong("current_auditor_user_id"),
                rs.getString("node_deadline_at"),
                rs.getInt("reject_count"),
                rs.getString("last_action_at"),
                rs.getString("status_changed_at"),
                rs.getObject("status_changed_by") == null ? null : rs.getLong("status_changed_by"),
                rs.getString("cancel_reason"),
                rs.getString("postpone_reason"),
                rs.getString("withdraw_reason"),
                rs.getInt("lock_version"),
                rs.getString("lock_at"),
                rs.getObject("locked_by") == null ? null : rs.getLong("locked_by"),
                rs.getString("invoice_config_json")
        );
        meeting.setProjectName(rs.getString("project_name"));
        meeting.setDeleted(rs.getInt("is_deleted") == 1);
        return meeting;
    };

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

    @Override
    public Meeting save(Meeting meeting) {
        if (meeting.getId() == null) {
            KeyHolder keyHolder = new GeneratedKeyHolder();
            jdbcTemplate.update(connection -> {
                PreparedStatement ps = connection.prepareStatement(
                        "INSERT INTO meeting (tenant_id, project_id, topic, meeting_category, meeting_form, location, start_time, end_time, budget_cent, labor_ratio, catering_ratio, meeting_status, audit_status, current_audit_node, last_submit_at, last_reject_reason, overdue_days, risk_flags_json, is_frozen, freeze_reason, current_auditor_user_id, node_deadline_at, reject_count, last_action_at, status_changed_at, status_changed_by, cancel_reason, postpone_reason, withdraw_reason, lock_version, lock_at, locked_by, invoice_config_json, created_by, updated_by) " +
                                "VALUES (?, ?, ?, ?, ?, ?, STR_TO_DATE(?, '%Y-%m-%d %H:%i:%s'), STR_TO_DATE(?, '%Y-%m-%d %H:%i:%s'), ?, ?, ?, ?, ?, ?, STR_TO_DATE(?, '%Y-%m-%dT%H:%i:%s'), ?, ?, ?, ?, ?, ?, STR_TO_DATE(?, '%Y-%m-%dT%H:%i:%s'), ?, STR_TO_DATE(?, '%Y-%m-%dT%H:%i:%s'), STR_TO_DATE(?, '%Y-%m-%dT%H:%i:%s'), ?, ?, ?, ?, ?, STR_TO_DATE(?, '%Y-%m-%dT%H:%i:%s'), ?, ?, ?, ?)",
                        Statement.RETURN_GENERATED_KEYS
                );
                Long operator = safeUserId();
                ps.setLong(1, tenantId());
                ps.setLong(2, meeting.getProjectId());
                ps.setString(3, meeting.getTopic());
                ps.setString(4, meeting.getMeetingCategory());
                ps.setString(5, meeting.getMeetingForm());
                ps.setString(6, meeting.getLocation());
                ps.setString(7, meeting.getStartTime());
                ps.setString(8, meeting.getEndTime());
                ps.setLong(9, meeting.getBudgetCent());
                ps.setBigDecimal(10, java.math.BigDecimal.valueOf(meeting.getLaborRatio()));
                ps.setBigDecimal(11, java.math.BigDecimal.valueOf(meeting.getCateringRatio()));
                ps.setString(12, meeting.getStatus().name());
                ps.setString(13, meeting.getAuditStatus().name());
                ps.setString(14, meeting.getCurrentAuditNode());
                ps.setString(15, meeting.getLastSubmitAt());
                ps.setString(16, meeting.getLastRejectReason());
                ps.setInt(17, meeting.getOverdueDays());
                ps.setString(18, meeting.getRiskFlagsJson());
                ps.setInt(19, meeting.isFrozen() ? 1 : 0);
                ps.setString(20, meeting.getFreezeReason());
                ps.setObject(21, meeting.getCurrentAuditorUserId());
                ps.setString(22, meeting.getNodeDeadlineAt());
                ps.setInt(23, meeting.getRejectCount());
                ps.setString(24, meeting.getLastActionAt());
                ps.setString(25, meeting.getStatusChangedAt());
                ps.setObject(26, meeting.getStatusChangedBy());
                ps.setString(27, meeting.getCancelReason());
                ps.setString(28, meeting.getPostponeReason());
                ps.setString(29, meeting.getWithdrawReason());
                ps.setInt(30, meeting.getLockVersion());
                ps.setString(31, meeting.getLockAt());
                ps.setObject(32, meeting.getLockedBy());
                ps.setString(33, meeting.getInvoiceConfigJson());
                ps.setLong(34, operator);
                ps.setLong(35, operator);
                return ps;
            }, keyHolder);
            Number key = keyHolder.getKey();
            Long id = key == null ? null : key.longValue();
            return new Meeting(
                    id,
                    meeting.getProjectId(),
                    meeting.getTopic(),
                    meeting.getMeetingCategory(),
                    meeting.getMeetingForm(),
                    meeting.getLocation(),
                    meeting.getStartTime(),
                    meeting.getEndTime(),
                    meeting.getBudgetCent(),
                    meeting.getLaborRatio(),
                    meeting.getCateringRatio(),
                    meeting.getStatus(),
                    meeting.getAuditStatus(),
                    meeting.getCurrentAuditNode(),
                    meeting.getLastSubmitAt(),
                    meeting.getLastRejectReason(),
                    meeting.getOverdueDays(),
                    meeting.getRiskFlagsJson(),
                    meeting.isFrozen(),
                    meeting.getFreezeReason(),
                    meeting.getCurrentAuditorUserId(),
                    meeting.getNodeDeadlineAt(),
                    meeting.getRejectCount(),
                    meeting.getLastActionAt(),
                    meeting.getStatusChangedAt(),
                    meeting.getStatusChangedBy(),
                    meeting.getCancelReason(),
                    meeting.getPostponeReason(),
                    meeting.getWithdrawReason(),
                    meeting.getLockVersion(),
                    meeting.getLockAt(),
                    meeting.getLockedBy(),
                    meeting.getInvoiceConfigJson()
            );
        }
        jdbcTemplate.update(
                "UPDATE meeting SET topic=?, meeting_category=?, meeting_form=?, location=?, " +
                        "start_time=STR_TO_DATE(?, '%Y-%m-%d %H:%i:%s'), end_time=STR_TO_DATE(?, '%Y-%m-%d %H:%i:%s'), budget_cent=?, labor_ratio=?, catering_ratio=?, " +
                        "meeting_status=?, audit_status=?, current_audit_node=?, last_submit_at=STR_TO_DATE(?, '%Y-%m-%dT%H:%i:%s'), last_reject_reason=?, overdue_days=?, " +
                        "risk_flags_json=?, is_frozen=?, freeze_reason=?, current_auditor_user_id=?, node_deadline_at=STR_TO_DATE(?, '%Y-%m-%dT%H:%i:%s'), reject_count=?, last_action_at=STR_TO_DATE(?, '%Y-%m-%dT%H:%i:%s'), " +
                        "status_changed_at=STR_TO_DATE(?, '%Y-%m-%dT%H:%i:%s'), status_changed_by=?, cancel_reason=?, postpone_reason=?, withdraw_reason=?, lock_version=?, lock_at=STR_TO_DATE(?, '%Y-%m-%dT%H:%i:%s'), locked_by=?, invoice_config_json=?, updated_by=? WHERE tenant_id=? AND id=?",
                meeting.getTopic(),
                meeting.getMeetingCategory(),
                meeting.getMeetingForm(),
                meeting.getLocation(),
                meeting.getStartTime(),
                meeting.getEndTime(),
                meeting.getBudgetCent(),
                java.math.BigDecimal.valueOf(meeting.getLaborRatio()),
                java.math.BigDecimal.valueOf(meeting.getCateringRatio()),
                meeting.getStatus().name(),
                meeting.getAuditStatus().name(),
                meeting.getCurrentAuditNode(),
                meeting.getLastSubmitAt(),
                meeting.getLastRejectReason(),
                meeting.getOverdueDays(),
                meeting.getRiskFlagsJson(),
                meeting.isFrozen() ? 1 : 0,
                meeting.getFreezeReason(),
                meeting.getCurrentAuditorUserId(),
                meeting.getNodeDeadlineAt(),
                meeting.getRejectCount(),
                meeting.getLastActionAt(),
                meeting.getStatusChangedAt(),
                meeting.getStatusChangedBy(),
                meeting.getCancelReason(),
                meeting.getPostponeReason(),
                meeting.getWithdrawReason(),
                meeting.getLockVersion(),
                meeting.getLockAt(),
                meeting.getLockedBy(),
                meeting.getInvoiceConfigJson(),
                safeUserId(),
                tenantId(),
                meeting.getId()
        );
        return meeting;
    }

    @Override
    public Optional<Meeting> findById(Long id) {
        List<Meeting> list = jdbcTemplate.query(
                "SELECT m.id, m.project_id, p.project_name, m.topic, m.meeting_category, m.meeting_form, m.location, " +
                        "DATE_FORMAT(m.start_time, '%Y-%m-%d %H:%i:%s') AS start_time, DATE_FORMAT(m.end_time, '%Y-%m-%d %H:%i:%s') AS end_time, " +
                        "m.budget_cent, m.labor_ratio, m.catering_ratio, m.meeting_status, m.audit_status, m.current_audit_node, " +
                        "DATE_FORMAT(m.last_submit_at, '%Y-%m-%dT%H:%i:%s') AS last_submit_at, m.last_reject_reason, m.overdue_days, m.risk_flags_json, m.is_frozen, m.freeze_reason, " +
                        "m.current_auditor_user_id, DATE_FORMAT(m.node_deadline_at, '%Y-%m-%dT%H:%i:%s') AS node_deadline_at, m.reject_count, DATE_FORMAT(m.last_action_at, '%Y-%m-%dT%H:%i:%s') AS last_action_at, " +
                        "DATE_FORMAT(m.status_changed_at, '%Y-%m-%dT%H:%i:%s') AS status_changed_at, m.status_changed_by, m.cancel_reason, m.postpone_reason, m.withdraw_reason, m.lock_version, DATE_FORMAT(m.lock_at, '%Y-%m-%dT%H:%i:%s') AS lock_at, m.locked_by, m.invoice_config_json, m.is_deleted " +
                        "FROM meeting m LEFT JOIN project p ON m.tenant_id=p.tenant_id AND m.project_id=p.id " +
                        "WHERE m.tenant_id=? AND m.id=? AND m.is_deleted=0",
                ROW_MAPPER,
                tenantId(),
                id
        );
        return list.stream().findFirst();
    }

    @Override
    public List<Meeting> findAll(boolean includeDeleted) {
        String whereSql = includeDeleted ? "WHERE m.tenant_id=? " : "WHERE m.tenant_id=? AND m.is_deleted=0 ";
        return jdbcTemplate.query(
                "SELECT m.id, m.project_id, p.project_name, m.topic, m.meeting_category, m.meeting_form, m.location, " +
                        "DATE_FORMAT(m.start_time, '%Y-%m-%d %H:%i:%s') AS start_time, DATE_FORMAT(m.end_time, '%Y-%m-%d %H:%i:%s') AS end_time, " +
                        "m.budget_cent, m.labor_ratio, m.catering_ratio, m.meeting_status, m.audit_status, m.current_audit_node, " +
                        "DATE_FORMAT(m.last_submit_at, '%Y-%m-%dT%H:%i:%s') AS last_submit_at, m.last_reject_reason, m.overdue_days, m.risk_flags_json, m.is_frozen, m.freeze_reason, " +
                        "m.current_auditor_user_id, DATE_FORMAT(m.node_deadline_at, '%Y-%m-%dT%H:%i:%s') AS node_deadline_at, m.reject_count, DATE_FORMAT(m.last_action_at, '%Y-%m-%dT%H:%i:%s') AS last_action_at, " +
                        "DATE_FORMAT(m.status_changed_at, '%Y-%m-%dT%H:%i:%s') AS status_changed_at, m.status_changed_by, m.cancel_reason, m.postpone_reason, m.withdraw_reason, m.lock_version, DATE_FORMAT(m.lock_at, '%Y-%m-%dT%H:%i:%s') AS lock_at, m.locked_by, m.invoice_config_json, m.is_deleted " +
                        "FROM meeting m LEFT JOIN project p ON m.tenant_id=p.tenant_id AND m.project_id=p.id " +
                        whereSql +
                        "ORDER BY m.id DESC",
                ROW_MAPPER,
                tenantId()
        );
    }

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

    private Long safeUserId() {
        Long userId = AuthContext.userId();
        return userId == null ? 0L : userId;
    }

    @Override
    public void softDelete(Long id) {
        jdbcTemplate.update(
                "UPDATE meeting SET is_deleted=1, updated_by=?, updated_at=CURRENT_TIMESTAMP WHERE tenant_id=? AND id=?",
                safeUserId(),
                tenantId(),
                id
        );
    }
}
