package com.zhengmeng.ocrplatform.extract;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.Set;

@Service
public class OcrBusinessProfileService {
    private final OcrBusinessProfileRepository repository;
    private final OcrBusinessProfileFeedbackRepository feedbackRepository;
    private final ObjectMapper objectMapper;

    public OcrBusinessProfileService(OcrBusinessProfileRepository repository,
                                     OcrBusinessProfileFeedbackRepository feedbackRepository,
                                     ObjectMapper objectMapper) {
        this.repository = repository;
        this.feedbackRepository = feedbackRepository;
        this.objectMapper = objectMapper;
    }

    public Optional<OcrBusinessProfile> findProfile(String profileId) {
        if (!StringUtils.hasText(profileId)) {
            return Optional.empty();
        }
        return repository.findFirstByProfileIdAndEnabledTrueOrderByVersionNoDesc(profileId.trim())
                .map(this::parseProfile);
    }

    public Optional<FieldRule> findFieldRule(OcrBusinessProfile profile, String key, String label) {
        if (profile == null || profile.fields() == null) {
            return Optional.empty();
        }
        String normalizedKey = normalize(key);
        String normalizedLabel = normalize(label);
        return profile.fields().stream()
                .filter(rule -> normalize(rule.key()).equals(normalizedKey)
                        || normalize(rule.label()).equals(normalizedLabel)
                        || normalize(rule.key()).equals(normalizedLabel)
                        || normalize(rule.label()).equals(normalizedKey))
                .findFirst();
    }

    public List<BusinessProfileItem> listProfiles() {
        return repository.findByOrderByProfileIdAscVersionNoDesc().stream()
                .map(this::toItem)
                .toList();
    }

    public BusinessProfileDetail getProfile(String profileId) {
        OcrBusinessProfileEntity entity = repository.findFirstByProfileIdOrderByVersionNoDesc(profileId)
                .orElseThrow(() -> new IllegalArgumentException("业务识别配置不存在：" + profileId));
        return toDetail(entity);
    }

    public BusinessProfileDetail submitFeedback(String profileId, ProfileFeedbackRequest request) {
        OcrBusinessProfileEntity latest = repository.findFirstByProfileIdOrderByVersionNoDesc(profileId)
                .orElseThrow(() -> new IllegalArgumentException("业务识别配置不存在：" + profileId));
        OcrBusinessProfileFeedbackEntity feedback = new OcrBusinessProfileFeedbackEntity();
        feedback.setProfileId(profileId);
        feedback.setProfileVersionNo(latest.getVersionNo());
        feedback.setTaskId(request.taskId());
        feedback.setFieldKey(request.fieldKey());
        feedback.setFieldLabel(request.fieldLabel());
        feedback.setOldValue(request.oldValue());
        feedback.setCorrectValue(request.correctValue());
        feedback.setAliasText(request.aliasText());
        feedback.setPreferStrategy(request.preferStrategy());
        feedback.setMaxDistance(request.maxDistance());
        feedback.setCommentText(request.commentText());
        feedback.setCreatedAt(LocalDateTime.now());
        feedbackRepository.save(feedback);

        OcrBusinessProfileEntity draft = createDraftFromFeedback(latest, request);
        return toDetail(repository.save(draft));
    }

    public BusinessProfileDetail publishProfile(String profileId, Integer versionNo) {
        OcrBusinessProfileEntity target = repository.findByProfileIdAndVersionNo(profileId, versionNo)
                .orElseThrow(() -> new IllegalArgumentException("业务识别配置版本不存在：" + profileId + " v" + versionNo));
        List<OcrBusinessProfileEntity> versions = repository.findByProfileIdOrderByVersionNoDesc(profileId);
        LocalDateTime now = LocalDateTime.now();
        for (OcrBusinessProfileEntity version : versions) {
            boolean isTarget = version.getId().equals(target.getId());
            version.setEnabled(isTarget);
            version.setStatus(isTarget ? "ENABLED" : "ARCHIVED");
            version.setUpdatedAt(now);
        }
        repository.saveAll(versions);
        OcrBusinessProfileEntity published = repository.findByProfileIdAndVersionNo(profileId, versionNo).orElse(target);
        return toDetail(published);
    }

