package com.writeoff.module.meeting.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.writeoff.common.exception.BusinessException;
import com.writeoff.common.exception.ErrorCodes;
import com.writeoff.module.meeting.dto.BindMeetingExpertsRequest;
import com.writeoff.module.meeting.model.MeetingExpertBinding;
import com.writeoff.module.system.service.BizChangeLogService;
import com.writeoff.security.AuthContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

@Service
public class MeetingExpertBindingService {
    private static final Long PLATFORM_TENANT_ID = 0L;
    private static final String EXPERT_LIST_MODULE_CODE = "EXPERT_LIST";
    private static final String UNBIND_BLOCKED_MESSAGE = "该专家在会议资料-专家列表中已有已保存资料，请先删除相关信息后再解绑";

    private final JdbcTemplate jdbcTemplate;
    private final MeetingService meetingService;
    private final BizChangeLogService bizChangeLogService;
    private final ObjectMapper objectMapper = new ObjectMapper();

    private static final RowMapper<MeetingExpertBinding> ROW_MAPPER = (rs, n) -> new MeetingExpertBinding(
            rs.getLong("id"),
            rs.getLong("meeting_id"),
            rs.getLong("expert_id"),
            rs.getString("expert_name"),
            maskPhone(rs.getString("phone")),
            rs.getString("title"),
            rs.getString("organization")
    );

    public MeetingExpertBindingService(JdbcTemplate jdbcTemplate, MeetingService meetingService, BizChangeLogService bizChangeLogService) {
        this.jdbcTemplate = jdbcTemplate;
        this.meetingService = meetingService;
        this.bizChangeLogService = bizChangeLogService;
    }

    public List<MeetingExpertBinding> listByMeetingId(Long meetingId) {
        meetingService.getById(meetingId);
        return jdbcTemplate.query(
                "SELECT id, meeting_id, expert_id, expert_name, phone, title, organization " +
                        "FROM meeting_expert_binding WHERE tenant_id=? AND meeting_id=? ORDER BY id DESC",
                ROW_MAPPER,
                tenantId(),
                meetingId
        );
    }

    @Transactional(rollbackFor = Exception.class)
    public List<MeetingExpertBinding> bind(Long meetingId, BindMeetingExpertsRequest request) {
        meetingService.getById(meetingId);
        List<MeetingExpertBinding> beforeBindings = listByMeetingId(meetingId);
        List<Long> rawIds = request.getExpertIds() == null ? new ArrayList<Long>() : request.getExpertIds();
        Set<Long> idSet = new HashSet<Long>();
        for (Long id : rawIds) {
            if (id != null && id > 0) {
                idSet.add(id);
            }
        }
        List<Long> expertIds = new ArrayList<Long>(idSet);
        validateRemovedExpertCanUnbind(meetingId, beforeBindings, expertIds);

        jdbcTemplate.update(
                "DELETE FROM meeting_expert_binding WHERE tenant_id=? AND meeting_id=?",
                tenantId(),
                meetingId
        );
        if (expertIds.isEmpty()) {
            return listByMeetingId(meetingId);
        }

        StringBuilder sql = new StringBuilder("SELECT id, expert_name, phone, title, organization FROM expert WHERE tenant_id=? AND is_deleted=0 AND id IN (");
        for (int i = 0; i < expertIds.size(); i++) {
            if (i > 0) {
                sql.append(",");
            }
            sql.append("?");
        }
        sql.append(")");

        List<Object> args = new ArrayList<Object>();
        args.add(PLATFORM_TENANT_ID);
        args.addAll(expertIds);
        List<Map<String, Object>> experts = jdbcTemplate.queryForList(sql.toString(), args.toArray());
        Map<Long, Map<String, Object>> expertMap = new LinkedHashMap<Long, Map<String, Object>>();
        for (Map<String, Object> item : experts) {
            Object idVal = item.get("id");
            if (idVal instanceof Number) {
                expertMap.put(((Number) idVal).longValue(), item);
            }
        }
        if (expertMap.isEmpty()) {
            throw new BusinessException(ErrorCodes.VALIDATION_ERROR, "未匹配到平台专家");
        }
        logBindingDiff(meetingId, beforeBindings, expertIds, expertMap);
        Long operator = safeUserId();
        for (Long expertId : expertIds) {
            Map<String, Object> expert = expertMap.get(expertId);
            if (expert == null) {
                continue;
            }
            jdbcTemplate.update(
                    "INSERT INTO meeting_expert_binding (tenant_id, meeting_id, expert_id, expert_name, phone, title, organization, created_by) " +
                            "VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
                    tenantId(),
                    meetingId,
                    expertId,
                    String.valueOf(expert.get("expert_name") == null ? "" : expert.get("expert_name")),
                    String.valueOf(expert.get("phone") == null ? "" : expert.get("phone")),
                    String.valueOf(expert.get("title") == null ? "" : expert.get("title")),
                    String.valueOf(expert.get("organization") == null ? "" : expert.get("organization")),
                    operator
            );
        }
        return listByMeetingId(meetingId);
    }

