using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;

using FMSAdmin.Data;
using FMSAdmin.Helpers;
using FMSAdmin.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using FMSAdmin.Models;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using System.Net.Http;
using System.Xml.Linq;
using System.Net;
using System.Data;
using System.IO;

namespace FMSAdmin.Services {
    public class SiteWeatherInfoService {
        private readonly ILogger<SiteWeatherInfoService> _logger;
        private readonly AppSettings _appSettings;
        private readonly FMSContext _context;

        public SiteWeatherInfoService(
            ILogger<SiteWeatherInfoService> logger,
            IOptions<AppSettings> appSettings,
            FMSContext context,
            StorageHelper storage
            ) {
            _logger = logger;
            _appSettings = appSettings.Value;
            _context = context;
        }

        public DataTable WeatherStat(int dayAgo = 0) {
            var query = $@"
SELECT Sido AS [시도], Sigun AS [구군], [00],[01],[02],[03],[04],[05],[06],[07],[08],[09],[10],[11],[12],[13],[14],[15],[16],[17],[18],[19],[21],[22],[23] FROM 
(
	SELECT weather.Sido, weather.Sigun, weather.CurrentTemp, DATEPART(HOUR, BaseTime) as Hour FROM SiteWeatherInfo weather
	LEFT JOIN CmSite site ON site.apiSido = weather.Sido AND site.ApiSigun = weather.Sigun
	WHERE CAST(weather.BaseTime AS DATE) = CAST(DATEADD(DAY, {dayAgo}, GETDATE()) AS DATE)
) T
 PIVOT (
	MAX(CurrentTemp) FOR Hour in ([00],[01],[02],[03],[04],[05],[06],[07],[08],[09],[10],[11],[12],[13],[14],[15],[16],[17],[18],[19],[21],[22],[23])
) AS PV
            ";

            var conn = _context.Database.GetDbConnection();
            conn.Open();
            var command = conn.CreateCommand();
            command.CommandText = query;
            var reader = command.ExecuteReader();

            DataTable dt = new DataTable();
            dt.Load(reader);
            return dt;
        }

        public string ReplaceSido(string sido) {
            string result = sido.ToString();
            result = result.Replace("특별시", "시");
            result = result.Replace("광역시", "시");
            result = result.Replace("특별자치도", "도");
            result = result.Replace("특별자치시", "시");
            return result;
        }

        public SiteWeatherInfo GetInfo(int siteId, string sido = null, string sigun = null, bool weather = true, bool dust = true) {

            SiteWeatherInfo weatherInfo = null;

            var site = _context.CmSite.Where(x => x.SiteId == siteId).FirstOrDefault();
            if (site != null) {
                if (string.IsNullOrEmpty(sido)) {
                    sido = site.ApiSido;
                }
                if (string.IsNullOrEmpty(sigun)) {
                    sigun = site.ApiSigun;
                }
            }
            if (string.IsNullOrEmpty(sido)) {
                sido = "서울시";
            }
            if (string.IsNullOrEmpty(sigun)) {
                sigun = "용산구";
            }
            sido = this.ReplaceSido(sido);

            var basetime = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, DateTime.Now.Hour, 0, 0);

            var query = from x in _context.SiteWeatherInfo
                        where x.Sido == sido && x.Sigun == sigun && x.BaseTime == basetime
                        orderby x.BaseTime descending
                        select x;
            if (query.Count() > 0) {
                weatherInfo = query.FirstOrDefault();
                if (weatherInfo.CurrentTemp != null && weatherInfo.Pm10 != null) {
                    return weatherInfo;
                }
            }

            var isNew = false;

            if (weatherInfo == null) {
                weatherInfo = new SiteWeatherInfo {
                    Sido = sido,
                    Sigun = sigun,
                    BaseTime = basetime,
                };
                isNew = true;
            }

