using Lada.Framework.Data.Domains;
using Lada.Framework.Data.Domains.Categories;
using Lada.Framework.Data.Domains.GoogleMap.PlaceAutocomplete;
using Lada.Framework.Data.Repositories;
using Lada.Framework.DTO;
using Lada.Framework.DTO.Items.GoogleMap.PlaceAutocomplete;
using Lada.Framework.DTO.Results.GoogleMap;
using Lada.Framework.DTO.Results.GoogleMap.PlaceAutocomplete;
using Lada.Framework.Infrastructures.Helpers;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Globalization;
using System.Security.Cryptography.X509Certificates;

namespace Lada.Framework.Infrastructures.Services
{
    public interface IGoogleMapService
    {
        /// <summary>
        /// Lấy ra các phường/xã chưa có latlong để cập nhật
        /// </summary>
        /// <returns></returns>
        Task UpdateWardLatLong(string apiKey);

        /// <summary>
        /// Cập nhật khoảng cách và văn bản khoảng cách của EndPoints.
        /// </summary>
        /// <returns></returns>
        Task UpdateEndPointsDistance(string apiKey);

        Task<int> UpdateEndPointsDistance(int endPointsId, string apiKey);

        Task<int> UpdateEndPointsDistance(int startLocation, int endLocation, string apiKey);


        #region PlaceAutocomplete
        Task<PlaceAutocompleteResult> PlaceAutocomplete(string keyword, string apiKey, string options = "");
        Task<PlaceDetailResult> PlaceDetail(string placeId, string apiKey);
        #endregion
    }
    public class GoogleMapService : IGoogleMapService
    {
        private const string BaseUrl = "https://maps.googleapis.com/maps/api/directions/json";
        private const string GeocodingBaseUrl = "https://maps.googleapis.com/maps/api/geocode/json";
        private const string PlaceAutocompleteUrl = "https://maps.googleapis.com/maps/api/place/autocomplete/json?input=";
        private readonly HttpClient _httpClient;
        private readonly IRepository<Location> _locationRepository;
        private readonly IRepository<EndPoints> _endPointsRepository;
        private readonly IRepository<PlacePrediction> _placePredictionRepository;


        public GoogleMapService(HttpClient httpClient, IRepository<Location> locationRepository,
            IRepository<EndPoints> endPointsRepository,
            IRepository<PlacePrediction> placePredictionRepository)
        {
            _httpClient = httpClient;
            _locationRepository = locationRepository;
            _endPointsRepository = endPointsRepository;
            _placePredictionRepository = placePredictionRepository;
        }


        #region Map Methods
        public async Task UpdateWardLatLong(string apiKey)
        {
            var wards = _locationRepository.Query().Where(l =>
            l.Level == 3 &&
            l.Lat == 0 && l.Long == 0 &&
            (l.Type == LocationType.Urban || l.Type == LocationType.SubUrban)).Take(500).ToList();
            if (wards.Any())
            {
                var districtIds = wards.Select(w => w.ParentId).ToList();
                var districts = _locationRepository.Query().Where(d => districtIds.Contains(d.Id)).ToList();

                var cityIds = districts.Select(d => d.ParentId).ToList();
                var cities = _locationRepository.Query().Where(c => cityIds.Contains(c.Id)).ToList();


                foreach (var ward in wards)
                {
                    var district = districts.FirstOrDefault(d => d.Id == ward.ParentId);
                    if (district == null || district.Id <= 0)
                    {
                        continue;
                    }
                    var city = cities.FirstOrDefault(c => c.Id == district.ParentId);
                    if (city == null || city.Id <= 0)
                    {
                        continue;
                    }
                    if (ward.Id > 0 && district.Id > 0 && city.Id > 0)
                    {
                        var address = $"{ward.Name},{district.Name},{city.Name}";
                        var latlongResult = await GetLatLngFromAddress(address, apiKey);
                        if (latlongResult.Result == Result.Success)
                        {
                            ward.Lat = latlongResult.Latitude;
                            ward.Long = latlongResult.Longitude;
                            await UpdateWardLatLong(ward);
                        }

                    }
                }
            }

        }