    @Transactional(rollbackFor = Exception.class)
    public List<MeetingExpertBinding> unbindOne(Long meetingId, Long expertId) {
        meetingService.getById(meetingId);
        validateExpertMaterialCanUnbind(meetingId, expertId);
        MeetingExpertBinding beforeBinding = findBinding(meetingId, expertId);
        jdbcTemplate.update(
                "DELETE FROM meeting_expert_binding WHERE tenant_id=? AND meeting_id=? AND expert_id=?",
                tenantId(),
                meetingId,
                expertId
        );
        if (beforeBinding != null && bizChangeLogService != null) {
            bizChangeLogService.logRelationRemove("MEETING", meetingId, "MEETING_EXPERT_UNBIND", "expertBinding", "绑定专家", beforeBinding.getExpertId(), beforeBinding.getExpertName(), null, null);
        }
        return listByMeetingId(meetingId);
    }

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

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

    private static String maskPhone(String phone) {
        if (phone == null || phone.trim().isEmpty()) {
            return phone;
        }
        String value = phone.trim();
        if (value.length() <= 7) {
            return value;
        }
        return value.substring(0, 3) + "****" + value.substring(value.length() - 4);
    }

    private void validateExpertMaterialCanUnbind(Long meetingId, Long expertId) {
        long id = expertId == null ? 0L : expertId;
        if (id <= 0L) {
            return;
        }
        String contentJson = loadExpertListContentJson(meetingId);
        if (!hasSavedExpertMaterial(contentJson, id)) {
            return;
        }
        throw new BusinessException(
                ErrorCodes.INVALID_STATE,
                UNBIND_BLOCKED_MESSAGE
        );
    }

    private void validateRemovedExpertCanUnbind(Long meetingId, List<MeetingExpertBinding> beforeBindings, List<Long> nextExpertIds) {
        if (beforeBindings == null || beforeBindings.isEmpty()) {
            return;
        }
        Set<Long> nextIdSet = new HashSet<Long>(nextExpertIds == null ? new ArrayList<Long>() : nextExpertIds);
        for (MeetingExpertBinding binding : beforeBindings) {
            if (binding == null || binding.getExpertId() == null) {
                continue;
            }
            Long expertId = binding.getExpertId();
            if (nextIdSet.contains(expertId)) {
                continue;
            }
            validateExpertMaterialCanUnbind(meetingId, expertId);
        }
    }

    private String loadExpertListContentJson(Long meetingId) {
        List<String> rows = jdbcTemplate.query(
                "SELECT content_json FROM meeting_material WHERE tenant_id=? AND meeting_id=? AND module_code=? AND is_deleted=0 LIMIT 1",
                (rs, n) -> rs.getString("content_json"),
                tenantId(),
                meetingId,
                EXPERT_LIST_MODULE_CODE
        );
        if (rows.isEmpty()) {
            return "";
        }
        return rows.get(0) == null ? "" : rows.get(0);
    }

    private boolean hasSavedExpertMaterial(String contentJson, long expertId) {
        if (contentJson == null || contentJson.trim().isEmpty()) {
            return false;
        }
        try {
            Map<String, Object> root = objectMapper.readValue(contentJson, new TypeReference<Map<String, Object>>() {});
            return hasExpertOnsitePhoto(root, expertId)
                    || hasExpertOnsiteSummary(root, expertId)
                    || hasExpertLaborDetail(root, expertId)
                    || hasExpertInvoiceDetail(root, expertId);
        } catch (Exception ignore) {
            return false;
        }
    }

