using Lada.Framework.Data.Domains;
using Lada.Framework.Data.Domains.Orders;
using Lada.Framework.Data.Enums;
using Lada.Framework.Data.Repositories;
using Lada.Framework.DTO;
using Lada.Framework.DTO.SmartTable;
using Lada.Framework.Infrastructures.Helpers;
using Ladaer.BackEnd.Infrastructures.DTO;
using Ladaer.BackEnd.Infrastructures.DTO.Items.Business.Operations;
using Ladaer.BackEnd.Infrastructures.DTO.Items.Identity;
using Ladaer.BackEnd.Infrastructures.DTO.Models.Business.Operations;
using Ladaer.BackEnd.Infrastructures.DTO.Models.Orders;
using Ladaer.BackEnd.Infrastructures.DTO.Models.Orders.Updates;
using Ladaer.BackEnd.Infrastructures.DTO.Results.Orders;
using Ladaer.BackEnd.Infrastructures.Services.Categories;
using Ladaer.BackEnd.Infrastructures.Services.Identity;
using Lada.Framework.Infrastructures.Services;
using Ladaer.BackEnd.Infrastructures.Services.Orders;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json.Linq;
using Xabe.FFmpeg;
using Dapper;
using System.Data;

namespace Ladaer.BackEnd.Infrastructures.Services.Business
{
    /// <summary>
    /// Interface for operation service.
    /// </summary>
    public interface IOperationService
    {
        /// <summary>
        /// Searches for orders based on the provided parameters.
        /// </summary>
        /// <param name="param">Search parameters.</param>
        /// <param name="userId">User ID.</param>
        /// <returns>Search results.</returns>
        Task<SmartTableResult<OpsOrderItem>> Search(SmartTableParam param, int userId);

        /// <summary>
        /// Gets an order by its ID.
        /// </summary>
        /// <param name="id">Order ID.</param>
        /// <param name="userId">User ID.</param>
        /// <returns>Order item.</returns>
        Task<OpsOrderItem> GetById(int id, int userId);

        // Các chức năng này đã được di chuyển sang DriverAppOrderService
        // để tách biệt logic giữa Business Operations và Driver App

        /// <summary>
        /// Picks up an order.
        /// </summary>
        /// <param name="model">Pickup model.</param>
        /// <returns>Order track result.</returns>
        Task<OrderTrackResult> Pickup(PickupModel model);

        /// <summary>
        /// Giao hàng thành công
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        Task<OrderTrackResult> Delivery(DeliveryModel model);
    }


    /// <summary>
    /// Implementation of the operation service.
    /// </summary>
    public class OperationService : IOperationService
    {
        private readonly IRepository<OrderOps> _orderOpsRepository;
        private readonly IRepository<OrderFin> _orderFinRepository;
        private readonly IRepository<Order> _orderRepository;
        private readonly ILocationService _locationService;
        private readonly IUserService _userService;
        private readonly IUnifiedMediaService _unifiedMediaService;
        private readonly IOrderTrackService _orderTrackService;
        private readonly IDbConnection _dbConnection;

        /// <summary>
        /// Initializes a new instance of the <see cref="OperationService"/> class.
        /// </summary>
        /// <param name="locationService">Location service.</param>
        /// <param name="userService">User service.</param>
        /// <param name="orderOpsRepository">Order operations repository.</param>
        /// <param name="orderFinRepository">Order financial repository.</param>
        /// <param name="orderTrackService">Order track service.</param>
        /// <param name="orderRepository">Order repository.</param>
        /// <param name="unifiedMediaService">Unified media service.</param>
        /// <param name="dbConnection">Database connection.</param>
        public OperationService(ILocationService locationService, IUserService userService,
            IRepository<OrderOps> orderOpsRepository,
            IRepository<OrderFin> orderFinRepository,
            IOrderTrackService orderTrackService,
            IRepository<Order> orderRepository, IUnifiedMediaService unifiedMediaService,
            IDbConnection dbConnection)
        {
            _orderOpsRepository = orderOpsRepository;
            _orderFinRepository = orderFinRepository;
            _orderRepository = orderRepository;
            _locationService = locationService;
            _userService = userService;
            _unifiedMediaService = unifiedMediaService;
            _orderTrackService = orderTrackService;
            _dbConnection = dbConnection;
        }