        /// <summary>
        /// Lấy thông tin Lat Long cho Ward
        /// </summary>
        /// <param name="wardId"></param>
        /// <param name="apiKey"></param>
        /// <returns></returns>
        private async Task<LatLongResult> GetAndUpdateWardLatLong(int wardId, string apiKey)
        {
            var result = new LatLongResult();
            var ward = await _locationRepository.Query().Where(l => l.Id == wardId).FirstOrDefaultAsync();
            if (ward == null || ward.Id <= 0)
            {
                result.Result = Result.Failed;
                result.Message = "Không xác định được phường / xã";
                return result;
            }
            var district = await _locationRepository.Query().Where(d => d.Id == ward.ParentId).FirstOrDefaultAsync();
            if (district == null || district.Id <= 0)
            {
                result.Result = Result.Failed;
                result.Message = "Không xác định được quận / huyện";
                return result;
            }

            var city = await _locationRepository.Query().Where(c => c.Id == district.ParentId).FirstOrDefaultAsync();
            if (city == null || city.Id <= 0)
            {
                result.Result = Result.Failed;
                result.Message = "Không xác định được tỉnh / thành phố";
                return result;
            }
            var address = $"{ward.Name},{district.Name},{city.Name}";
            result = await GetLatLngFromAddress(address, apiKey);
            if (result.Result == Result.Success)
            {
                ward.Lat = result.Latitude;
                ward.Long = result.Longitude;
                await UpdateWardLatLong(ward);
            }
            return result;

        }

        public async Task UpdateEndPointsDistance(string apiKey)
        {
            var endPointsList = _endPointsRepository.Query().Where(ep => ep.Distance <= 0).Take(1).ToList();
            if (endPointsList.Any())
            {
                foreach (var endPoints in endPointsList)
                {
                    var startLocation = _locationRepository.Query().FirstOrDefault(e => e.Id == endPoints.StartId);
                    var endLocation = _locationRepository.Query().FirstOrDefault(e => e.Id == endPoints.EndId);
                    if (startLocation != null && endLocation != null && startLocation.Id > 0 && endLocation.Id > 0 &&
                        startLocation.Lat > 0 && startLocation.Long > 0 && endLocation.Lat > 0 && endLocation.Long > 0)
                    {
                        var distanceResult = await GetDistance(startLocation, endLocation, apiKey);
                        if (distanceResult.Result == Result.Success)
                        {
                            endPoints.Distance = distanceResult.Distance;
                            endPoints.DistanceText = distanceResult.DistaceText;
                            await UpdateEndPointsDistance(endPoints);
                        }
                    }

                }

            }

        }

        public async Task<int> UpdateEndPointsDistance(int endPointsId, string apiKey)
        {
            var result = 0;
            var endPoints = await _endPointsRepository.Query().FirstOrDefaultAsync(ep => ep.Id == endPointsId);
            if (endPoints != null && endPoints.Id > 0)
            {
                var locations = _locationRepository.Query().Where(l => l.Id == endPoints.StartId || l.Id == endPoints.EndId).ToList();

                var startLocation = locations.FirstOrDefault(e => e.Id == endPoints.StartId);
                var endLocation = locations.FirstOrDefault(e => e.Id == endPoints.EndId);
                if (startLocation != null && endLocation != null && startLocation.Id > 0 && endLocation.Id > 0 &&
                    startLocation.Lat > 0 && startLocation.Long > 0 && endLocation.Lat > 0 && endLocation.Long > 0)
                {
                    var distanceResult = await GetDistance(startLocation, endLocation, apiKey);
                    if (distanceResult.Result == Result.Success)
                    {
                        endPoints.Distance = distanceResult.Distance;
                        endPoints.DistanceText = distanceResult.DistaceText;
                        result = distanceResult.Distance;
                        await UpdateEndPointsDistance(endPoints);
                    }
                }

            }
            return result;

        }