    private boolean hasExpertOnsitePhoto(Map<String, Object> root, long expertId) {
        Map<String, Object> onsitePhoto = asMap(root.get("onsitePhoto"));
        Object photosObj = onsitePhoto.get("photos");
        if (!(photosObj instanceof Collection)) {
            return false;
        }
        for (Object obj : (Collection<?>) photosObj) {
            if (!(obj instanceof Map)) {
                continue;
            }
            Map<?, ?> photo = (Map<?, ?>) obj;
            if (!matchesExpert(photo.get("expertId"), expertId)) {
                continue;
            }
            if (hasText(photo.get("ossKey")) || hasText(firstNonNull(photo.get("fileName"), photo.get("name")))) {
                return true;
            }
        }
        return false;
    }

    private boolean hasExpertOnsiteSummary(Map<String, Object> root, long expertId) {
        Map<String, Object> onsitePhoto = asMap(root.get("onsitePhoto"));
        Object expertSummariesObj = onsitePhoto.get("expertSummaries");
        if (expertSummariesObj instanceof Collection) {
            for (Object obj : (Collection<?>) expertSummariesObj) {
                if (!(obj instanceof Map)) {
                    continue;
                }
                Map<?, ?> summary = (Map<?, ?>) obj;
                if (matchesExpert(summary.get("expertId"), expertId) && hasText(summary.get("summary"))) {
                    return true;
                }
            }
        }
        Object globalSummary = onsitePhoto.get("summary");
        return hasText(globalSummary);
    }

    private boolean hasExpertLaborDetail(Map<String, Object> root, long expertId) {
        Map<String, Object> laborProtocol = asMap(root.get("laborProtocol"));
        Object detailsObj = laborProtocol.get("details");
        if (!(detailsObj instanceof Collection)) {
            return false;
        }
        for (Object obj : (Collection<?>) detailsObj) {
            if (!(obj instanceof Map)) {
                continue;
            }
            Map<?, ?> detail = (Map<?, ?>) obj;
            if (!matchesExpert(detail.get("expertId"), expertId)) {
                continue;
            }
            if (hasExpertLaborContent(detail)) {
                return true;
            }
        }
        return false;
    }

    private boolean hasExpertInvoiceDetail(Map<String, Object> root, long expertId) {
        Map<String, Object> invoiceDetail = asMap(root.get("invoiceDetail"));
        Object invoicesObj = firstNonNull(invoiceDetail.get("invoices"), invoiceDetail.get("items"));
        if (!(invoicesObj instanceof Collection)) {
            return false;
        }
        for (Object obj : (Collection<?>) invoicesObj) {
            if (!(obj instanceof Map)) {
                continue;
            }
            Map<?, ?> invoice = (Map<?, ?>) obj;
            if (!matchesExpert(invoice.get("expertId"), expertId)) {
                continue;
            }
            if (hasText(invoice.get("invoiceNo"))
                    || hasText(invoice.get("vendorName"))
                    || hasText(invoice.get("remark"))
                    || hasPositiveNumber(firstNonNull(invoice.get("invoiceAmountCent"), invoice.get("amountCent")))
                    || hasInvoiceFiles(invoice)) {
                return true;
            }
        }
        return false;
    }

    private boolean hasExpertLaborContent(Map<?, ?> detail) {
        Map<String, Object> protocolFile = asMap(detail.get("protocolFile"));
        Map<String, Object> invoiceFile = asMap(detail.get("invoiceFile"));
        Object invoiceFilesObj = detail.get("invoiceFiles");
        return hasText(protocolFile.get("ossKey"))
                || hasText(firstNonNull(protocolFile.get("fileName"), protocolFile.get("name")))
                || hasText(invoiceFile.get("ossKey"))
                || hasText(firstNonNull(invoiceFile.get("fileName"), invoiceFile.get("name")))
                || hasInvoiceFiles(invoiceFilesObj)
                || hasPositiveNumber(detail.get("amountCent"))
                || hasText(detail.get("remark"))
                || hasNonIdleInvoiceOcr(detail.get("invoiceOcr"));
    }

    private boolean hasInvoiceFiles(Map<?, ?> invoice) {
        return hasInvoiceFiles(firstNonNull(invoice.get("files"), invoice.get("file")));
    }

    private boolean hasInvoiceFiles(Object filesObj) {
        if (filesObj instanceof Collection) {
            for (Object obj : (Collection<?>) filesObj) {
                if (!(obj instanceof Map)) {
                    continue;
                }
                Map<?, ?> file = (Map<?, ?>) obj;
                if (hasText(file.get("ossKey")) || hasText(firstNonNull(file.get("fileName"), file.get("name")))) {
                    return true;
                }
            }
            return false;
        }
        if (filesObj instanceof Map) {
            Map<?, ?> file = (Map<?, ?>) filesObj;
            return hasText(file.get("ossKey")) || hasText(firstNonNull(file.get("fileName"), file.get("name")));
        }
        return false;
    }

