package com.zcloud.personnel.positioning.command;

import com.zcloud.personnel.positioning.persistence.dataobject.OrgLocalCorpDO;
import com.zcloud.personnel.positioning.persistence.dataobject.OrgLocalDepartmentDO;
import com.zcloud.personnel.positioning.persistence.dataobject.OrgMatchSnapshotDO;
import com.zcloud.personnel.positioning.persistence.repository.OrgMatchRepository;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/** Resolves a FindS organization snapshot to the local organization master data. */
@Component
@RequiredArgsConstructor
public class FindsCorpMappingService {
    private static final String DEFAULT_CONFIG_CODE = "FINDS";

    private final OrgMatchRepository orgMatchRepository;

    public CorpMatch resolve(String sourceConfigCode, String findsCorpinfoId, String findsCorpinfoName,
                             String findsDepartmentName, String creditCode) {
        String configCode = firstText(sourceConfigCode, DEFAULT_CONFIG_CODE);
        String sourceCorpId = clean(findsCorpinfoId);
        String corpName = clean(findsCorpinfoName);
        String departmentName = clean(findsDepartmentName);
        String sourceCreditCode = clean(creditCode);
        if (!StringUtils.hasText(sourceCorpId) && !StringUtils.hasText(corpName)
                && !StringUtils.hasText(departmentName) && !StringUtils.hasText(sourceCreditCode)) {
            return null;
        }

        OrgMatchSnapshotDO saved = StringUtils.hasText(corpName) || StringUtils.hasText(departmentName)
                ? orgMatchRepository.findMatched(configCode, emptyIfBlank(corpName), emptyIfBlank(departmentName))
                : null;
        if (saved != null) {
            orgMatchRepository.touch(configCode, emptyIfBlank(corpName),
                    emptyIfBlank(departmentName), LocalDateTime.now());
            return fromSaved(saved);
        }

        MatchAttempt attempt = queryCorp("CORP_ID", "corpinfoId", validCorpinfoId(sourceCorpId));
        if (!hasCandidates(attempt)) {
            attempt = queryCorp("CORP_CODE", "creditCode", sourceCreditCode);
        }
        if (!hasCandidates(attempt)) {
            attempt = queryCorp("CORP_NAME", "corpinfoName", corpName);
        }
        if (!hasCandidates(attempt)) {
            attempt = queryDepartment(departmentName);
        }
        if (!hasCandidates(attempt) && StringUtils.hasText(corpName) && corpName.length() >= 3) {
            attempt = queryCorp("CORP_NAME_SUFFIX", "corpinfoNameSuffix", escapeLike(corpName));
        }

        CorpMatch resolved = attempt == null ? null : attempt.uniqueMatch();
        String status = attempt == null || attempt.candidates.isEmpty()
                ? "UNMATCHED" : (resolved == null ? "AMBIGUOUS" : "MATCHED");
        if (StringUtils.hasText(corpName) || StringUtils.hasText(departmentName)) {
            save(configCode, sourceCorpId, corpName, departmentName, resolved, status,
                    attempt == null ? null : attempt.matchType);
        }
        return resolved;
    }

    private MatchAttempt queryCorp(String matchType, String key, String value) {
        if (!StringUtils.hasText(value)) {
            return null;
        }
        Map<String, Object> params = new LinkedHashMap<>();
        params.put(key, value);
        List<CorpMatch> matches = new ArrayList<>();
        for (OrgLocalCorpDO row : orgMatchRepository.listCorpCandidates(params)) {
            matches.add(new CorpMatch(row.getCorpinfoId(), row.getCorpinfoName(),
                    null, null, matchType));
        }
        return distinct(matchType, matches);
    }

    private MatchAttempt queryDepartment(String departmentName) {
        if (!StringUtils.hasText(departmentName)) {
            return null;
        }
        List<CorpMatch> matches = new ArrayList<>();
        for (OrgLocalDepartmentDO row : orgMatchRepository.listDepartmentCandidates(departmentName)) {
            matches.add(new CorpMatch(row.getRootCorpinfoId(), row.getRootCorpinfoName(),
                    row.getDepartmentId(), row.getDepartmentName(), "DEPARTMENT_NAME"));
        }
        return distinct("DEPARTMENT_NAME", matches);
    }

    private MatchAttempt distinct(String matchType, List<CorpMatch> rows) {
        Map<String, CorpMatch> distinct = new LinkedHashMap<>();
        for (CorpMatch row : rows) {
            if (row != null && StringUtils.hasText(row.corpinfoId)) {
                distinct.putIfAbsent(row.corpinfoId, row);
            }
        }
        return new MatchAttempt(matchType, new ArrayList<>(distinct.values()));
    }

    private void save(String configCode, String sourceCorpId, String corpName,
                      String departmentName, CorpMatch match, String status, String matchType) {
        Map<String, Object> params = new LinkedHashMap<>();
        params.put("sourceConfigCode", configCode);
        params.put("findsCorpinfoId", sourceCorpId);
        params.put("findsCorpinfoName", emptyIfBlank(corpName));
        params.put("findsDepartmentName", emptyIfBlank(departmentName));
        params.put("localCorpinfoId", match == null ? null : match.corpinfoId);
        params.put("localCorpinfoName", match == null ? null : match.corpinfoName);
        params.put("localDepartmentId", match == null ? null : match.departmentId);
        params.put("localDepartmentName", match == null ? null : match.departmentName);
        params.put("matchStatus", status);
        params.put("matchType", matchType);
        params.put("syncTime", LocalDateTime.now());
        orgMatchRepository.upsert(params);
    }

    private CorpMatch fromSaved(OrgMatchSnapshotDO saved) {
        return new CorpMatch(saved.getLocalCorpinfoId(), saved.getLocalCorpinfoName(),
                saved.getLocalDepartmentId(), saved.getLocalDepartmentName(), saved.getMatchType());
    }

    private boolean hasCandidates(MatchAttempt attempt) {
        return attempt != null && !attempt.candidates.isEmpty();
    }

    private String validCorpinfoId(String value) {
        return StringUtils.hasText(value) && !"0".equals(value) ? value : null;
    }

    private String clean(String value) {
        return StringUtils.hasText(value) ? value.trim() : null;
    }

    private String firstText(String first, String second) {
        return StringUtils.hasText(first) ? first.trim() : second;
    }

    private String emptyIfBlank(String value) {
        return value == null ? "" : value;
    }

    private String escapeLike(String value) {
        return value.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_");
    }

    @Getter
    public static class CorpMatch {
        private final String corpinfoId;
        private final String corpinfoName;
        private final Long departmentId;
        private final String departmentName;
        private final String matchType;

        public CorpMatch(String corpinfoId, String corpinfoName, Long departmentId,
                         String departmentName, String matchType) {
            this.corpinfoId = corpinfoId;
            this.corpinfoName = corpinfoName;
            this.departmentId = departmentId;
            this.departmentName = departmentName;
            this.matchType = matchType;
        }
    }

    private static class MatchAttempt {
        private final String matchType;
        private final List<CorpMatch> candidates;

        private MatchAttempt(String matchType, List<CorpMatch> candidates) {
            this.matchType = matchType;
            this.candidates = candidates;
        }

        private CorpMatch uniqueMatch() {
            return candidates.size() == 1 ? candidates.get(0) : null;
        }
    }
}