        public async Task<int> UpdateEndPointsDistance(int startId, int endId, string apiKey)
        {
            var result = 0;

            var locations = _locationRepository.Query().Where(l => l.Id == startId || l.Id == endId).ToList();

            if (locations.Any())
            {
                var startLocation = locations.FirstOrDefault(e => e.Id == startId);

                var endLocation = locations.FirstOrDefault(e => e.Id == endId);

                if (startLocation == null || startLocation.Id == 0 || endLocation == null || endLocation.Id == 0)
                {
                    return result;
                }

                //Nếu chưa có latlong thì lấy 
                if (startLocation.Lat <= 0 || startLocation.Long <= 0)
                {
                    var startLatLongResult = await GetAndUpdateWardLatLong(startLocation.Id, apiKey);
                    if (startLatLongResult.Result == Result.Success)
                    {
                        startLocation.Lat = startLatLongResult.Latitude;
                        startLocation.Long = startLatLongResult.Longitude;
                    }
                }

                //Nếu chưa có latlong thì lấy 
                if (endLocation.Lat <= 0 || endLocation.Long <= 0)
                {
                    var endLatLongResult = await GetAndUpdateWardLatLong(endLocation.Id, apiKey);
                    if (endLatLongResult.Result == Result.Success)
                    {
                        endLocation.Lat = endLatLongResult.Latitude;
                        endLocation.Long = endLatLongResult.Longitude;
                    }
                }

                //Kiểm tra xem đã có endpoint nào chưa
                var endPoints = await _endPointsRepository.Query().FirstOrDefaultAsync(ep => ep.StartId == startId && ep.EndId == endId);

                //Nếu đã có endpoints thì lấy ra
                if (endPoints != null && endPoints.Id > 0)
                {
                    if (endPoints.Distance <= 0)
                    {
                        //Nếu khoảng cách nhỏ hơn 0 thì lấy khoảng cách từ google
                        if (startLocation != null && endLocation != null && startLocation.Id > 0 && endLocation.Id > 0 &&
                    startLocation.Lat > 0 && startLocation.Long > 0 && endLocation.Lat > 0 && endLocation.Long > 0)
                        {
                            var distanceResult = await GetDistance(startLocation, endLocation, apiKey);
                            if (distanceResult.Result == Result.Success)
                            {
                                endPoints.Distance = distanceResult.Distance;
                                endPoints.DistanceText = distanceResult.DistaceText;
                                result = distanceResult.Distance;
                                await UpdateEndPointsDistance(endPoints);
                            }
                        }
                    }
                    else
                    {
                        result = endPoints.Distance;
                    }
                }
                else
                {
                    //Thêm endPoints vào CSDL

                    var currentTime = DateTimeHelper.CurrentTime();


                    if (startLocation != null && endLocation != null && startLocation.Id > 0 && endLocation.Id > 0 &&
                    startLocation.Lat > 0 && startLocation.Long > 0 && endLocation.Lat > 0 && endLocation.Long > 0)
                    {


                        var distanceResult = await GetDistance(startLocation, endLocation, apiKey);
                        if (distanceResult.Result == Result.Success)
                        {

                            result = distanceResult.Distance;
                            var newEndPoint = new EndPoints()
                            {
                                StartId = startLocation.Id,
                                EndId = endLocation.Id,
                                Distance = distanceResult.Distance,
                                DistanceText = distanceResult.DistaceText,
                                CreatedBy = startLocation.CreatedBy,
                                CreatedByUserName = startLocation.CreatedByUserName,
                                UpdatedBy = startLocation.CreatedBy,
                                UpdatedByUserName = startLocation.CreatedByUserName,
                                CreatedTime = currentTime,
                                UpdatedTime = currentTime
                            };
                            try
                            {
                                await _endPointsRepository.InsertAsync(newEndPoint);
                            }
                            catch (Exception e)
                            {
                                result = 0;
                            }
                        }
                    }




                }
            }

            return result;

        }

        #endregion


        #region Google Map Place API
        public async Task<PlaceAutocompleteResult> PlaceAutocomplete(string keyword, string apiKey, string options = "")
        {
            var result = new PlaceAutocompleteResult();
            result.Items = new List<PlaceItem>();
            var url = $"{PlaceAutocompleteUrl}{Uri.EscapeDataString(keyword)}&components=country:vn&language=vi{options}&key={apiKey}";
            try
            {
                var response = await _httpClient.GetStringAsync(url);
                var data = JObject.Parse(response);
                if (data["status"].ToString() == "OK")
                {
                    var predictions = data["predictions"];
                    var places = new List<PlacePrediction>();
                    var currentTime = DateTimeHelper.CurrentTime();
                    foreach (var prediction in predictions)
                    {
                        string placeId = prediction["place_id"].ToString();
                        string description = prediction["description"].ToString();

                        // Tạo đối tượng Place và thêm vào danh sách để lưu vào CSDL
                        var place = new PlacePrediction
                        {
                            PlaceId = placeId,
                            Description = description,
                            CreatedBy = 0,
                            CreatedByUserName = "SystemAuto",
                            UpdatedBy = 0,
                            UpdatedByUserName = "SystemAuto",
                            CreatedTime = currentTime,
                            UpdatedTime = currentTime
                        };
                        places.Add(place);

                        result.Items.Add(new PlaceItem() { Id = placeId, Name = description.Replace(", Việt Nam", "") });

                    }
                    if (places.Any())
                    {
                        var placeIds = places.Select(p => p.PlaceId);
                        var existPlaceIds = await _placePredictionRepository.Query().Where(p => placeIds.Contains(p.PlaceId)).Select(p => p.PlaceId).ToListAsync();
                        var addPlaces = places.Where(p => !existPlaceIds.Contains(p.PlaceId)).ToList();
                        if (addPlaces.Any())
                        {
                            //Lưu danh sách vào CSDL
                            await _placePredictionRepository.InsertAsync(addPlaces);
                        }

                    }
                }
                else
                {
                    result.Result = Result.Failed;
                    result.Message = "Hiện chưa lấy được danh sách, vui lòng thử lại sau!";
                }
            }
            catch (Exception ex)
            {
                result.Result = Result.SystemError;
                result.Message = ex.Message;
            }

            return result;
        }

