package incheon.sgp.common.service.impl;

import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.concurrent.TimeUnit;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.CacheControl;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;

import incheon.cmm.ahm.flight.vo.G2fFlightVO;
import incheon.sgp.common.mapper.SgpCmnMapper;
import incheon.sgp.common.service.SgpCommonService;
import incheon.sgp.common.vo.SgpCmnVO;

@Service
public class SgpCommonServiceImpl implements SgpCommonService {
	private final SgpCmnMapper sgpCmnMapper;

	public SgpCommonServiceImpl(SgpCmnMapper sgpCmnMapper) {
		this.sgpCmnMapper = sgpCmnMapper;
	}

	@Value("${sgp.weather.api.base-url:https://apihub.kma.go.kr/api/typ01/cgi-bin/url/nph-dfs_odam_grd}")
	private String WEATHER_BASE_URL;

	@Value("${sgp.weather.api.key:#{null}}")
	private String WEATHER_API_KEY;

	private final Map<String, float[]> weatherValues = new LinkedHashMap<>();
	// T1H(기온), UUU(동서바람성분), VVV(남북바람성분), VEC(풍향), WSD(풍속), PTY(강수형태), RN1(1시간 강수량),
	// REH(상대습도)
	private final String[] weatherCategorys = { "T1H", "UUU", "VVV", "VEC", "WSD", "PTY", "RN1", "REH" };
	private final int weatherGridXLength = 149;
	private final int weatherGridYLength = 253;
	private final Duration weatherUpdateOffset = Duration.ofMinutes(5);
	private final Duration weatherUpdateStep = Duration.ofMinutes(10);
	private LocalDateTime weatherExpires = next(LocalDateTime.now().minus(weatherUpdateStep), weatherUpdateStep,
			weatherUpdateOffset); // 초기에는 만료로 설정
	@Autowired
	private RestTemplate restTemplate;
	@Autowired
	private ObjectMapper objectMapper;
	
	private final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmm");

	@Override
	public ResponseEntity<?> getWeather(String nx, String ny) {
		// GRID가 0이아닌 1부터 시작함.
		int x = Integer.parseInt(nx) - 1;
		int y = Integer.parseInt(ny) - 1;
		if (x < 0 || x >= weatherGridXLength || y < 0 || y >= weatherGridYLength) {
			return ResponseEntity.badRequest().body("기상정보 조회 영역을 벗어났습니다.");
		}
		LocalDateTime now = LocalDateTime.now();
		if (weatherExpires.isBefore(now)) { // API 요청이 있을때만 갱신한다.
			synchronized (weatherValues) {
				if (weatherExpires.isBefore(now)) {
					weatherUpdate();
				}
			}
		}
		try {
			JsonNode result = objectMapper.readTree(
					"{\"response\":{\"header\":{\"resultCode\":\"00\",\"resultMsg\":\"NORMAL_SERVICE\"},\"body\":{\"dataType\":\"JSON\",\"items\":{\"item\":[]},\"pageNo\":1,\"numOfRows\":8,\"totalCount\":8}}}");
			ArrayNode items = (ArrayNode) result.path("response").path("body").path("items").path("item");
			String tmfc = weatherExpires.minus(weatherUpdateStep).minus(weatherUpdateOffset).format(FORMATTER);
			for (int i = 0; i < weatherCategorys.length; i++) {
				float value = weatherValues.get(weatherCategorys[i])[weatherGridXLength * y + x];
				if (value == -99 || value == -999)
					continue; // 정보없음
				Map<String, Object> item = new LinkedHashMap<>();
				item.put("baseDate", tmfc.substring(0, 8));
				item.put("baseTime", tmfc.substring(8, 10) + "00");
				item.put("category", weatherCategorys[i]);
				item.put("fcstDate", tmfc.substring(0, 8));
				item.put("fcstTime", tmfc.substring(8, 12));
				item.put("fcstValue", String.valueOf(value));
				item.put("nx", nx);
				item.put("ny", ny);
				items.add(objectMapper.valueToTree(item));
			}
				return ResponseEntity.ok()
						.contentType(MediaType.APPLICATION_JSON)
						.cacheControl(CacheControl.maxAge(ChronoUnit.SECONDS.between(now, weatherExpires), TimeUnit.SECONDS).cachePublic())
					.body(result);
		} catch (JsonProcessingException e) {
			return ResponseEntity.internalServerError().body(e.getMessage());
		}
	}

	void weatherUpdate() {
		LocalDateTime tmfcTime = next(LocalDateTime.now().minus(weatherUpdateStep).minus(weatherUpdateOffset),
				weatherUpdateStep, Duration.ofMinutes(0));
		String tmfc = tmfcTime.format(DateTimeFormatter.ofPattern("yyyyMMddHHmm"));
		try {
			int weatherValueLength = weatherGridXLength * weatherGridYLength;
			for (String category : weatherCategorys) {

				String source;
				if (StringUtils.hasText(WEATHER_BASE_URL) && StringUtils.hasText(WEATHER_API_KEY)) {
					UriComponentsBuilder weatherUri = UriComponentsBuilder.fromHttpUrl(this.WEATHER_BASE_URL)
							.queryParam("tmfc", tmfc).queryParam("vars", category)
							.queryParam("authKey", this.WEATHER_API_KEY);
					source = restTemplate.getForObject(weatherUri.toUriString(), String.class);
				} else { // 개발시에는 더미 데이터 사용
					ClassPathResource resource = new ClassPathResource(category + ".txt", SgpCommonService.class);
					try (InputStream is = resource.getInputStream()) {
						source = StreamUtils.copyToString(is, StandardCharsets.US_ASCII);
					}
				}
				float[] values = new float[weatherValueLength];
				try (Scanner scanner = new Scanner(source).useDelimiter(", *")) {
					for (int i = 0; i < weatherValueLength; i++) {
						values[i] = Float.parseFloat(scanner.next());
					}
					weatherValues.put(category, values);
				}
			}
			weatherExpires = tmfcTime.plus(weatherUpdateStep).plus(weatherUpdateOffset);
		} catch (Exception e) {
			throw new RuntimeException("weatherUpdate error", e);
		}

	}

	public static LocalDateTime next(LocalDateTime now, Duration step, Duration offset) {
		long s = step.getSeconds(), n = now.toLocalTime().toSecondOfDay() - offset.getSeconds();
		return now.plusSeconds(Math.floorMod(s - Math.floorMod(n, s), s)).withNano(0);
	}

	@Override
	public SgpCmnVO selectUrbTrgtParcelByPoint(Map<String, Object> param) throws Exception {
		return sgpCmnMapper.selectUrbTrgtParcelByPoint(param);
	}

	@Override
	public List<G2fFlightVO> getFlightList() {
		return sgpCmnMapper.getFlightList();
	}



}
