using Lada.Framework.Data.Domains;
using Lada.Framework.Data.Domains.Categories;
using Lada.Framework.Data.Repositories;
using Lada.Framework.DTO;
using Lada.Framework.DTO.SmartTable;
using Lada.Framework.Infrastructures;
using Ladaer.BackEnd.Infrastructures.DTO;
using Ladaer.BackEnd.Infrastructures.DTO.Items.Categories;
using Ladaer.BackEnd.Infrastructures.DTO.Models.Categories;
using Ladaer.BackEnd.Infrastructures.DTO.Results.Categories;
using Microsoft.EntityFrameworkCore;

namespace Ladaer.BackEnd.Infrastructures.Services.Categories
{
    /// <summary>
    /// Interface cho dịch vụ EndPoints.
    /// </summary>
    public interface IEndPointsService
    {
        /// <summary>
        /// Tìm kiếm EndPoints dựa trên tham số SmartTable.
        /// </summary>
        /// <param name="param">Tham số SmartTable.</param>
        /// <returns>Kết quả tìm kiếm dưới dạng SmartTableResult.</returns>
        Task<SmartTableResult<EndPointsItem>> Search(SmartTableParam param);

        /// <summary>
        /// Tạo mới hoặc cập nhật EndPoints.
        /// </summary>
        /// <param name="model">Mô hình EndPoints.</param>
        /// <returns>Kết quả của việc tạo mới hoặc cập nhật.</returns>
        Task<EndpointsResult> CreateOrUpdate(EndPointsModel model);

        /// <summary>
        /// Lấy EndPoints theo Id.
        /// </summary>
        /// <param name="id">Id của EndPoints.</param>
        /// <returns>Mô hình EndPoints.</returns>
        Task<EndPointsModel> GetById(int id);

        /// <summary>
        /// Xóa EndPoints theo Id.
        /// </summary>
        /// <param name="id">Id của EndPoints.</param>
        /// <param name="currentUser">Người dùng hiện tại.</param>
        /// <returns>Kết quả của việc xóa.</returns>
        Task<BaseResult> Delete(int id, CurrentUser currentUser);

        /// <summary>
        /// Tự động tạo EndPoints.
        /// </summary>
        /// <param name="currentUser">Người dùng hiện tại.</param>
        /// <returns>Kết quả của việc tự động tạo.</returns>
        Task<BaseResult> AutoGenerate(CurrentUser currentUser);

        /// <summary>
        /// Lấy thông tin điểm đầu cuối EndPoints.
        /// </summary>
        /// <param name="id">Id của EndPoints.</param>
        /// <returns>Thông tin điểm đầu cuối EndPoints.</returns>
        Task<EndPointsItem> GetEndPoints(int id);
    }
    /// <summary>
    /// Dịch vụ EndPoints.
    /// </summary>
    public class EndPointsService : IEndPointsService
    {
        private readonly IRepository<EndPoints> _endPointsRepository;
        private readonly IRepository<Location> _locationRepository;

        /// <summary>
        /// Khởi tạo một instance của EndPointsService.
        /// </summary>
        /// <param name="endPointsRepository">Repository của EndPoints.</param>
        /// <param name="locationRepository">Repository của Location.</param>
        public EndPointsService(IRepository<EndPoints> endPointsRepository, IRepository<Location> locationRepository)
        {
            _endPointsRepository = endPointsRepository;
            _locationRepository = locationRepository;
        }