        public async Task<PlaceDetailResult> PlaceDetail(string placeId, string apiKey)
        {
            var result = new PlaceDetailResult();
            var place = await _placePredictionRepository.Query().FirstOrDefaultAsync(p => p.PlaceId == placeId);
            if (place != null)
            {
                if (place.CityId == 0 && place.DistrictId == 0 && place.WardId == 0)
                {
                    result = await PlaceDetailFromGoogle(placeId, apiKey);
                }
                else
                {
                    result.CityId = place.CityId;
                    result.DistrictId = place.DistrictId;
                    result.WardId = place.WardId;
                    if (result.CityId > 0)
                    {
                        var city = await _locationRepository.Query().FirstOrDefaultAsync(c => c.Id == result.CityId);
                        if (city != null && city.Id > 0)
                        {
                            result.City = city.Name;
                        }
                    }
                    if (result.DistrictId > 0)
                    {
                        var district = await _locationRepository.Query().FirstOrDefaultAsync(c => c.Id == result.DistrictId);
                        if (district != null && district.Id > 0)
                        {
                            result.District = district.CombinedName;
                        }
                    }
                    if (result.WardId > 0)
                    {
                        var ward = await _locationRepository.Query().FirstOrDefaultAsync(c => c.Id == result.WardId);
                        if (ward != null && ward.Id > 0)
                        {
                            result.Ward = ward.Name;
                        }
                    }
                }
            }
            else
            {
                result = await PlaceDetailFromGoogle(placeId, apiKey);
            }

            return result;

        }
        public async Task<PlaceDetailResult> PlaceDetailFromGoogle(string placeId, string apiKey)
        {
            var result = new PlaceDetailResult();
            string geocodeUrl = $"https://maps.googleapis.com/maps/api/geocode/json?place_id={placeId}&key={apiKey}";
            try
            {
                var response = await _httpClient.GetStringAsync(geocodeUrl);
                var data = JObject.Parse(response);

                if (data["status"].ToString() == "OK")
                {
                    var addressComponents = data["results"][0]["address_components"];


                    foreach (var component in addressComponents)
                    {
                        var types = component["types"];
                        if (types.Any(t => t.ToString() == "administrative_area_level_1"))
                        {
                            result.City = component["long_name"].ToString();
                        }
                        else if (types.Any(t => t.ToString() == "administrative_area_level_2"))
                        {
                            result.District = component["long_name"].ToString();
                        }
                        else if (types.Any(t => t.ToString() == "sublocality_level_1"))
                        {
                            result.Ward = component["long_name"].ToString();
                        }
                    }

                    if (!string.IsNullOrEmpty(result.City))
                    {
                        var unsignCity = result.City.Unsigned().Trim();
                        var city = await _locationRepository.Query().FirstOrDefaultAsync(c => c.Level == 1 && (c.Name == result.City || c.UnsignCombinedName == unsignCity));

                        if (city != null && city.Id > 0 && !string.IsNullOrEmpty(result.District))
                        {
                            result.CityId = city.Id;
                            var district = _locationRepository.Query().FirstOrDefaultAsync(d => d.ParentId == city.Id && (d.Name == result.District));
                            if (district != null && district.Id > 0 && !string.IsNullOrEmpty(result.Ward))
                            {
                                result.DistrictId = district.Id;
                                var ward = _locationRepository.Query().FirstOrDefaultAsync(d => d.ParentId == district.Id && (d.Name == result.Ward));
                                if (ward != null && ward.Id > 0)
                                {
                                    result.WardId = ward.Id;

                                }
                            }
                        }

                    }
                }
                else
                {
                    result.Result = Result.Failed;
                    result.Message = "Không lấy được dữ liệu";

                }
            }
            catch (Exception ex)
            {
                result.Result = Result.SystemError;
                result.Message = ex.Message;

            }

            return result;
        }
        #endregion


