package com.codechecker.api.analyzer;

import com.codechecker.api.model.ApiChangeKind;
import com.codechecker.api.model.EndpointChangeReport;
import com.codechecker.api.model.EndpointSnapshot;
import com.codechecker.api.model.ParameterChange;

import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * 接口快照对比：路径 / 方法 / 增删 / 参数（拆分报告，互不混合类型）。
 */
public class EndpointDiffEngine {
    private final ParameterDiffEngine parameterDiffEngine;

    public EndpointDiffEngine(ParameterDiffEngine parameterDiffEngine) {
        this.parameterDiffEngine = parameterDiffEngine;
    }

    public List<EndpointChangeReport> diff(List<EndpointSnapshot> oldSnapshots,
                                           List<EndpointSnapshot> newSnapshots) throws IOException {
        Map<String, EndpointSnapshot> oldMap = indexByFingerprint(oldSnapshots);
        Map<String, EndpointSnapshot> newMap = indexByFingerprint(newSnapshots);
        List<EndpointChangeReport> reports = new ArrayList<>();

        for (String fp : newMap.keySet()) {
            if (!oldMap.containsKey(fp)) {
                EndpointSnapshot snap = newMap.get(fp);
                reports.add(new EndpointChangeReport(
                        ApiChangeKind.NEW_ENDPOINT,
                        snap.getHttpMethod(), null,
                        snap.getUri(), null,
                        snap.getSourceFile(), snap.getControllerClass(),
                        snap.getMethodDescription()));
            }
        }
        for (String fp : oldMap.keySet()) {
            if (!newMap.containsKey(fp)) {
                EndpointSnapshot snap = oldMap.get(fp);
                reports.add(new EndpointChangeReport(
                        ApiChangeKind.REMOVED_ENDPOINT,
                        snap.getHttpMethod(), null,
                        snap.getUri(), null,
                        snap.getSourceFile(), snap.getControllerClass(),
                        snap.getMethodDescription()));
            }
        }
        for (String fp : oldMap.keySet()) {
            if (!newMap.containsKey(fp)) {
                continue;
            }
            EndpointSnapshot oldSnap = oldMap.get(fp);
            EndpointSnapshot newSnap = newMap.get(fp);
            reports.addAll(diffMatched(oldSnap, newSnap));
        }
        return reports;
    }

    private List<EndpointChangeReport> diffMatched(EndpointSnapshot oldSnap,
                                                 EndpointSnapshot newSnap) throws IOException {
        List<EndpointChangeReport> reports = new ArrayList<>();
        boolean pathChanged = !oldSnap.getUri().equals(newSnap.getUri());
        boolean methodChanged = !oldSnap.getHttpMethod().equalsIgnoreCase(newSnap.getHttpMethod());

        if (pathChanged) {
            reports.add(new EndpointChangeReport(
                    ApiChangeKind.PATH_CHANGED,
                    newSnap.getHttpMethod(), null,
                    newSnap.getUri(), oldSnap.getUri(),
                    newSnap.getSourceFile(), newSnap.getControllerClass(),
                    preferDescription(newSnap, oldSnap)));
        }
        if (methodChanged) {
            reports.add(new EndpointChangeReport(
                    ApiChangeKind.METHOD_CHANGED,
                    newSnap.getHttpMethod(), oldSnap.getHttpMethod(),
                    newSnap.getUri(), null,
                    newSnap.getSourceFile(), newSnap.getControllerClass(),
                    preferDescription(newSnap, oldSnap)));
        }

        List<ParameterChange> paramChanges = parameterDiffEngine.diff(oldSnap, newSnap);
        if (!paramChanges.isEmpty()) {
            if (pathChanged || methodChanged) {
                EndpointChangeReport paramReport = new EndpointChangeReport(
                        ApiChangeKind.PARAM_CHANGED,
                        newSnap.getHttpMethod(), methodChanged ? oldSnap.getHttpMethod() : null,
                        newSnap.getUri(), pathChanged ? oldSnap.getUri() : null,
                        newSnap.getSourceFile(), newSnap.getControllerClass(),
                        preferDescription(newSnap, oldSnap));
                paramChanges.forEach(paramReport::addParameterChange);
                reports.add(paramReport);
            } else {
                EndpointChangeReport paramReport = new EndpointChangeReport(
                        ApiChangeKind.PARAM_CHANGED,
                        newSnap.getHttpMethod(), null,
                        newSnap.getUri(), null,
                        newSnap.getSourceFile(), newSnap.getControllerClass(),
                        preferDescription(newSnap, oldSnap));
                paramChanges.forEach(paramReport::addParameterChange);
                reports.add(paramReport);
            }
        }
        return reports;
    }

    private String preferDescription(EndpointSnapshot primary, EndpointSnapshot fallback) {
        if (primary != null && primary.getMethodDescription() != null
                && !primary.getMethodDescription().isBlank()) {
            return primary.getMethodDescription();
        }
        return fallback == null ? "" : fallback.getMethodDescription();
    }

    private Map<String, EndpointSnapshot> indexByFingerprint(List<EndpointSnapshot> snapshots) {
        Map<String, EndpointSnapshot> map = new LinkedHashMap<>();
        for (EndpointSnapshot snap : snapshots) {
            map.putIfAbsent(snap.getFingerprint(), snap);
        }
        return map;
    }
}