        #region SEARCH
        /// <summary>
        /// Tìm kiếm EndPoints dựa trên tham số SmartTable.
        /// </summary>
        /// <param name="param">Tham số SmartTable.</param>
        /// <returns>Kết quả tìm kiếm dưới dạng SmartTableResult.</returns>
        public async Task<SmartTableResult<EndPointsItem>> Search(SmartTableParam param)
        {
            var query = _endPointsRepository.Query();
            if (param.Search.PredicateObject != null)
            {
                dynamic search = param.Search.PredicateObject;
                if (search.DistanceFrom != null)
                {
                    int distanceFrom = search.DistanceFrom;
                    query = query.Where(x => x.Distance >= distanceFrom);
                }
                if (search.DistanceTo != null)
                {
                    int distanceTo = search.DistanceTo;
                    query = query.Where(x => x.Distance <= distanceTo);
                }
                if (search.SourceCityId != null)
                {
                    int sourceCityId = 0;
                    int sourceDistrictId = 0;
                    sourceCityId = search.SourceCityId;
                    if (search.SourceDistrictId != null)
                    {
                        sourceDistrictId = search.SourceDistrictId;
                    }
                    if (sourceDistrictId > 0)
                    {
                        var wardIds = _locationRepository.Query().Where(l => l.Level == 3 && l.ParentId == sourceDistrictId).Select(l => l.Id).ToList();
                        query = query.Where(x => wardIds.Contains(x.StartId));
                    }
                    else if (sourceCityId > 0)
                    {
                        var districtIds = _locationRepository.Query().Where(l => l.Level == 2 && l.ParentId == sourceCityId).Select(l => l.Id).ToList();
                        var wardIds = _locationRepository.Query().Where(l => l.Level == 3 && districtIds.Contains(l.ParentId)).Select(l => l.Id).ToList();
                        query = query.Where(x => wardIds.Contains(x.StartId));
                    }

                }
                if (search.DestCityId != null)
                {
                    int destCityId = 0;
                    int destDistrictId = 0;
                    destCityId = search.DestCityId;
                    if (search.DestDistrictId != null)
                    {
                        destDistrictId = search.DestDistrictId;
                    }
                    if (destDistrictId > 0)
                    {
                        var wardIds = _locationRepository.Query().Where(l => l.Level == 3 && l.ParentId == destDistrictId).Select(l => l.Id).ToList();
                        query = query.Where(x => wardIds.Contains(x.EndId));
                    }
                    else if (destCityId > 0)
                    {
                        var districtIds = _locationRepository.Query().Where(l => l.Level == 2 && l.ParentId == destCityId).Select(l => l.Id).ToList();
                        var wardIds = _locationRepository.Query().Where(l => l.Level == 3 && districtIds.Contains(l.ParentId)).Select(l => l.Id).ToList();
                        query = query.Where(x => wardIds.Contains(x.EndId));
                    }

                }

            }
            param.Sort = new Sort() { Predicate = "Id", Reverse = false };
            var gridData = await query.ToSmartTableResultAsync(param, x => x.ToItem());
            gridData.Items = await AdditionInfo(gridData.Items);
            return gridData;
        }