    private OcrBusinessProfileEntity createDraftFromFeedback(OcrBusinessProfileEntity source, ProfileFeedbackRequest request) {
        try {
            ObjectNode root = (ObjectNode) objectMapper.readTree(source.getProfileJson());
            root.put("version", source.getVersionNo() + 1);
            ArrayNode fields = ensureArray(root, "fields");
            ObjectNode field = findOrCreateField(fields, request.fieldKey(), request.fieldLabel());
            addAlias(field, request.aliasText());
            ObjectNode strategy = ensureObject(field, "valueStrategy");
            if (StringUtils.hasText(request.preferStrategy())) {
                strategy.put("prefer", request.preferStrategy().trim());
            }
            if (request.maxDistance() != null && request.maxDistance() > 0) {
                strategy.put("maxDistance", request.maxDistance());
            }
            if (StringUtils.hasText(request.correctValue()) && normalize(request.correctValue()).length() <= 2) {
                addAllowShortValue(strategy, request.correctValue());
            }
            OcrBusinessProfileEntity draft = new OcrBusinessProfileEntity();
            draft.setProfileId(source.getProfileId());
            draft.setProfileName(source.getProfileName());
            draft.setVersionNo(nextVersion(source.getProfileId()));
            root.put("version", draft.getVersionNo());
            draft.setProfileJson(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(root));
            draft.setStatus("DRAFT");
            draft.setEnabled(false);
            draft.setDescription("基于人工微调生成的草稿版本。");
            draft.setSourceTaskId(request.taskId());
            draft.setGeneratedBy("USER_FEEDBACK");
            draft.setCreatedAt(LocalDateTime.now());
            draft.setUpdatedAt(LocalDateTime.now());
            return draft;
        } catch (Exception ex) {
            throw new IllegalArgumentException("无法根据反馈生成配置草稿：" + ex.getMessage(), ex);
        }
    }

    private Integer nextVersion(String profileId) {
        return repository.findByProfileIdOrderByVersionNoDesc(profileId).stream()
                .map(OcrBusinessProfileEntity::getVersionNo)
                .max(Comparator.naturalOrder())
                .orElse(0) + 1;
    }

    private ArrayNode ensureArray(ObjectNode root, String fieldName) {
        JsonNode existing = root.path(fieldName);
        if (existing.isArray()) {
            return (ArrayNode) existing;
        }
        ArrayNode array = objectMapper.createArrayNode();
        root.set(fieldName, array);
        return array;
    }

    private ObjectNode ensureObject(ObjectNode root, String fieldName) {
        JsonNode existing = root.path(fieldName);
        if (existing.isObject()) {
            return (ObjectNode) existing;
        }
        ObjectNode object = objectMapper.createObjectNode();
        root.set(fieldName, object);
        return object;
    }

    private ObjectNode findOrCreateField(ArrayNode fields, String fieldKey, String fieldLabel) {
        String normalizedKey = normalize(fieldKey);
        for (JsonNode field : fields) {
            if (field.isObject() && normalize(field.path("key").asText()).equals(normalizedKey)) {
                return (ObjectNode) field;
            }
        }
        ObjectNode created = objectMapper.createObjectNode();
        created.put("key", fieldKey);
        created.put("label", defaultIfBlank(fieldLabel, fieldKey));
        created.set("aliases", objectMapper.createArrayNode());
        ObjectNode strategy = objectMapper.createObjectNode();
        strategy.put("type", "adjacent_text_block");
        strategy.put("prefer", "next");
        strategy.put("maxDistance", 2);
        strategy.set("allowShortValues", objectMapper.createArrayNode());
        created.set("valueStrategy", strategy);
        fields.add(created);
        return created;
    }

    private void addAlias(ObjectNode field, String alias) {
        if (!StringUtils.hasText(alias)) {
            return;
        }
        ArrayNode aliases = ensureArray(field, "aliases");
        Set<String> existing = new LinkedHashSet<>();
        aliases.forEach(item -> existing.add(normalize(item.asText())));
        if (!existing.contains(normalize(alias))) {
            aliases.add(alias.trim());
        }
    }

