package com.writeoff.module.finance.service;

import com.writeoff.common.api.PageResult;
import com.writeoff.common.exception.BusinessException;
import com.writeoff.common.exception.ErrorCodes;
import com.writeoff.module.finance.dto.ConfirmPaymentRequest;
import com.writeoff.module.finance.dto.FinanceLockRequest;
import com.writeoff.module.finance.dto.FinanceReconciliationRequest;
import com.writeoff.module.finance.dto.UpsertFinanceMeetingBillRequest;
import com.writeoff.module.finance.model.FinanceMeetingBillInfo;
import com.writeoff.module.finance.model.Payment;
import com.writeoff.module.finance.model.PaymentStatus;
import com.writeoff.module.finance.repository.PaymentRepository;
import com.writeoff.module.meeting.model.Meeting;
import com.writeoff.module.meeting.model.MeetingAuditStatus;
import com.writeoff.module.meeting.service.MeetingService;
import com.writeoff.module.system.service.DataPermissionService;
import com.writeoff.security.AuthContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;

import java.util.LinkedHashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

@Service
public class FinanceService {
    private final PaymentRepository paymentRepository;
    private final MeetingService meetingService;
    private final DataPermissionService dataPermissionService;
    private final JdbcTemplate jdbcTemplate;
    private final Map<String, Long> paymentIdempotency = new ConcurrentHashMap<>();
    private static final RowMapper<FinanceMeetingBillInfo> BILL_ROW_MAPPER = (rs, n) -> new FinanceMeetingBillInfo(
            rs.getLong("id"),
            rs.getLong("project_id"),
            rs.getLong("meeting_id"),
            rs.getLong("venue_amount_cent"),
            rs.getLong("build_amount_cent"),
            rs.getLong("hotel_amount_cent"),
            rs.getLong("catering_amount_cent"),
            rs.getLong("local_traffic_amount_cent"),
            rs.getLong("long_distance_traffic_amount_cent"),
            rs.getLong("material_amount_cent"),
            rs.getLong("design_amount_cent"),
            rs.getLong("labor_payable_amount_cent"),
            rs.getLong("labor_actual_amount_cent"),
            rs.getLong("finance_review_fee_cent"),
            rs.getLong("management_fee_cent"),
            rs.getLong("tax_fee_cent"),
            rs.getString("custom_fee_json"),
            rs.getLong("paid_amount_cent"),
            rs.getLong("unpaid_amount_cent"),
            rs.getString("reconciliation_result"),
            rs.getLong("reconciliation_diff_amount_cent"),
            rs.getString("reconciliation_diff_reason"),
            rs.getString("settlement_no"),
            rs.getString("status"),
            rs.getString("updated_at")
    );

    @Autowired
    public FinanceService(PaymentRepository paymentRepository, MeetingService meetingService, DataPermissionService dataPermissionService, JdbcTemplate jdbcTemplate) {
        this.paymentRepository = paymentRepository;
        this.meetingService = meetingService;
        this.dataPermissionService = dataPermissionService;
        this.jdbcTemplate = jdbcTemplate;
    }

    public FinanceService(PaymentRepository paymentRepository, MeetingService meetingService, JdbcTemplate jdbcTemplate) {
        this(paymentRepository, meetingService, null, jdbcTemplate);
    }

    public FinanceService(PaymentRepository paymentRepository, MeetingService meetingService) {
        this(paymentRepository, meetingService, null, null);
    }

    public PageResult<Payment> listProjects() {
        List<Payment> list = paymentRepository.findAll();
        if (dataPermissionService != null) {
            DataPermissionService.DataScope scope = dataPermissionService.resolveCurrentUserScope();
            Set<Long> meetingIds = list.stream().map(Payment::getMeetingId).collect(Collectors.toCollection(HashSet::new));
            Map<Long, Long> meetingCreatorMap = dataPermissionService.listMeetingCreators(meetingIds);
            Map<Long, Long> meetingProjectMap = dataPermissionService.listMeetingProjectIds(meetingIds);
            Set<Long> projectIds = new HashSet<>(meetingProjectMap.values());
            Map<Long, Long> projectCreatorMap = dataPermissionService.listProjectCreators(projectIds);
            list = list.stream()
                    .filter(payment -> {
                        Long projectId = payment.getProjectId();
                        if (projectId == null) {
                            projectId = meetingProjectMap.get(payment.getMeetingId());
                        }
                        Long meetingCreatedBy = meetingCreatorMap.get(payment.getMeetingId());
                        Long projectCreatedBy = projectId == null ? null : projectCreatorMap.get(projectId);
                        return dataPermissionService.canAccessMeeting(payment.getMeetingId(), projectId, meetingCreatedBy, projectCreatedBy, scope);
                    })
                    .collect(Collectors.toList());
        }
        return new PageResult<>(list, list.size(), 1, 20);
    }