        /// <summary>
        /// Thêm thông tin bổ sung cho danh sách EndPointsItem.
        /// </summary>
        /// <param name="items">Danh sách EndPointsItem.</param>
        /// <returns>Danh sách EndPointsItem đã được thêm thông tin bổ sung.</returns>
        private async Task<IEnumerable<EndPointsItem>> AdditionInfo(IEnumerable<EndPointsItem> items)
        {
            if (items != null && items.Any())
            {
                var startIds = items.Select(w => w.StartId).ToList();
                var endIds = items.Select(w => w.EndId).ToList();
                var wardIds = new List<int>();
                wardIds.AddRange(startIds);
                wardIds.AddRange(endIds);
                wardIds = wardIds.Distinct().ToList();
                if (wardIds.Any())
                {
                    var wards = _locationRepository.Query().Where(l => wardIds.Contains(l.Id)).ToList();
                    var districtIds = wards.Select(w => w.ParentId).ToList();
                    var districts = _locationRepository.Query().Where(l => districtIds.Contains(l.Id)).ToList();
                    var cityIds = districts.Select(w => w.ParentId).ToList();
                    var cities = _locationRepository.Query().Where(l => cityIds.Contains(l.Id)).ToList();
                    foreach (var item in items)
                    {
                        var startWard = wards.FirstOrDefault(w => w.Id == item.StartId);
                        if (startWard != null && startWard.Id > 0)
                        {
                            item.Start = $"{startWard.Prefix} {startWard.Name}";
                            var startDistrict = districts.FirstOrDefault(d => d.Id == startWard.ParentId);
                            if (startDistrict != null && startDistrict.Id > 0)
                            {
                                item.StartDistrict = $"{startDistrict.Prefix} {startDistrict.Name}";
                                var startCity = cities.FirstOrDefault(d => d.Id == startDistrict.ParentId);
                                if (startCity != null && startCity.Id > 0)
                                {
                                    item.StartCity = $"{startCity.Prefix} {startCity.Name}";
                                }
                            }
                        }

                        var endWard = wards.FirstOrDefault(w => w.Id == item.EndId);
                        if (endWard != null && endWard.Id > 0)
                        {
                            item.End = $"{endWard.Prefix} {endWard.Name}";
                            var endDistrict = districts.FirstOrDefault(d => d.Id == endWard.ParentId);
                            if (endDistrict != null && endDistrict.Id > 0)
                            {
                                item.EndDistrict = $"{endDistrict.Prefix} {endDistrict.Name}";
                                var endCity = cities.FirstOrDefault(d => d.Id == endDistrict.ParentId);
                                if (endCity != null && endCity.Id > 0)
                                {
                                    item.EndCity = $"{endCity.Prefix} {endCity.Name}";
                                }
                            }
                        }
                    }

                }
            }
            return items;
        }
        #endregion

        #region CRUD
        /// <summary>
        /// Tạo mới hoặc cập nhật EndPoints.
        /// </summary>
        /// <param name="model">Mô hình EndPoints.</param>
        /// <returns>Kết quả của việc tạo mới hoặc cập nhật.</returns>
        public async Task<EndpointsResult> CreateOrUpdate(EndPointsModel model)
        {
            var endPoints = model.ToEntity();

            // Cập nhật thông tin chung của thực thể
            endPoints = endPoints.UpdateCommonInt(model.UpdatedBy, model.UpdatedByUserName);

            if (endPoints.Id > 0)
            {
                // Cập nhật
                return await Update(endPoints);
            }
            else
            {
                // Thêm mới
                return await Create(endPoints);
            }
        }

        #region PRIVATE
        /// <summary>
        /// Tạo mới EndPoints.
        /// </summary>
        /// <param name="endPoints">Thực thể EndPoints.</param>
        /// <returns>Kết quả của việc tạo mới.</returns>
        private async Task<EndpointsResult> Create(EndPoints endPoints)
        {
            var result = new EndpointsResult();
            try
            {
                await _endPointsRepository.InsertAsync(endPoints);
                result.Id = endPoints.Id;

            }
            catch (Exception e)
            {
                result.Result = Result.SystemError;
                result.Message = e.ToString();
                result.Id = 0;
            }
            return result;
        }

        /// <summary>
        /// Cập nhật EndPoints.
        /// </summary>
        /// <param name="endPoints">Thực thể EndPoints.</param>
        /// <returns>Kết quả của việc cập nhật.</returns>
        private async Task<EndpointsResult> Update(EndPoints endPoints)
        {
            var result = new EndpointsResult() { Result = Result.Success };
            var endPointsForUpdate = await _endPointsRepository.Query().FirstOrDefaultAsync(c => c.Id == endPoints.Id);
            if (endPointsForUpdate == null || endPoints.Id <= 0)
            {
                result.Result = Result.Failed;
                result.Message = "Không tìm thấy điểm đầu cuối yêu cầu!";
                return result;
            }

            try
            {
                endPointsForUpdate.StartId = endPoints.StartId;
                endPointsForUpdate.EndId = endPoints.EndId;
                endPointsForUpdate.Distance = endPoints.Distance;

                await _endPointsRepository.UpdateAsync(endPointsForUpdate);
                result.Id = endPointsForUpdate.Id;
            }
            catch (Exception e)
            {
                result.Result = Result.SystemError;
                result.Message = e.ToString();
            }

            return result;
        }
        #endregion