        /// <summary>
        /// Searches for orders based on the provided parameters.
        /// </summary>
        /// <param name="param">Search parameters.</param>
        /// <param name="userId">User ID.</param>
        /// <returns>Search results.</returns>
        public async Task<SmartTableResult<OpsOrderItem>> Search(SmartTableParam param, int userId)
        {
            SmartTableResult<OpsOrderItem> gridData = new SmartTableResult<OpsOrderItem>();
            var query = _orderOpsRepository.Query().Where(o => o.PicId == userId 
            && o.Status != OrderStatus.Delivered && o.Status != OrderStatus.Cancelled);
            if (param != null)
            {
                if (param.Search != null)
                {
                    if (param.Search.PredicateObject != null)
                    {
                        dynamic search = param.Search.PredicateObject;
                        if (search.Keyword != null)
                        {
                            string keyword = search.Keyword;
                            keyword = keyword.Trim();
                            if (!string.IsNullOrWhiteSpace(keyword))
                                query = query.Where(x => x.Order.Code.Contains(keyword));
                        }
                        if (search.OrderStatuses != null)
                        {
                            var statusIds = (JArray)search.OrderStatuses;
                            if (statusIds.Any())
                            {
                                var ids = statusIds.ToObject<List<OrderStatus>>();
                                query = query.Where(x => ids.Contains(x.Status));
                            }
                            
                        }
                        if (search.OrderPhysicalStatuses != null)
                        {
                            var physicalStatusIds = (JArray)search.OrderPhysicalStatuses;
                            if (physicalStatusIds.Any())
                            {
                                var ids = physicalStatusIds.ToObject<List<OrderPhysicalStatus>>();
                                query = query.Where(x => ids.Contains(x.PhysicalStatus));
                            }
                        }
                    }
                }

                param.Sort = new Sort() { Predicate = "DisplayOrder" };
                gridData = await query.Include(o => o.Order.OrderFin)
                .Include(o => o.Order.Addresses)
                .Include(o => o.Order.Packages)
                .ToSmartTableResultAsync(param, x => x.ToOpsOrderItem());
                if (gridData.Items.Any())
                {
                    var addresses = gridData.Items.SelectMany(i => i.Addresses).ToList();
                    var wardIds = addresses.Where(a => a.WardId > 0).Select(a => a.WardId).Distinct().ToList();
                    var fullAddresss = await _locationService.GetFullAddress(wardIds, 3);

                    var saleIds = gridData.Items.Select(i => i.SaleId).ToList();
                    var saleItems = new List<StaffItem>();
                    if (saleIds != null && saleIds.Any())
                    {
                        saleItems = await _userService.GetByListIdAsync(saleIds);
                    }

                    foreach (var order in gridData.Items)
                    {
                        if (order.SaleId > 0)
                        {
                            var sale = saleItems.FirstOrDefault(s => s.Id == order.SaleId);
                            if (sale != null && sale.Id > 0)
                            {
                                order.SaleUserName = sale.UserName;
                            }
                        }
                        int addIndex = 1;
                        foreach (var address in order.Addresses)
                        {
                            if (address.WardId > 0)
                            {
                                var fullAddress = fullAddresss.FirstOrDefault(f => f.Id == address.WardId);
                                if (fullAddress != null && fullAddress.Id > 0)
                                {
                                    address.FullAddress = $"{address.Address1}, {fullAddress.FullAddress}";
                                    if (addIndex == 1)
                                    {
                                        order.RouteName += fullAddress.CityName;
                                    }
                                    else
                                    {
                                        order.RouteName += $" - {fullAddress.CityName}";
                                    }
                                    addIndex++;
                                }
                            }
                        }
                    }
                }
            }
            return gridData;
        }