            try {
                if (weatherInfo.CurrentTemp == null && weather) {
                    var shortTermNow = _apiShortTermNow(sido, sigun);
                    weatherInfo.ShortTermNowTime = shortTermNow.BaseTime;
                    if (shortTermNow.T1H != null) {
                        weatherInfo.CurrentTemp = Util.ToDouble(shortTermNow.T1H);
                    }
                    if (shortTermNow.RN1 != null) {
                        weatherInfo.Sky = Util.ToInt(shortTermNow.RN1);
                    }
                    if (shortTermNow.REH != null) {
                        weatherInfo.Humidity = Util.ToDouble(shortTermNow.REH);
                    }
                    if (shortTermNow.PTY != null) {
                        weatherInfo.RainType = Util.ToInt(shortTermNow.PTY);
                    }
                    if (shortTermNow.RN1 != null) {
                        weatherInfo.Rain = Util.ToDouble(shortTermNow.RN1);
                    }
                    if (shortTermNow.LGT != null) {
                        weatherInfo.Thunder = Util.ToInt(shortTermNow.LGT);
                    }
                    if (shortTermNow.VEC != null) {
                        weatherInfo.WindAngle = Util.ToInt(shortTermNow.VEC);
                    }
                    if (shortTermNow.WSD != null) {
                        weatherInfo.WindSpeed = Util.ToDouble(shortTermNow.WSD);
                    }
                }

                if (weatherInfo.HighTemp == null && weather) {
                    var forecast = _apiForecast(sido, sigun);
                    weatherInfo.ForecastTime = forecast.BaseTime;
                    if (forecast.TMX != null) {
                        weatherInfo.HighTemp = Util.ToDouble(forecast.TMX);
                    }
                    if (forecast.TMN != null) {
                        weatherInfo.LowTemp = Util.ToDouble(forecast.TMN);
                    }
                }
            } catch (ApiException ex) {
                _logger.LogWarning(ex.Message);
            } catch (Exception ex) {
                _logger.LogError(ex, "");
            }

            try {
                if (weatherInfo.Pm10 == null && dust) {
                    var dustHourly = _apiDustHourly(sido, sigun);
                    if (dustHourly != null) {
                        if (dustHourly.DataTime != null) {
                            weatherInfo.DustTime = _parseDate(dustHourly.DataTime);
                        }
                        if (dustHourly.Pm10Value != null) {
                            weatherInfo.Pm10 = Util.ToDouble(dustHourly.Pm10Value);
                        }
                        if (dustHourly.Pm25Value != null) {
                            weatherInfo.Pm25 = Util.ToDouble(dustHourly.Pm25Value);
                        }
                    }
                }
                if (weatherInfo.CurrentTemp < -200 || weatherInfo.CurrentTemp > 200) {
                    weatherInfo.CurrentTemp = null;
                    //throw new Exception("잘못된 데이터");
                }

                weatherInfo.IsNew = true;
                if (isNew) {
                    _context.SiteWeatherInfo.Add(weatherInfo);
                }
                _context.SaveChanges();
            } catch (ApiException ex) {
                _logger.LogWarning(ex.Message);
            } catch (Exception ex) {
                _logger.LogError(ex, "");
            }

            if (weatherInfo.CurrentTemp == null && weatherInfo.Pm10 == null) {
                var query2 = from x in _context.SiteWeatherInfo
                             where x.Sido == sido && x.Sigun == sigun && x.BaseTime <= basetime
                             orderby x.BaseTime descending
                             select x;
                if (query2.Count() > 0) {
                    weatherInfo = query.FirstOrDefault();
                    return weatherInfo;
                }
            }

            return weatherInfo;
        }

        private DateTime? _parseDate(string datestr) {
            var items = datestr.Split(" ");
            DateTime? date = null;
            try {
                if (items.Length >= 2 && items[1].StartsWith("24")) {
                    date = DateTime.Parse(items[0]);
                    date.Value.AddDays(1);
                } else {
                    date = DateTime.Parse(datestr);
                }
            } catch (Exception ex) {
                _logger.LogError(ex, "");
            }
            return date;
        }