    public Map<String, Object> confirmPayment(ConfirmPaymentRequest request) {
        if (paymentIdempotency.containsKey(request.getIdempotencyKey())) {
            throw new BusinessException(ErrorCodes.IDEMPOTENCY_CONFLICT, "请求幂等冲突");
        }
        paymentIdempotency.put(request.getIdempotencyKey(), request.getMeetingId());

        Meeting meeting = meetingService.getById(request.getMeetingId());
        if (meeting.getAuditStatus() != MeetingAuditStatus.APPROVED) {
            throw new BusinessException(ErrorCodes.PAYMENT_STATE_INVALID, "当前会议未终审通过，不能支付确认");
        }
        if (paymentRepository.isProjectLocked(request.getProjectId())) {
            throw new BusinessException(ErrorCodes.PAYMENT_LOCKED, "项目已锁账，暂不允许支付确认");
        }

        Payment payment = paymentRepository.save(new Payment(
                null,
                request.getProjectId(),
                request.getMeetingId(),
                request.getAmountCent(),
                PaymentStatus.CONFIRMED
        ));

        Map<String, Object> result = new LinkedHashMap<>();
        result.put("paymentId", payment.getId());
        result.put("paymentStatus", payment.getStatus().name());
        return result;
    }

    public Map<String, Object> exportLedger() {
        if (dataPermissionService != null && !dataPermissionService.canExportCurrentUser()) {
            throw new BusinessException(ErrorCodes.NO_PERMISSION, "当前账号无导出权限");
        }
        List<Payment> list = listProjects().getList();
        Map<String, Object> result = new LinkedHashMap<>();
        result.put("total", list.size());
        result.put("records", list);
        return result;
    }

    public Map<String, Object> reconciliation(FinanceReconciliationRequest request) {
        checkIdempotency(request.getIdempotencyKey(), request.getProjectId());
        return paymentRepository.createReconciliation(request.getProjectId(), request.getExpectedAmountCent(), 0L);
    }

    public Map<String, Object> lock(FinanceLockRequest request) {
        checkIdempotency(request.getIdempotencyKey(), request.getProjectId());
        paymentRepository.lockProject(request.getProjectId(), request.getReason(), 0L);
        Map<String, Object> result = new LinkedHashMap<>();
        result.put("projectId", request.getProjectId());
        result.put("lockStatus", "LOCKED");
        return result;
    }

    public Map<String, Object> unlock(FinanceLockRequest request) {
        checkIdempotency(request.getIdempotencyKey(), request.getProjectId());
        paymentRepository.unlockProject(request.getProjectId(), request.getReason(), 0L);
        Map<String, Object> result = new LinkedHashMap<>();
        result.put("projectId", request.getProjectId());
        result.put("lockStatus", "UNLOCKED");
        return result;
    }

    public Map<String, Object> reconciliationList(Long projectId) {
        Map<String, Object> result = new LinkedHashMap<>();
        result.put("reconciliation", paymentRepository.listReconciliations());
        result.put("lockLogs", paymentRepository.listLockLogs(projectId));
        return result;
    }

    public PageResult<FinanceMeetingBillInfo> listMeetingBills(Long projectId) {
        List<FinanceMeetingBillInfo> list = jdbcTemplate.query(
                "SELECT id, project_id, meeting_id, venue_amount_cent, build_amount_cent, hotel_amount_cent, catering_amount_cent, local_traffic_amount_cent, " +
                        "long_distance_traffic_amount_cent, material_amount_cent, design_amount_cent, labor_payable_amount_cent, labor_actual_amount_cent, finance_review_fee_cent, " +
                        "management_fee_cent, tax_fee_cent, custom_fee_json, paid_amount_cent, unpaid_amount_cent, reconciliation_result, reconciliation_diff_amount_cent, " +
                        "reconciliation_diff_reason, settlement_no, status, DATE_FORMAT(updated_at, '%Y-%m-%d %H:%i:%s') AS updated_at " +
                        "FROM finance_meeting_bill WHERE tenant_id=? AND is_deleted=0 AND (? IS NULL OR project_id=?) ORDER BY id DESC",
                BILL_ROW_MAPPER,
                tenantId(),
                projectId,
                projectId
        );
        return new PageResult<>(list, list.size(), 1, 50);
    }