        /// <summary>
        /// Gets an order by its ID.
        /// </summary>
        /// <param name="id">Order ID.</param>
        /// <param name="userId">User ID.</param>
        /// <returns>Order item.</returns>
        public async Task<OpsOrderItem> GetById(int id, int userId)
        {
            var result = new OpsOrderItem();
            var query = _orderOpsRepository.Query().Where(o => o.PicId == userId && o.Id == id);
            result = await query.Include(o => o.Order.OrderFin)
                .Include(o => o.Order.Addresses)
                .Include(o => o.Order.Packages)
                .Select(o => o.ToOpsOrderItem())
                .FirstOrDefaultAsync();
            if (result != null && result.Id > 0)
            {
                var addresses = result.Addresses;
                var wardIds = addresses.Where(a => a.WardId > 0).Select(a => a.WardId).Distinct().ToList();
                var fullAddresss = await _locationService.GetFullAddress(wardIds, 3);

                var saleId = result.SaleId;

                if (result.SaleId > 0)
                {
                    var sale = await _userService.GetByIdAsync(saleId);
                    if (sale != null && sale.Id > 0)
                    {
                        result.SaleUserName = sale.UserName;
                    }
                }
                int addIndex = 1;
                foreach (var address in result.Addresses)
                {
                    if (address.WardId > 0)
                    {
                        var fullAddress = fullAddresss.FirstOrDefault(f => f.Id == address.WardId);
                        if (fullAddress != null && fullAddress.Id > 0)
                        {
                            address.FullAddress = $"{address.Address1}, {fullAddress.FullAddress}";
                            if (addIndex == 1)
                            {
                                result.RouteName += fullAddress.CityName;
                            }
                            else
                            {
                                result.RouteName += $" - {fullAddress.CityName}";
                            }
                            addIndex++;
                        }
                    }
                }
            }
            else
            {
                result = new OpsOrderItem();
            }
            return result;
        }
        /// <summary>
        /// Picks up an order.
        /// </summary>
        /// <param name="model">Pickup model.</param>
        /// <returns>Order track result.</returns>
        public async Task<OrderTrackResult> Pickup(PickupModel model)
        {
            var result = new OrderTrackResult();
            
            // 1. Validate input
            if (model.Id <= 0 || model.UpdatedBy <= 0 || string.IsNullOrEmpty(model.UpdatedByUserName))
            {
                result.Result = Result.Failed;
                result.Message = "Thông tin đầu vào không hợp lệ";
                return result;
            }

            try
            {
                // 2. Validate business rules (C# validation)
                var order = await _orderOpsRepository.Query().FirstOrDefaultAsync(c => c.Id == model.Id && c.PicId == model.UpdatedBy);
                if (order == null || order.Id <= 0)
                {
                    result.Result = Result.Failed;
                    result.Message = "Không tìm thấy đơn hàng yêu cầu";
                    return result;
                }

                // Validate status transition
                var validUpdateStatus = ValidateUpdateStatus(order.Status, order.PhysicalStatus, OrderStatus.Received, OrderPhysicalStatus.Picked);
                if (!validUpdateStatus)
                {
                    result.Result = Result.Failed;
                    result.Message = $"Trạng thái cập nhật không hợp lệ, trạng thái hiện tại đang là {order.Status.GetDescription()} và {order.PhysicalStatus.GetDescription()}";
                    return result;
                }

                // Get agency ID
                var agencyId = await _userService.GetAgencyIdByIdAsync(model.UpdatedBy);
                if (agencyId <= 0)
                {
                    result.Result = Result.Failed;
                    result.Message = "Không xác định được đại lý của người dùng";
                    return result;
                }

                // 3. Call stored procedure for atomic update
                var parameters = new DynamicParameters();
                parameters.Add("@OrderId", model.Id);
                parameters.Add("@PicId", model.UpdatedBy);
                parameters.Add("@PicUserName", model.UpdatedByUserName);
                parameters.Add("@AgencyId", agencyId);
                parameters.Add("@Result", dbType: DbType.Int32, direction: ParameterDirection.Output);
                parameters.Add("@Message", dbType: DbType.String, direction: ParameterDirection.Output, size: 500);
                parameters.Add("@OrderTrackId", dbType: DbType.Guid, direction: ParameterDirection.Output);

                await _dbConnection.ExecuteAsync("SP_UpdateOrderPickup", parameters, commandType: CommandType.StoredProcedure);

                // 4. Get results from stored procedure
                var spResult = parameters.Get<int>("@Result");
                var spMessage = parameters.Get<string>("@Message");
                var orderTrackId = parameters.Get<Guid>("@OrderTrackId");

                result.Result = (Result)spResult;
                result.Message = spMessage;
                result.Id = orderTrackId;

                // 5. Handle media if SP was successful and media exists
                if (result.Result == Result.Success && model.MediaList != null && model.MediaList.Any())
                {
                    // Get order code for media processing
                    var orderCommon = await _orderRepository.Query().FirstOrDefaultAsync(c => c.Id == model.Id);
                    if (orderCommon != null)
                    {
                        var mediaResults = await _unifiedMediaService.SaveOrderTrackMediaAsync(
                            model.MediaList, result.Id, model.Id, orderCommon.Code, 
                            model.UpdatedBy, model.UpdatedByUserName);

                        // Check for media save errors
                        var failedResult = mediaResults.FirstOrDefault(r => r.Result != Lada.Framework.DTO.Result.Success);
                        if (failedResult != null)
                        {
                            result.Result = Lada.Framework.DTO.Result.SystemError;
                            result.Message = $"Lỗi lưu ảnh: {failedResult.Message}";
                            return result;
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                result.Result = Result.SystemError;
                result.Message = $"Lỗi hệ thống: {ex.Message}";
            }

            return result;
        }


        /// <summary>
        /// Giao hàng.
        /// </summary>
        /// <param name="model">model.</param>
        /// <returns>Order track result.</returns>
        public async Task<OrderTrackResult> Delivery(DeliveryModel model)
        {
            var result = new OrderTrackResult();
            
            // 1. Validate input
            if (model.Id <= 0 || model.UpdatedBy <= 0 || string.IsNullOrEmpty(model.UpdatedByUserName))
            {
                result.Result = Result.Failed;
                result.Message = "Thông tin đầu vào không hợp lệ";
                return result;
            }

            try
            {
                // 2. Validate business rules (C# validation)
                var order = await _orderOpsRepository.Query().FirstOrDefaultAsync(c => c.Id == model.Id && c.PicId == model.UpdatedBy);
                if (order == null || order.Id <= 0)
                {
                    result.Result = Result.Failed;
                    result.Message = "Không tìm thấy đơn hàng yêu cầu";
                    return result;
                }

                // Validate status transition
                var validUpdateStatus = ValidateUpdateStatus(order.Status, order.PhysicalStatus, OrderStatus.Delivered, OrderPhysicalStatus.Delivered);
                if (!validUpdateStatus)
                {
                    result.Result = Result.Failed;
                    result.Message = $"Trạng thái cập nhật không hợp lệ, trạng thái hiện tại đang là {order.Status.GetDescription()} và {order.PhysicalStatus.GetDescription()}";
                    return result;
                }

                // Get agency ID
                var agencyId = await _userService.GetAgencyIdByIdAsync(model.UpdatedBy);
                if (agencyId <= 0)
                {
                    result.Result = Result.Failed;
                    result.Message = "Không xác định được đại lý của người dùng";
                    return result;
                }

                // 3. Call stored procedure for atomic update
                var parameters = new DynamicParameters();
                parameters.Add("@OrderId", model.Id);
                parameters.Add("@PicId", model.UpdatedBy);
                parameters.Add("@PicUserName", model.UpdatedByUserName);
                parameters.Add("@AgencyId", agencyId);
                parameters.Add("@Result", dbType: DbType.Int32, direction: ParameterDirection.Output);
                parameters.Add("@Message", dbType: DbType.String, direction: ParameterDirection.Output, size: 500);
                parameters.Add("@OrderTrackId", dbType: DbType.Guid, direction: ParameterDirection.Output);

                await _dbConnection.ExecuteAsync("SP_UpdateOrderDelivery", parameters, commandType: CommandType.StoredProcedure);

                // 4. Get results from stored procedure
                var spResult = parameters.Get<int>("@Result");
                var spMessage = parameters.Get<string>("@Message");
                var orderTrackId = parameters.Get<Guid>("@OrderTrackId");

                result.Result = (Result)spResult;
                result.Message = spMessage;
                result.Id = orderTrackId;

                // 5. Handle media if SP was successful and media exists
                if (result.Result == Result.Success && model.MediaList != null && model.MediaList.Any())
                {
                    // Get order code for media processing
                    var orderCommon = await _orderRepository.Query().FirstOrDefaultAsync(c => c.Id == model.Id);
                    if (orderCommon != null)
                    {
                        var mediaResults = await _unifiedMediaService.SaveOrderTrackMediaAsync(
                            model.MediaList, result.Id, model.Id, orderCommon.Code, 
                            model.UpdatedBy, model.UpdatedByUserName);

                        // Check for media save errors
                        var failedResult = mediaResults.FirstOrDefault(r => r.Result != Lada.Framework.DTO.Result.Success);
                        if (failedResult != null)
                        {
                            result.Result = Lada.Framework.DTO.Result.SystemError;
                            result.Message = $"Lỗi lưu ảnh: {failedResult.Message}";
                            return result;
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                result.Result = Result.SystemError;
                result.Message = $"Lỗi hệ thống: {ex.Message}";
            }

            return result;
        }
        // Các method Pickup, PickupFail, Delivery, DeliveryFail đã được di chuyển sang DriverAppOrderService
        // để tách biệt logic giữa Business Operations và Driver App
        // 
        // Lý do di chuyển:
        // - OperationService: Xử lý logic nghiệp vụ kinh doanh (Business Operations)
        // - DriverAppOrderService: Xử lý logic cho ứng dụng lái xe (Driver App)
        // 
        // Việc tách biệt này giúp:
        // 1. Tách biệt rõ ràng trách nhiệm của từng service
        // 2. Dễ dàng bảo trì và mở rộng
        // 3. Tránh phụ thuộc chéo giữa các module

        #region Utilities
        /// <summary>
        /// Validates the update status of an order.
        /// </summary>
        /// <param name="currentStatus">Current order status.</param>
        /// <param name="currentPhysicalStatus">Current physical status.</param>
        /// <param name="newStatus">New order status.</param>
        /// <param name="newPhysicalStatus">New physical status.</param>
        /// <returns>True if the update is valid, otherwise false.</returns>
        private bool ValidateUpdateStatus(OrderStatus currentStatus, OrderPhysicalStatus currentPhysicalStatus, OrderStatus newStatus, OrderPhysicalStatus newPhysicalStatus)
        {
            var result = false;
            switch (newStatus)
            {
                case OrderStatus.New:
                    if (currentStatus == OrderStatus.Cancelled && currentPhysicalStatus == OrderPhysicalStatus.Cancelled)
                    {
                        result = true;
                    }
                    break;
                case OrderStatus.Cancelled:
                    if (currentStatus == OrderStatus.New &&
                        (currentPhysicalStatus == OrderPhysicalStatus.WaitToPickup ||
                        currentPhysicalStatus == OrderPhysicalStatus.OnPicking))
                    {
                        result = true;
                    }
                    break;
                case OrderStatus.Received:
                    if ((currentStatus == OrderStatus.New || currentStatus == OrderStatus.PickupFail)
                        && (currentPhysicalStatus == OrderPhysicalStatus.WaitToPickup || currentPhysicalStatus == OrderPhysicalStatus.OnPicking))
                    {
                        result = true;
                    }
                    break;
                case OrderStatus.PickupFail:
                    if ((currentStatus == OrderStatus.New || currentStatus == OrderStatus.PickupFail)
                        && (currentPhysicalStatus == OrderPhysicalStatus.WaitToPickup || currentPhysicalStatus == OrderPhysicalStatus.OnPicking))
                    {
                        result = true;
                    }
                    break;
                case OrderStatus.DeliveryFail:
                    if ((currentStatus == OrderStatus.Received || currentStatus == OrderStatus.DeliveryFail)
                        && (currentPhysicalStatus == OrderPhysicalStatus.OnDelivery || currentPhysicalStatus == OrderPhysicalStatus.OutboundForDelivery))
                    {
                        result = true;
                    }
                    break;
                case OrderStatus.Delivered:
                    if ((currentStatus == OrderStatus.Received || currentStatus == OrderStatus.DeliveryFail)
                        && (currentPhysicalStatus == OrderPhysicalStatus.OnDelivery || currentPhysicalStatus == OrderPhysicalStatus.OutboundForDelivery))
                    {
                        result = true;
                    }
                    break;

                case OrderStatus.WaitToReturn:
                    break;
                case OrderStatus.ReturnFail:
                    break;
                case OrderStatus.Returned:
                    break;
                case OrderStatus.Unknown:
                    break;
                default:
                    break;
            }
            return result;
        }
        #endregion
    }
}