        private DustApiItem _apiDustHourly(string sido, string sigun) {
            if (sido == null) sido = "";

            if (sido == "경상북도") {
                sido = "경북";
            } else if (sido == "경상남도") {
                sido = "경남";
            } else if (sido == "전라북도") {
                sido = "전북";
            } else if (sido == "전라남도") {
                sido = "전남";
            } else if (sido == "충청북도") {
                sido = "충북";
            } else if (sido == "충청남도") {
                sido = "충남";
            }
            if (sido[sido.Length - 1] == '시') {
                sido = sido.Substring(0, sido.Length - 1);
            }
            if (sido[sido.Length - 1] == '도') {
                sido = sido.Substring(0, sido.Length - 1);
            }

            var sidoName = WebUtility.UrlDecode(sido);

            //var forecast = $"http://openapi.airkorea.or.kr/openapi/services/rest/ArpltnInforInqireSvc/getCtprvnMesureSidoLIst?serviceKey={_appSettings.DustApiKey}&numOfRows=100&pageNo=1&sidoName={sidoName}&searchCondition=HOUR";
            var forecast = $"http://apis.data.go.kr/B552584/ArpltnStatsSvc/getCtprvnMesureSidoLIst?serviceKey={_appSettings.DustApiKey}&numOfRows=100&pageNo=1&sidoName={sidoName}&searchCondition=HOUR";

            string content = null;
            content = pullUrl(forecast);
            // _logger.LogInformation(content);

            XElement xml = XElement.Parse(content);

            var resultCode = xml.Descendants("resultCode").FirstOrDefault()?.Value;
            var resultMsg = xml.Descendants("resultMsg").FirstOrDefault()?.Value;

            if (resultCode != "00") {
                throw new ApiException($"apiDustHourly {resultCode}:{resultMsg}");
            }

            var items = from item in xml.Descendants("item")
                        select new DustApiItem {
                            DataTime = item.Element("dataTime")?.Value,
                            CityName = item.Element("cityName")?.Value,
                            So2Value = item.Element("so2Value")?.Value,
                            CoValue = item.Element("coValue")?.Value,
                            No2Value = item.Element("no2Value")?.Value,
                            Pm10Value = item.Element("pm10Value")?.Value,
                            Pm25Value = item.Element("pm25Value")?.Value,
                        };

            var result = items.Where(x => x.CityName == sigun).FirstOrDefault();
            if (result == null) {
                result = items.FirstOrDefault();
            }
            return result;
        }

        private WeatherForecast _apiForecast(string sido, string sigun, string gu = null) {
            var pos = _GetRegionPosition(sido, sigun, gu);
            var nx = pos.Nx;
            var ny = pos.Ny;

            var forenow = DateTime.Now.AddHours(-4);
            var forehour = forenow.Hour;
            if (forehour >= 2 && forehour < 5) {
                forehour = 2;
            }

            if (forehour < 2) {
                forenow = forenow.AddDays(-1);
                forehour = 23;
            } else {
                // 2시부터 23시까지 3시간 간격으로 발표
                forehour = (int)Math.Floor((float)(forehour - 2) / 3) * 3 + 2;
            }
            forenow = new DateTime(forenow.Year, forenow.Month, forenow.Day, forehour, 0, 0);
            var targetnow = forenow.AddHours(4);

            var foredate = forenow.ToString("yyyyMMdd");
            var foretime = forenow.ToString("HH00");
            var targettime = targetnow.ToString("HH00");

            //_logger.LogInformation("foredate:" + foredate);
            //_logger.LogInformation("foretime:" + foretime);
            //_logger.LogInformation("targettime:" + targettime);

            var forecast = $"http://apis.data.go.kr/1360000/VilageFcstInfoService/getVilageFcst?serviceKey={_appSettings.WeatherApiKey}&pageNo=1&numOfRows=100&dataType=XML&base_date={foredate}&base_time={foretime}&nx={nx}&ny={ny}";
            //_logger.LogInformation(forecast);

            string content = null;
            content = pullUrl(forecast);
            //_logger.LogInformation(content);

            XElement xml = XElement.Parse(content);

            var resultCode = xml.Descendants("resultCode").FirstOrDefault()?.Value;
            var resultMsg = xml.Descendants("resultMsg").FirstOrDefault()?.Value;

            if (resultCode != "00") {
                throw new ApiException($"apiForecast {resultCode}:{resultMsg}");
            }

            var items = from item in xml.Descendants("item")
                        select new WeatherApiItem {
                            BaseDate = item.Element("baseDate")?.Value,
                            BaseTime = item.Element("baseTime")?.Value,
                            Category = item.Element("category")?.Value,
                            FcstValue = item.Element("fcstValue")?.Value,
                            FcstTime = item.Element("fcstTime")?.Value,
                        };

            var list = items.ToList();
            //_logger.LogInformation(list.Dump());
            var result = new WeatherForecast();
            result.BaseTime = targetnow;
            result.ForecastTime = forenow;
            result.POP = list.Where(x => x.Category == "POP").Select(x => x.FcstValue).FirstOrDefault();
            result.PTY = list.Where(x => x.Category == "PTY").Select(x => x.FcstValue).FirstOrDefault();
            result.R06 = list.Where(x => x.Category == "R06").Select(x => x.FcstValue).FirstOrDefault();
            result.REH = list.Where(x => x.Category == "REH").Select(x => x.FcstValue).FirstOrDefault();
            result.S06 = list.Where(x => x.Category == "S06").Select(x => x.FcstValue).FirstOrDefault();
            result.SKY = list.Where(x => x.Category == "SKY").Select(x => x.FcstValue).FirstOrDefault();
            result.T3H = list.Where(x => x.Category == "T3H").Select(x => x.FcstValue).FirstOrDefault();
            result.TMN = list.Where(x => x.Category == "TMN").Select(x => x.FcstValue).FirstOrDefault();
            result.TMX = list.Where(x => x.Category == "TMX").Select(x => x.FcstValue).FirstOrDefault();
            result.UUU = list.Where(x => x.Category == "UUU").Select(x => x.FcstValue).FirstOrDefault();
            result.VVV = list.Where(x => x.Category == "VVV").Select(x => x.FcstValue).FirstOrDefault();
            result.VEC = list.Where(x => x.Category == "VEC").Select(x => x.FcstValue).FirstOrDefault();
            result.WSD = list.Where(x => x.Category == "WSD").Select(x => x.FcstValue).FirstOrDefault();

            return result;
        }