        /// <summary>
        /// Lấy EndPoints theo Id.
        /// </summary>
        /// <param name="id">Id của EndPoints.</param>
        /// <returns>Mô hình EndPoints.</returns>
        public async Task<EndPointsModel> GetById(int id)
        {
            var result = new EndPointsModel();
            if (id <= 0)
            {
                return result;
            }
            var endPoints = await _endPointsRepository.Query().FirstOrDefaultAsync(c => c.Id == id);
            if (endPoints == null || endPoints.Id <= 0)
            {
                return result;
            }
            var endPointsModel = endPoints.ToModel();
            return endPointsModel;
        }

        /// <summary>
        /// Xóa EndPoints theo Id.
        /// </summary>
        /// <param name="id">Id của EndPoints.</param>
        /// <param name="currentUser">Người dùng hiện tại.</param>
        /// <returns>Kết quả của việc xóa.</returns>
        public async Task<BaseResult> Delete(int id, CurrentUser currentUser)
        {
            var result = new BaseResult() { Result = Result.Success };
            var endPointsForUpdate = await _endPointsRepository.Query().FirstOrDefaultAsync(c => c.Id == id);
            if (endPointsForUpdate == null)
            {
                result.Result = Result.Failed;
                result.Message = "Không tìm thấy bản ghi yêu cầu";
                return result;
            }

            try
            {
                endPointsForUpdate.IsDeleted = true;
                endPointsForUpdate = endPointsForUpdate.UpdateCommonInt(currentUser.UserId, currentUser.UserName);
                await _endPointsRepository.UpdateAsync(endPointsForUpdate);
            }
            catch (Exception e)
            {
                result.Result = Result.SystemError;
                result.Message = e.Message;
            }

            return result;
        }
        #endregion

        #region GENERATE
        /// <summary>
        /// Tự động tạo EndPoints theo từng batch 1000 bản ghi để tránh delay.
        /// </summary>
        /// <param name="currentUser">Người dùng hiện tại.</param>
        /// <returns>Kết quả của việc tự động tạo.</returns>
        public async Task<BaseResult> AutoGenerate(CurrentUser currentUser)
        {
            var result = new BaseResult();
            var batchSize = 1000;
            var existWards = _locationRepository.Query().Where(w => w.Level == 3
                            && (w.Type == LocationType.Urban || w.Type == LocationType.SubUrban)).ToList();
            var currentTime = DateTimeOffset.Now;
            var addedNumber = 0;
            var errorMessage = "";

            if (existWards.Any())
            {
                // Tạo danh sách các cặp ward chưa tồn tại EndPoints
                var wardPairs = new List<(int StartId, int EndId)>();
                var wardIds = existWards.Select(w => w.Id).ToHashSet();

                // Lấy tất cả các EndPoints đã tồn tại (theo cặp không phân biệt chiều)
                var existEndPoints = _endPointsRepository.Query()
                    .Select(ep => new { ep.StartId, ep.EndId })
                    .ToList()
                    .Select(ep => ep.StartId < ep.EndId
                        ? (ep.StartId, ep.EndId)
                        : (ep.EndId, ep.StartId))
                    .ToHashSet();

                foreach (var startPoint in existWards)
                {
                    foreach (var endPoint in existWards)
                    {
                        if (startPoint.Id == endPoint.Id) continue;

                        // Đảm bảo chỉ tạo 1 chiều (ví dụ: (1,2) chứ không tạo cả (2,1))
                        var pair = startPoint.Id < endPoint.Id
                            ? (startPoint.Id, endPoint.Id)
                            : (endPoint.Id, startPoint.Id);

                        if (existEndPoints.Contains(pair)) continue;

                        wardPairs.Add((pair.Item1, pair.Item2));
                    }
                }

                // Chỉ lấy tối đa batchSize cặp để insert
                var batchPairs = wardPairs.Take(batchSize).ToList();

                var newEndPoints = new List<EndPoints>();
                foreach (var pair in batchPairs)
                {
                    var newEndPoint = new EndPoints()
                    {
                        StartId = pair.Item1,
                        EndId = pair.Item2,
                        Distance = 0,
                        DistanceText = "",
                        CreatedBy = currentUser.UserId,
                        CreatedByUserName = currentUser.UserName,
                        UpdatedBy = currentUser.UserId,
                        UpdatedByUserName = currentUser.UserName,
                        CreatedTime = currentTime,
                        UpdatedTime = currentTime
                    };
                    newEndPoints.Add(newEndPoint);
                }

                try
                {
                    foreach (var ep in newEndPoints)
                    {
                        await _endPointsRepository.InsertAsync(ep);
                        addedNumber += 1;
                    }
                }
                catch (Exception e)
                {
                    result.Result = Result.SystemError;
                    errorMessage = e.ToString();
                }
            }

            if (result.Result == Result.SystemError)
            {
                result.Message = $"Đã tạo được {addedNumber} EndPoints nhưng gặp lỗi: {errorMessage}";
            }
            else
            {
                result.Message = $"Đã tạo được {addedNumber} EndPoints trong lần chạy này. Nếu còn cặp chưa tạo, hãy chạy lại để tiếp tục.";
            }
            return result;
        }
        #endregion