    public FinanceMeetingBillInfo upsertMeetingBill(UpsertFinanceMeetingBillRequest request) {
        jdbcTemplate.update(
                "INSERT INTO finance_meeting_bill (tenant_id, project_id, meeting_id, venue_amount_cent, build_amount_cent, hotel_amount_cent, catering_amount_cent, local_traffic_amount_cent, " +
                        "long_distance_traffic_amount_cent, material_amount_cent, design_amount_cent, labor_payable_amount_cent, labor_actual_amount_cent, finance_review_fee_cent, management_fee_cent, " +
                        "tax_fee_cent, custom_fee_json, paid_amount_cent, unpaid_amount_cent, reconciliation_result, reconciliation_diff_amount_cent, reconciliation_diff_reason, settlement_no, status, created_by, updated_by) " +
                        "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) " +
                        "ON DUPLICATE KEY UPDATE project_id=VALUES(project_id), venue_amount_cent=VALUES(venue_amount_cent), build_amount_cent=VALUES(build_amount_cent), " +
                        "hotel_amount_cent=VALUES(hotel_amount_cent), catering_amount_cent=VALUES(catering_amount_cent), local_traffic_amount_cent=VALUES(local_traffic_amount_cent), " +
                        "long_distance_traffic_amount_cent=VALUES(long_distance_traffic_amount_cent), material_amount_cent=VALUES(material_amount_cent), design_amount_cent=VALUES(design_amount_cent), " +
                        "labor_payable_amount_cent=VALUES(labor_payable_amount_cent), labor_actual_amount_cent=VALUES(labor_actual_amount_cent), finance_review_fee_cent=VALUES(finance_review_fee_cent), " +
                        "management_fee_cent=VALUES(management_fee_cent), tax_fee_cent=VALUES(tax_fee_cent), custom_fee_json=VALUES(custom_fee_json), paid_amount_cent=VALUES(paid_amount_cent), " +
                        "unpaid_amount_cent=VALUES(unpaid_amount_cent), reconciliation_result=VALUES(reconciliation_result), reconciliation_diff_amount_cent=VALUES(reconciliation_diff_amount_cent), " +
                        "reconciliation_diff_reason=VALUES(reconciliation_diff_reason), settlement_no=VALUES(settlement_no), status=VALUES(status), updated_at=CURRENT_TIMESTAMP, updated_by=VALUES(updated_by)",
                tenantId(),
                request.getProjectId(),
                request.getMeetingId(),
                nvl(request.getVenueAmountCent()),
                nvl(request.getBuildAmountCent()),
                nvl(request.getHotelAmountCent()),
                nvl(request.getCateringAmountCent()),
                nvl(request.getLocalTrafficAmountCent()),
                nvl(request.getLongDistanceTrafficAmountCent()),
                nvl(request.getMaterialAmountCent()),
                nvl(request.getDesignAmountCent()),
                nvl(request.getLaborPayableAmountCent()),
                nvl(request.getLaborActualAmountCent()),
                nvl(request.getFinanceReviewFeeCent()),
                nvl(request.getManagementFeeCent()),
                nvl(request.getTaxFeeCent()),
                request.getCustomFeeJson(),
                nvl(request.getPaidAmountCent()),
                nvl(request.getUnpaidAmountCent()),
                request.getReconciliationResult(),
                nvl(request.getReconciliationDiffAmountCent()),
                request.getReconciliationDiffReason(),
                request.getSettlementNo(),
                request.getStatus() == null || request.getStatus().trim().isEmpty() ? "DRAFT" : request.getStatus().trim().toUpperCase(),
                safeUserId(),
                safeUserId()
        );

        List<FinanceMeetingBillInfo> list = jdbcTemplate.query(
                "SELECT id, project_id, meeting_id, venue_amount_cent, build_amount_cent, hotel_amount_cent, catering_amount_cent, local_traffic_amount_cent, " +
                        "long_distance_traffic_amount_cent, material_amount_cent, design_amount_cent, labor_payable_amount_cent, labor_actual_amount_cent, finance_review_fee_cent, " +
                        "management_fee_cent, tax_fee_cent, custom_fee_json, paid_amount_cent, unpaid_amount_cent, reconciliation_result, reconciliation_diff_amount_cent, " +
                        "reconciliation_diff_reason, settlement_no, status, DATE_FORMAT(updated_at, '%Y-%m-%d %H:%i:%s') AS updated_at " +
                        "FROM finance_meeting_bill WHERE tenant_id=? AND meeting_id=? AND is_deleted=0 LIMIT 1",
                BILL_ROW_MAPPER,
                tenantId(),
                request.getMeetingId()
        );
        if (list.isEmpty()) {
            throw new BusinessException(ErrorCodes.RESOURCE_NOT_FOUND, "会议账单不存在");
        }
        return list.get(0);
    }

    private void checkIdempotency(String key, Long marker) {
        if (paymentIdempotency.containsKey(key)) {
            throw new BusinessException(ErrorCodes.IDEMPOTENCY_CONFLICT, "请求幂等冲突");
        }
        paymentIdempotency.put(key, marker == null ? 0L : marker);
    }

    private long nvl(Long v) {
        return v == null ? 0L : v;
    }

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

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