        private WeatherShortTermNow _apiShortTermNow(string sido, string sigun, string gu = null) {
            var pos = _GetRegionPosition(sido, sigun, gu);
            var nx = pos.Nx;
            var ny = pos.Ny;

            var nowminute = DateTime.Now.Minute;
            var shortnow = DateTime.Now;
            if (nowminute <= 40) {
                shortnow = shortnow.AddHours(-1);
            }
            shortnow = new DateTime(shortnow.Year, shortnow.Month, shortnow.Day, shortnow.Hour, 0, 0);

            var nowdate = shortnow.ToString("yyyyMMdd");
            var nowtime = shortnow.ToString("HH00");

            var shortNow = $"http://apis.data.go.kr/1360000/VilageFcstInfoService/getUltraSrtNcst?serviceKey={_appSettings.WeatherApiKey}&pageNo=1&numOfRows=100&dataType=XML&base_date={nowdate}&base_time={nowtime}&nx={nx}&ny={ny}";
            //_logger.LogInformation(shortNow);

            string content = null;
            content = pullUrl(shortNow);
            //_logger.LogInformation(content);

            XElement xml = XElement.Parse(content);

            var resultCode = xml.Descendants("resultCode").FirstOrDefault()?.Value;
            var resultMsg = xml.Descendants("resultMsg").FirstOrDefault()?.Value;

            if (resultCode != "00") {
                throw new ApiException($"apiShortTermNow {resultCode}:{resultMsg}");
            }

            var items = from item in xml.Descendants("item")
                        select new WeatherApiItem {
                            BaseDate = item.Element("baseDate")?.Value,
                            BaseTime = item.Element("baseTime")?.Value,
                            Category = item.Element("category")?.Value,
                            ObsrValue = item.Element("obsrValue")?.Value,
                        };


            var list = items.ToList();
            //_logger.LogInformation(list.Dump());
            var result = new WeatherShortTermNow();
            result.BaseTime = shortnow;
            result.T1H = list.Where(x => x.Category == "T1H").Select(x => x.ObsrValue).FirstOrDefault();
            result.RN1 = list.Where(x => x.Category == "RN1").Select(x => x.ObsrValue).FirstOrDefault();
            result.SKY = list.Where(x => x.Category == "SKY").Select(x => x.ObsrValue).FirstOrDefault();
            result.UUU = list.Where(x => x.Category == "UUU").Select(x => x.ObsrValue).FirstOrDefault();
            result.VVV = list.Where(x => x.Category == "VVV").Select(x => x.ObsrValue).FirstOrDefault();
            result.REH = list.Where(x => x.Category == "REH").Select(x => x.ObsrValue).FirstOrDefault();
            result.PTY = list.Where(x => x.Category == "PTY").Select(x => x.ObsrValue).FirstOrDefault();
            result.LGT = list.Where(x => x.Category == "LGT").Select(x => x.ObsrValue).FirstOrDefault();
            result.VEC = list.Where(x => x.Category == "VEC").Select(x => x.ObsrValue).FirstOrDefault();
            result.WSD = list.Where(x => x.Category == "WSD").Select(x => x.ObsrValue).FirstOrDefault();

            return result;
        }

