package cn.reghao.tnb.search.esapp.log.service;

import cn.reghao.jutil.jdk.converter.DateTimeConverter;
import cn.reghao.jutil.jdk.io.TextFile;
import cn.reghao.jutil.jdk.serializer.JsonConverter;
import cn.reghao.jutil.jdk.string.SnowFlake;
import cn.reghao.jutil.jdk.web.log.NginxLog;
import cn.reghao.tnb.common.db.SelectOption;
import cn.reghao.tnb.search.esapp.config.AppProperties;
import cn.reghao.tnb.search.esapp.es.EsQuery;
import cn.reghao.tnb.search.esapp.es.SearchService;
import cn.reghao.tnb.search.esapp.log.geoip.GeoIpTool;
import cn.reghao.tnb.search.esapp.log.geoip.Location;
import cn.reghao.tnb.search.esapp.log.model.dto.DateTimeQuery;
import cn.reghao.tnb.search.esapp.log.model.dto.LogQuery;
import cn.reghao.tnb.search.esapp.log.model.vo.ChartData;
import cn.reghao.tnb.search.esapp.log.model.vo.ChartMap;
import cn.reghao.tnb.search.esapp.log.model.vo.GroupCount;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch._types.query_dsl.RangeQuery;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author reghao
 * @date 2023-11-08 10:08:02
 */
@Slf4j
@Service
public class NginxLogService {
    private final String indexName = "nginx_log";
    private final String dateTimeFormat = "yyyy-MM-dd HH:mm:ss";
    private final String timeZone = "+08:00";
    private final NginxLogDocument nginxLogDocument;
    private final SearchService searchService;
    private final AppProperties appProperties;
    private final SnowFlake idGenerator;
    private GeoIpTool geoIpTool;
    private String geoJson;

    public NginxLogService(NginxLogDocument nginxLogDocument, SearchService searchService,
                           AppProperties appProperties) {
        this.nginxLogDocument = nginxLogDocument;
        this.searchService = searchService;
        this.appProperties = appProperties;
        this.idGenerator = new SnowFlake(1, 1);
    }

