using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
using System.Globalization;
using System.Net;
using System.Text.Json;
using ZTest0049Pwrs.Web.Models;

namespace ZTest0049Pwrs.Web.Controllers
{
    [Route("api/weathers")]
    [ApiController]
    public class WeatherController : ControllerBase
    {
        private const string ApiKey = "fa8b3df74d4042b9aa7135114252304";
        private const string City = "Moscow";
        private const string CacheKey = "WeatherData_Moscow";

        private static readonly string[] ShortDays = ["Вс", "Пн", "Вт", "Ср", "Чт", "Пт", "Сб"];
        private static readonly DayOfWeek[] Weekends = [DayOfWeek.Saturday, DayOfWeek.Sunday];

        private readonly IHttpClientFactory _httpClientFactory;
        private readonly IMemoryCache _memoryCache;

        public WeatherController(IHttpClientFactory httpClientFactory, IMemoryCache memoryCache)
        {
            _httpClientFactory = httpClientFactory;
            _memoryCache = memoryCache;
        }

        [HttpGet]
        public async Task<IActionResult> GetAsync()
        {
            try
            {
                if (_memoryCache.TryGetValue(CacheKey, out WeatherResponse? cachedResult))
                    return Ok(cachedResult);

                var coordinates = await GetCoordinatesAsync(City);

                var result = new WeatherResponse
                {
                    // Получаем текущую погоду
                    Current = await GetCurrentWeatherAsync(coordinates.Lat, coordinates.Lon)
                };

                // Заполняем прогноз
                await FillForecastWeatherAsync(result, coordinates.Lat, coordinates.Lon);

                // Сохраняем в кэш на 5 минут
                var cacheOptions = new MemoryCacheEntryOptions()
                    .SetAbsoluteExpiration(TimeSpan.FromMinutes(5))
                    .SetPriority(CacheItemPriority.Normal);

                _memoryCache.Set(CacheKey, result, cacheOptions);

                return Ok(result);
            }
            catch (Exception ex)
            {
                if (ex.Message.Contains("HttpClient.Timeout"))
                    return UnprocessableEntity($"Ошибка получения погодной информации: Погодный сервис не отвечает");

                return UnprocessableEntity($"Ошибка получения погодной информации: {ex.Message}");
            }
        }

        private async Task<Coordinates> GetCoordinatesAsync(string city)
        {
            // Для получения координат используем API геокодирования
            string url = $"http://api.weatherapi.com/v1/current.json?key={ApiKey}&q={city}";

            using var client = _httpClientFactory.CreateClient();
            client.Timeout = TimeSpan.FromSeconds(2);
            string response = await client.GetStringAsync(url);
            var weatherData = JsonSerializer.Deserialize<WeatherApiResponse>(response);

            if (weatherData?.Location == null)
                throw new Exception($"Ошибка при получении координат: координаты не найдены");

            return new Coordinates { Lat = weatherData.Location.Lat, Lon = weatherData.Location.Lon };
        }

        private async Task<WeatherCurrent> GetCurrentWeatherAsync(double lat, double lon)
        {
            string url = $"http://api.weatherapi.com/v1/current.json?key={ApiKey}&q={lat.ToString(CultureInfo.InvariantCulture)},{lon.ToString(CultureInfo.InvariantCulture)}";

            using var client = _httpClientFactory.CreateClient();
            client.Timeout = TimeSpan.FromSeconds(2);
            string response = await client.GetStringAsync(url);
            var weatherData = JsonSerializer.Deserialize<WeatherApiResponse>(response);

            if (weatherData?.Location == null)
                throw new Exception($"Ошибка при получении текущей погоды: текущей погода не найдена");

            var current = weatherData.Current;
            return new WeatherCurrent
            {
                TemperatureC = (int)Math.Round(current.TemperatureC),
                FeelsLikeC = (int)Math.Round(current.FeelsLikeC),
                ConditionText = current.Condition.LocalizedText,
                ConditionIcon = current.Condition.Icon,
                Humidity = current.Humidity,
                WindKph = (int)Math.Round(current.WindKph),
                PressureMb = (int)Math.Round(current.PressureMmHg),
                Uv = (int)Math.Round(current.Uv),
            };
        }

        private async Task FillForecastWeatherAsync(WeatherResponse weather, double lat, double lon)
        {
            string url = $"http://api.weatherapi.com/v1/forecast.json?key={ApiKey}&q={lat.ToString(CultureInfo.InvariantCulture)},{lon.ToString(CultureInfo.InvariantCulture)}&days=3";

            var handler = new HttpClientHandler
            {
                AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
            };

            using var client = new HttpClient(handler);
            client.Timeout = TimeSpan.FromSeconds(2);

            string response = await client.GetStringAsync(url);
            var weatherData = JsonSerializer.Deserialize<WeatherApiResponse>(response);

            if (weatherData?.Forecast?.ForecastDays == null)
                throw new Exception($"Ошибка при получении прогноза: прогноз погоды не найден");

            weather.Next3Days = [];
            weather.TodayHourly = [];
            weather.TomorrowHourly = [];
            foreach (var day in weatherData.Forecast.ForecastDays)
            {
                var date = DateTime.ParseExact(day.Date, "yyyy-MM-dd", null);

                var isToday = date.Date == DateTime.Now.Date;
                var isTomorrow = date.Date == DateTime.Now.Date.AddDays(1);

                weather.Next3Days.Add(new WeatherDaily
                {
                    Title = ShortDays[(int)date.DayOfWeek],
                    SubTitle = isToday
                        ? "Сегодня"
                        : date.Day == 1 ? $"{date:dd.MM}" : $"{date:dd}",
                    Weekend = Weekends.Contains(date.DayOfWeek),
                    ConditionIcon = day.Day.Condition.Icon,
                    MaxTemp = (int)Math.Round(day.Day.MaxTempC),
                    MinTemp = (int)Math.Round(day.Day.MinTempC),
                });

                if (isToday)
                {
                    var currentHour = DateTime.Now.Hour;
                    foreach (var hour in day.Hours)
                    {
                        var hourTime = DateTime.Parse(hour.Time);
                        if (hourTime.Hour >= currentHour)
                            weather.TodayHourly.Add(GetHourlyInfo(hour));
                    }
                }

                if (isTomorrow)
                {
                    foreach (var hour in day.Hours)
                    {
                        weather.TomorrowHourly.Add(GetHourlyInfo(hour));
                    }
                }
            }
        }

        static WeatherHourly GetHourlyInfo(HourWeather hour)
        {
            var hourDateTime = DateTime.Parse(hour.Time);
            return new WeatherHourly
            {
                Title = hourDateTime.Hour == 0
                    ? $"{ShortDays[(int)hourDateTime.DayOfWeek]}, {hourDateTime:HH:00}"
                    : $"{hourDateTime:HH:00}",
                ConditionIcon = hour.Condition.Icon,
                Temp = (int)Math.Round(hour.TemperatureC)
            };
        }
    }
}