        /// <summary>
        /// Lấy thông tin điểm đầu cuối EndPoints.
        /// </summary>
        /// <param name="id">Id của EndPoints.</param>
        /// <returns>Thông tin điểm đầu cuối EndPoints.</returns>
        public async Task<EndPointsItem> GetEndPoints(int id)
        {
            var result = new EndPointsItem();
            if (id <= 0)
            {
                return result;
            }

            var endPoints = await _endPointsRepository.Query().FirstOrDefaultAsync(c => c.Id == id);
            if (endPoints == null || endPoints.Id <= 0)
            {
                return result;
            }

            result = endPoints.ToItem();
            var startWard = await _locationRepository.Query().FirstOrDefaultAsync(w => w.Id == endPoints.StartId);
            if (startWard != null && startWard.Id > 0)
            {
                result.Start = $"{startWard.Prefix} {startWard.Name}";
                var startDistrict = await _locationRepository.Query().FirstOrDefaultAsync(d => d.Id == startWard.ParentId);
                if (startDistrict != null && startDistrict.Id > 0)
                {
                    result.StartDistrict = $"{startDistrict.Prefix} {startDistrict.Name}";
                    var startCity = await _locationRepository.Query().FirstOrDefaultAsync(d => d.Id == startDistrict.ParentId);
                    if (startCity != null && startCity.Id > 0)
                    {
                        result.StartCity = $"{startCity.Prefix} {startCity.Name}";
                    }
                }
            }

            var endWard = await _locationRepository.Query().FirstOrDefaultAsync(w => w.Id == endPoints.EndId);
            if (endWard != null && endWard.Id > 0)
            {
                result.End = $"{endWard.Prefix} {endWard.Name}";
                var endDistrict = await _locationRepository.Query().FirstOrDefaultAsync(d => d.Id == endWard.ParentId);
                if (endDistrict != null && endDistrict.Id > 0)
                {
                    result.EndDistrict = $"{endDistrict.Prefix} {endDistrict.Name}";
                    var endCity = await _locationRepository.Query().FirstOrDefaultAsync(d => d.Id == endDistrict.ParentId);
                    if (endCity != null && endCity.Id > 0)
                    {
                        result.EndCity = $"{endCity.Prefix} {endCity.Name}";
                    }
                }
            }

            return result;
        }
    }
}