    private GeoIpTool getGeoIpTool() {
        if (geoIpTool == null) {
            String geoipPath = String.format("%s/%s", appProperties.getBaseDir(), appProperties.getGeoipFilename());
            try {
                this.geoIpTool = new GeoIpTool(geoipPath);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        return geoIpTool;
    }

    private String getGeoJson() {
        if (geoJson == null) {
            String geojsonPath = String.format("%s/%s", appProperties.getBaseDir(), appProperties.getGeojsonFilename());
            geoJson = new TextFile().readFile(geojsonPath);
        }

        return geoJson;
    }


    public void processNginxLogs(List<NginxLog> list) {
        if (list.isEmpty()) {
            return;
        }

        list.forEach(nginxLog -> {
            // es 的日期时间格式 yyyy-MM-ddTHH:mm:ss+08:00
            String timeIso8601 = nginxLog.getTimeIso8601();
            String method = nginxLog.getRequestMethod();
            String url = nginxLog.getUrl();
            String methodUrl = String.format("%s %s", method, url);
            nginxLog.setMethodUrl(methodUrl);
            nginxLog.setId(idGenerator.nextId() + "");
        });
        nginxLogDocument.saveAll(indexName, list);
    }

    public List<SelectOption> getDateList() {
        String aggregateField = "timeIso8601";
        Query query = Query.of(q -> q.matchAll(m -> m));
        Map<String, Long> groupMap = searchService.aggregateByDay(indexName, aggregateField, query);

        Set<String> dateSet = groupMap.keySet();
        return dateSet.stream()
                .sorted(Comparator.reverseOrder())
                .map(dateStr -> new SelectOption(dateStr, dateStr))
                .collect(Collectors.toList());
    }

    public List<ChartData> getTopN(String dateStr) {
        int pageSize = 100;
        int pageNumber = 1;

        String startDate = dateStr;
        String endDate = dateStr;
        String start1 = String.format("%s 00:00:00", startDate);
        String end1 = String.format("%s 23:59:59", endDate);

        String dateField = "timeIso8601";
        Query dateQuery = RangeQuery.of(q -> q.field(dateField)
                .from(start1).to(end1).format(dateTimeFormat).timeZone(timeZone))._toQuery();
        Query termQuery = EsQuery.getTermQuery("status", "200");
        Query combinedQuery = Query.of(q -> q.bool(b -> b.filter(termQuery).filter(dateQuery)));

        String sortField = "requestTime";
        SortOrder sortOrder = SortOrder.Desc;
        List<NginxLog> list = searchService.searchByPage(indexName, pageSize, pageNumber, combinedQuery, sortField, sortOrder);
        List<ChartData> chartDataList = new ArrayList<>();
        list.forEach(nginxLog -> {
            String method = nginxLog.getRequestMethod();
            String url = nginxLog.getUrl();
            String name = String.format("%s %s", method, url);
            double requestTime = nginxLog.getRequestTime();
            chartDataList.add(new ChartData(name, requestTime));
        });
        return chartDataList;
    }

    public long countNginxLog(String dateStr) {
        String startDate = dateStr;
        String endDate = dateStr;
        String start1 = String.format("%s 00:00:00", startDate);
        String end1 = String.format("%s 23:59:59", endDate);
        String dateField = "timeIso8601";
        Query dateQuery = RangeQuery.of(q -> q.field(dateField)
                .from(start1).to(end1).format(dateTimeFormat).timeZone(timeZone))._toQuery();
        return searchService.count(indexName, dateQuery);
    }

    public List<Object> getChartData2(LogQuery logQuery) {
        String aggregateField = "timeIso8601";
        String dateField = "timeIso8601";
        String fieldName = "methodUrl.raw";

        String dateStr = logQuery.getDateStr();
        String fieldValue = logQuery.getUrl();

        String start = String.format("%s 00:00:00", dateStr);
        String end = String.format("%s 23:59:59", dateStr);
        Query dateQuery = RangeQuery.of(q -> q.field(dateField)
                .from(start).to(end).format(dateTimeFormat).timeZone(timeZone))._toQuery();
        Query combinedQuery;
        if (fieldValue == null || fieldValue.isBlank()) {
            combinedQuery = Query.of(q -> q.bool(b -> b.filter(dateQuery)));
        } else {
            Query termQuery = EsQuery.getTermQuery(fieldName, fieldValue);
            combinedQuery = Query.of(q -> q.bool(b -> b.filter(dateQuery).filter(termQuery)));
        }

        Map<String, Long> urlGroupMap = searchService.aggregateByQuery(indexName, aggregateField, combinedQuery);
        List<String> xList = new ArrayList<>();
        List<Long> yList = new ArrayList<>();
        urlGroupMap.forEach((key, value) -> {
            // x 轴显示时分秒
            xList.add(key);
            // y 轴显示访问次数
            yList.add(value);
        });

        List<Object> results = new ArrayList<>();
        results.add(xList.toArray());
        results.add(yList.toArray());
        return results;
    }

    /**
     * 按字段和时间范围聚合
     *
     * @param
     * @return
     * @date 2026-01-09 21:26:58
     */
    public List<GroupCount> getChartData3(LogQuery logQuery) {
        String dateStr = logQuery.getDateStr();
        String aggregateField0 = logQuery.getAggregateField();
        String aggregateField;
        if ("url".equals(aggregateField0)) {
            aggregateField = "methodUrl.raw";
        } else if ("statusCode".equals(aggregateField0)) {
            aggregateField = "status";
        } else if ("ip".equals(aggregateField0)) {
            aggregateField = "remoteAddr";
        } else if ("userAgent".equals(aggregateField0)) {
            aggregateField = "httpUserAgent.raw";
        } else {
            return Collections.emptyList();
        }

        String dateField = "timeIso8601";
        String start = String.format("%s 00:00:00", dateStr);
        String end = String.format("%s 23:59:59", dateStr);
        Query dateQuery = RangeQuery.of(q -> q.field(dateField)
                .from(start).to(end).format(dateTimeFormat).timeZone(timeZone))._toQuery();
        Map<String, Long> groupMap = searchService.aggregateByQuery(indexName, aggregateField, dateQuery);

        List<GroupCount> list = new ArrayList<>();
        groupMap.forEach((key, value) -> {
            list.add(new GroupCount(key, value));
        });
        list.sort(new Comparator<>() {
            @Override
            public int compare(GroupCount groupCount1, GroupCount groupCount2) {
                // 降序
                long result = groupCount2.getTotal() - groupCount1.getTotal();
                return (int) result;
            }
        });
        return list;
    }

    public ChartMap getChartMap(LogQuery logQuery) {
        String dateStr = logQuery.getDateStr();
        String aggregateField0 = logQuery.getAggregateField();
        String aggregateField;
        if ("ip".equals(aggregateField0)) {
            aggregateField = "remoteAddr";
        } else {
            return null;
        }

        int deep = 1;
        String dateField = "timeIso8601";
        String start = String.format("%s 00:00:00", dateStr);
        String end = String.format("%s 23:59:59", dateStr);
        Query dateQuery = RangeQuery.of(q -> q.field(dateField)
                .from(start).to(end).format(dateTimeFormat).timeZone(timeZone))._toQuery();
        Map<String, Long> groupMap1 = searchService.aggregateByQuery(indexName, aggregateField, dateQuery);

        Map<String, Long> map0 = new HashMap<>();
        Map<String, Map<String, Set<String>>> map = new HashMap<>();
        groupMap1.forEach((key, value) -> {
            String accessIp = key;
            Location location = getGeoIpTool().getLocation(accessIp);
            String country0 = location.getCountry();
            String[] array = country0.split("–");
            int len = array.length;
            String country = array[0];
            if (!"中国".equals(country) || len < 2) {
                return;
            }

            String province = array[1];
            Long count = map0.get(province);
            if (count == null) {
                count = 1L;
            } else {
                count += 1L;
            }
            map0.put(province, count);

            Map<String, Set<String>> map1 = map.computeIfAbsent(province, k -> new HashMap<>());
            if (len == 3) {
                String city = array[2];
                Set<String> set = map1.computeIfAbsent(city, k -> new HashSet<>());
            } else if (len == 4) {
                String city = array[2];
                Set<String> set = map1.computeIfAbsent(city, k -> new HashSet<>());
                String county = array[3];
                set.add(county);
            }
        });

        Map<Integer, String> geoMap = new HashMap<>();
        JsonObject jsonObject = JsonConverter.jsonToJsonElement(getGeoJson()).getAsJsonObject();
        for (JsonElement jsonElement : jsonObject.get("features").getAsJsonArray()) {
            JsonObject propertiesObject = jsonElement.getAsJsonObject().get("properties").getAsJsonObject();
            int adcode = propertiesObject.get("adcode").getAsInt();
            String name = propertiesObject.get("name").getAsString();
            if (deep == 3) {
                geoMap.put(adcode, name);
            } else if (deep == 2) {
                String adcodeStr = (""+adcode).substring(0, 4);
                geoMap.put(Integer.parseInt(adcodeStr), name);
            } else if (deep == 1) {
                String adcodeStr = (""+adcode).substring(0, 2);
                geoMap.put(Integer.parseInt(adcodeStr), name);
            }
        }

        List<String> areaNames = new ArrayList<>(geoMap.values());
        List<ChartData> chartDataList = new ArrayList<>();
        map0.forEach((key, value) -> {
            for (String areaName : areaNames) {
                if (areaName.startsWith(key)) {
                    chartDataList.add(new ChartData(areaName, value));
                }
            }
        });
        return new ChartMap(geoJson, chartDataList);
    }

    public List<Object> getChartData5(DateTimeQuery dateTimeQuery) {
        try {
            LocalDateTime start = DateTimeConverter.localDateTime2(dateTimeQuery.getStart());
            LocalDateTime end = DateTimeConverter.localDateTime2(dateTimeQuery.getEnd());
            Duration duration = Duration.between(start, end);
            long days = duration.toDays();
            long hours = duration.toHours();
            if (days < 0 || days > 7) {
                return Collections.emptyList();
            }
        } catch (Exception e) {
            // ignore
        }

        String aggregateField = "timeIso8601";
        String fieldName = "methodUrl.raw";

        String fieldValue = dateTimeQuery.getUrl();
        String start = dateTimeQuery.getStart();
        String end = dateTimeQuery.getEnd();
        String dateField = "timeIso8601";
        Query dateQuery = RangeQuery.of(q -> q.field(dateField)
                .from(start).to(end).format(dateTimeFormat).timeZone(timeZone))._toQuery();

        Query combinedQuery;
        if (fieldValue != null && !fieldValue.isBlank()) {
            Query termQuery = EsQuery.getTermQuery(fieldName, fieldValue);
            combinedQuery = Query.of(q -> q.bool(b -> b.filter(termQuery).filter(dateQuery)));
        } else {
            combinedQuery = Query.of(q -> q.bool(b -> b.filter(dateQuery)));
        }

        Map<String, Long> groupMap = searchService.aggregateByDay(indexName, aggregateField, combinedQuery);

        List<String> xList = new ArrayList<>();
        List<Long> yList = new ArrayList<>();
        groupMap.forEach((key, value) -> {
            // x 轴显示时分秒
            xList.add(key);
            // y 轴显示访问次数
            yList.add(value);
        });
        List<Object> results = new ArrayList<>();
        results.add(xList.toArray());
        results.add(yList.toArray());
        return results;
    }
}