    private boolean hasNonIdleInvoiceOcr(Object invoiceOcrObj) {
        if (!(invoiceOcrObj instanceof Map)) {
            return false;
        }
        Map<?, ?> invoiceOcr = (Map<?, ?>) invoiceOcrObj;
        String status = stringValue(invoiceOcr.get("status"));
        if (status != null && !status.isEmpty() && !"idle".equalsIgnoreCase(status)) {
            return true;
        }
        return hasText(invoiceOcr.get("message")) || !asMap(invoiceOcr.get("normalized")).isEmpty();
    }

    private boolean matchesExpert(Object value, long expertId) {
        if (value == null) {
            return false;
        }
        if (value instanceof Number) {
            return ((Number) value).longValue() == expertId;
        }
        try {
            return Long.parseLong(String.valueOf(value).trim()) == expertId;
        } catch (Exception ignore) {
            return false;
        }
    }

    private boolean hasPositiveNumber(Object value) {
        if (value == null) {
            return false;
        }
        try {
            return Long.parseLong(String.valueOf(value).trim()) > 0L;
        } catch (Exception ignore) {
            return false;
        }
    }

    private boolean hasText(Object value) {
        return value != null && !String.valueOf(value).trim().isEmpty();
    }

    private String stringValue(Object value) {
        return value == null ? null : String.valueOf(value).trim();
    }

    private Object firstNonNull(Object first, Object second) {
        return first != null ? first : second;
    }

    private void logBindingDiff(Long meetingId,
                                List<MeetingExpertBinding> beforeBindings,
                                List<Long> afterExpertIds,
                                Map<Long, Map<String, Object>> expertMap) {
        if (bizChangeLogService == null) {
            return;
        }
        Map<Long, String> beforeMap = new LinkedHashMap<Long, String>();
        for (MeetingExpertBinding item : beforeBindings) {
            if (item != null && item.getExpertId() != null) {
                beforeMap.put(item.getExpertId(), item.getExpertName());
            }
        }
        Map<Long, String> afterMap = new LinkedHashMap<Long, String>();
        for (Long expertId : afterExpertIds) {
            Map<String, Object> expert = expertMap.get(expertId);
            if (expert == null) {
                continue;
            }
            afterMap.put(expertId, String.valueOf(expert.get("expert_name") == null ? "" : expert.get("expert_name")).trim());
        }
        Set<Long> allIds = new HashSet<Long>();
        allIds.addAll(beforeMap.keySet());
        allIds.addAll(afterMap.keySet());
        String batchId = bizChangeLogService.newBatchId();
        for (Long expertId : allIds) {
            boolean beforeExists = beforeMap.containsKey(expertId);
            boolean afterExists = afterMap.containsKey(expertId);
            String expertName = beforeExists ? beforeMap.get(expertId) : afterMap.get(expertId);
            if (!beforeExists && afterExists) {
                bizChangeLogService.logRelationAdd("MEETING", meetingId, "MEETING_EXPERT_BIND_ADD", "expertBinding", "绑定专家", expertId, expertName, batchId, null);
            } else if (beforeExists && !afterExists) {
                bizChangeLogService.logRelationRemove("MEETING", meetingId, "MEETING_EXPERT_BIND_REMOVE", "expertBinding", "绑定专家", expertId, expertName, batchId, null);
            }
        }
    }

    private MeetingExpertBinding findBinding(Long meetingId, Long expertId) {
        if (expertId == null || expertId <= 0L) {
            return null;
        }
        List<MeetingExpertBinding> list = jdbcTemplate.query(
                "SELECT id, meeting_id, expert_id, expert_name, phone, title, organization " +
                        "FROM meeting_expert_binding WHERE tenant_id=? AND meeting_id=? AND expert_id=? LIMIT 1",
                ROW_MAPPER,
                tenantId(),
                meetingId,
                expertId
        );
        return list.isEmpty() ? null : list.get(0);
    }

    @SuppressWarnings("unchecked")
    private Map<String, Object> asMap(Object value) {
        if (value instanceof Map) {
            return (Map<String, Object>) value;
        }
        return new LinkedHashMap<String, Object>();
    }
}