    private void addAllowShortValue(ObjectNode strategy, String value) {
        ArrayNode values = ensureArray(strategy, "allowShortValues");
        Set<String> existing = new LinkedHashSet<>();
        values.forEach(item -> existing.add(normalize(item.asText())));
        if (!existing.contains(normalize(value))) {
            values.add(value.trim());
        }
    }

    private BusinessProfileItem toItem(OcrBusinessProfileEntity entity) {
        return new BusinessProfileItem(
                entity.getProfileId(),
                entity.getProfileName(),
                entity.getVersionNo(),
                entity.getStatus(),
                entity.getEnabled(),
                entity.getDescription(),
                entity.getGeneratedBy(),
                entity.getSourceTaskId(),
                entity.getUpdatedAt()
        );
    }

    private BusinessProfileDetail toDetail(OcrBusinessProfileEntity entity) {
        return new BusinessProfileDetail(
                toItem(entity),
                entity.getProfileJson(),
                feedbackRepository.findByProfileIdOrderByCreatedAtDesc(entity.getProfileId()).stream()
                        .limit(20)
                        .map(feedback -> new ProfileFeedbackItem(
                                feedback.getId(),
                                feedback.getTaskId(),
                                feedback.getFieldKey(),
                                feedback.getFieldLabel(),
                                feedback.getOldValue(),
                                feedback.getCorrectValue(),
                                feedback.getAliasText(),
                                feedback.getPreferStrategy(),
                                feedback.getMaxDistance(),
                                feedback.getCommentText(),
                                feedback.getCreatedAt()
                        ))
                        .toList()
        );
    }

    private OcrBusinessProfile parseProfile(OcrBusinessProfileEntity entity) {
        try {
            OcrBusinessProfile profile = objectMapper.readValue(entity.getProfileJson(), OcrBusinessProfile.class);
            return new OcrBusinessProfile(
                    defaultIfBlank(profile.profileId(), entity.getProfileId()),
                    defaultIfBlank(profile.profileName(), entity.getProfileName()),
                    profile.version() == null ? entity.getVersionNo() : profile.version(),
                    profile.fields()
            );
        } catch (Exception ex) {
            throw new IllegalStateException("Failed to parse OCR business profile: " + entity.getProfileId(), ex);
        }
    }

    private String defaultIfBlank(String value, String defaultValue) {
        return StringUtils.hasText(value) ? value : defaultValue;
    }

    private String normalize(String value) {
        return value == null
                ? ""
                : value.toLowerCase(Locale.ROOT).replaceAll("[\\\\s:：;；,，_\\-（）()\\[\\]【】]+", "");
    }

    public record OcrBusinessProfile(
            String profileId,
            String profileName,
            Integer version,
            List<FieldRule> fields
    ) {
    }

    public record FieldRule(
            String key,
            String label,
            List<String> aliases,
            ValueStrategy valueStrategy
    ) {
        public List<String> matchTerms() {
            List<String> terms = new ArrayList<>();
            if (StringUtils.hasText(key)) {
                terms.add(key);
            }
            if (StringUtils.hasText(label)) {
                terms.add(label);
            }
            if (aliases != null) {
                aliases.stream().filter(StringUtils::hasText).forEach(terms::add);
            }
            return terms.stream().distinct().toList();
        }
    }

    public record ValueStrategy(
            String type,
            String prefer,
            Integer maxDistance,
            List<String> allowShortValues
    ) {
    }

    public record BusinessProfileItem(
            String profileId,
            String profileName,
            Integer versionNo,
            String status,
            Boolean enabled,
            String description,
            String generatedBy,
            String sourceTaskId,
            LocalDateTime updatedAt
    ) {
    }

    public record BusinessProfileDetail(
            BusinessProfileItem profile,
            String profileJson,
            List<ProfileFeedbackItem> feedbacks
    ) {
    }

    public record ProfileFeedbackRequest(
            String taskId,
            String fieldKey,
            String fieldLabel,
            String oldValue,
            String correctValue,
            String aliasText,
            String preferStrategy,
            Integer maxDistance,
            String commentText
    ) {
    }

    public record ProfileFeedbackItem(
            Long id,
            String taskId,
            String fieldKey,
            String fieldLabel,
            String oldValue,
            String correctValue,
            String aliasText,
            String preferStrategy,
            Integer maxDistance,
            String commentText,
            LocalDateTime createdAt
    ) {
    }
}