        #region Map Actions
        private async Task<DistanceResult> GetDistance(Location startLocation, Location endLocation, string apiKey)
        {
            var result = new DistanceResult();
            var startPointStr = startLocation.Lat.ToString(CultureInfo.InvariantCulture) + "," + startLocation.Long.ToString(CultureInfo.InvariantCulture);
            var endPointStr = endLocation.Lat.ToString(CultureInfo.InvariantCulture) + "," + endLocation.Long.ToString(CultureInfo.InvariantCulture);


            var url = $"https://maps.googleapis.com/maps/api/distancematrix/json?origins={startPointStr}&destinations={endPointStr}&key={apiKey}";
            //var url = $"{BaseUrl}?origin={origin}&destination={destination}&key={apiKey}";

            var response = await _httpClient.GetAsync(url);
            //response.EnsureSuccessStatusCode();

            if (!response.IsSuccessStatusCode)
            {
                result.Result = Result.Failed;
                result.Message = $"Failed to get distance from Google Maps API: {response.ReasonPhrase}";
                return result;
            }

            var content = await response.Content.ReadAsStringAsync();
            var responseJson = JsonConvert.DeserializeObject<DistanceMatrixResponse>(content);

            if (responseJson.Status != "OK")
            {
                result.Result = Result.Failed;
                result.Message = $"Failed to get distance from Google Maps API: {responseJson.Status}";
                return result;

            }

            // Kiểm tra null cho Rows và Elements
            if (responseJson.Rows == null || responseJson.Rows.Count == 0)
            {
                result.Result = Result.Failed;
                result.Message = "Không có dữ liệu khoảng cách từ Google Maps API";
                return result;
            }

            if (responseJson.Rows[0].Elements == null || responseJson.Rows[0].Elements.Count == 0)
            {
                result.Result = Result.Failed;
                result.Message = "Không có dữ liệu element từ Google Maps API";
                return result;
            }

            var element = responseJson.Rows[0].Elements[0];

            // Kiểm tra status của element và Distance
            if (element.Status != "OK" || element.Distance == null)
            {
                result.Result = Result.Failed;
                result.Message = $"Không tìm được khoảng cách giữa 2 điểm. Status: {element.Status}";
                return result;
            }

            result.Distance = element.Distance.Value;
            result.DistaceText = element.Distance.Text;

            return result;
        }
        private async Task<LatLongResult> GetLatLngFromAddress(string address, string apiKey)
        {
            var result = new LatLongResult();
            var url = $"{GeocodingBaseUrl}?address={Uri.EscapeDataString(address)}&key={apiKey}";

            var response = await _httpClient.GetAsync(url);

            if (!response.IsSuccessStatusCode)
            {
                result.Result = Lada.Framework.DTO.Result.Failed;
                result.Message = $"Failed to get latlng from Google Maps Geocoding API: {response.ReasonPhrase}";
                return result;
            }

            var content = await response.Content.ReadAsStringAsync();
            var responseJson = JsonConvert.DeserializeObject<GoogleMapsGeocodingResponse>(content);

            if (responseJson.Status != "OK" || responseJson.Results.Length == 0)
            {
                result.Result = Lada.Framework.DTO.Result.Failed;
                result.Message = $"Failed to get latlng from Google Maps Geocoding API.";
                return result;

            }

            var location = responseJson.Results[0].Geometry.Location;
            result.Latitude = location.Lat;
            result.Longitude = location.Lng;
            return result;
        }
        #endregion


        #region Entity Actions
        private async Task UpdateWardLatLong(Location ward)
        {
            var wardForUpdate = _locationRepository.Query().FirstOrDefault(w => ward.Id == w.Id);
            if (wardForUpdate != null && wardForUpdate.Id > 0)
            {
                wardForUpdate.Lat = ward.Lat;
                wardForUpdate.Long = ward.Long;
                wardForUpdate = wardForUpdate.UpdateCommonInt();
                await _locationRepository.UpdateAsync(ward);
            }
        }

        private async Task UpdateEndPointsDistance(EndPoints endPoints)
        {
            var endPointsForUpdate = _endPointsRepository.Query().FirstOrDefault(w => endPoints.Id == w.Id);
            if (endPointsForUpdate != null && endPointsForUpdate.Id > 0)
            {
                endPointsForUpdate.Distance = endPoints.Distance;
                endPointsForUpdate.DistanceText = endPoints.DistanceText;
                endPointsForUpdate = endPointsForUpdate.UpdateCommonInt();
                await _endPointsRepository.UpdateAsync(endPoints);
            }
        }
        #endregion
    }


}