        private WeatherRegionNxNy _GetRegionPosition(string sido, string sigun, string gu = null) {
            sido = sido.Trim();
            if (sido == "서울시") {
                sido = "서울특별시";
            } else if (sido == "제주도") {
                sido = "제주특별자치도";
            } else if (sido == "인천시") {
                sido = "인천광역시";
            } else if (sido == "광주시") {
                sido = "광주광역시";
            } else if (sido == "대구시") {
                sido = "대구광역시";
            } else if (sido == "대전시") {
                sido = "대전광역시";
            } else if (sido == "부산시") {
                sido = "부산광역시";
            } else if (sido == "울산시") {
                sido = "울산광역시";
            } else if (sido == "세종시") {
                sido = "세종특별자치시";
            }
            var query = _context.WeatherRegionNxNy.Where(x => x.Sido == sido && x.Sigun == sigun);
            WeatherRegionNxNy find = null;
            if (!string.IsNullOrEmpty(gu)) {
                if (query.Where(x => x.Gu == gu).Count() > 0) {
                    find = query.Where(x => x.Gu == gu).FirstOrDefault();
                } else {
                    find = query.FirstOrDefault();
                }
            } else {
                find = query.FirstOrDefault();
            }
            return find;
        }

        private string pullUrl(string url) {
            // Create a request for the URL. 		
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
            request.Timeout = 5000;
            // If required by the server, set the credentials.
            request.Credentials = CredentialCache.DefaultCredentials;
            // Get the response.
            HttpWebResponse response = (HttpWebResponse)request.GetResponse();
            // Display the status.
            // Get the stream containing content returned by the server.
            Stream dataStream = response.GetResponseStream();
            // Open the stream using a StreamReader for easy access.
            StreamReader reader = new StreamReader(dataStream);
            // Read the content.
            string responseFromServer = reader.ReadToEnd();
            // Display the content.
            reader.Close();
            dataStream.Close();
            response.Close();
            return responseFromServer;
        }
    }

    public class DustApiItem {
        public string DataTime { get; set; }
        public string CityName { get; set; }
        public string So2Value { get; set; }
        public string CoValue { get; set; }
        public string O3Value { get; set; }
        public string No2Value { get; set; }
        public string Pm10Value { get; set; }
        public string Pm25Value { get; set; }
    }

    public class WeatherApiItem {
        public string BaseDate { get; set; }
        public string BaseTime { get; set; }
        public string Category { get; set; }
        public string ObsrValue { get; set; }
        public string FcstValue { get; set; }
        public string FcstTime { get; set; }
    }

    public class WeatherForecast {
        public WeatherForecast() {
        }
        public DateTime BaseTime;
        public DateTime ForecastTime;

        [Display(Name = "강수확률")]
        public string POP { get; set; }
        [Display(Name = "강수형태")]
        public string PTY { get; set; }
        [Display(Name = "6시간 강수량")]
        public string R06 { get; set; }
        [Display(Name = "습도")]
        public string REH { get; set; }
        [Display(Name = "6시간 신적설")]
        public string S06 { get; set; }
        [Display(Name = "하늘상태")]
        public string SKY { get; set; }
        [Display(Name = "3시간 기온")]
        public string T3H { get; set; }
        [Display(Name = "아침 최저기온")]
        public string TMN { get; set; }
        [Display(Name = "낮 최고기온")]
        public string TMX { get; set; }
        [Display(Name = "풍속(동서)")]
        public string UUU { get; set; }
        [Display(Name = "풍속(남북)")]
        public string VVV { get; set; }
        [Display(Name = "풍향")]
        public string VEC { get; set; }
        [Display(Name = "풍속")]
        public string WSD { get; set; }
    }

    public class WeatherShortTermNow {
        public WeatherShortTermNow() {
        }
        public DateTime BaseTime;
        [Display(Name = "기온")]
        public string T1H { get; set; }
        [Display(Name = "1시간 강수량")]
        public string RN1 { get; set; }
        [Display(Name = "하늘상태")]
        public string SKY { get; set; }
        [Display(Name = "동서바람성분")]
        public string UUU { get; set; }
        [Display(Name = "남북바람성분")]
        public string VVV { get; set; }
        [Display(Name = "습도")]
        public string REH { get; set; }
        [Display(Name = "강수형태")]
        public string PTY { get; set; }
        [Display(Name = "낙뢰")]
        public string LGT { get; set; }
        [Display(Name = "풍향")]
        public string VEC { get; set; }
        [Display(Name = "풍속")]
        public string WSD { get; set; }
    }
}