// EN: Queries for branch management.
// VI: Queries cho quản lý chi nhánh.

using MediatR;
using MerchantService.Domain.AggregatesModel.ShopAggregate;

namespace MerchantService.API.Application.Queries.Shops;

#region Get Branches Query

/// <summary>
/// EN: Query to get all branches of a shop.
/// VI: Query để lấy tất cả chi nhánh của shop.
/// </summary>
public record GetBranchesQuery(Guid ShopId) : IRequest<IReadOnlyList<ShopBranchDto>>;

/// <summary>
/// EN: Handler for GetBranchesQuery.
/// VI: Handler cho GetBranchesQuery.
/// </summary>
public class GetBranchesQueryHandler : IRequestHandler<GetBranchesQuery, IReadOnlyList<ShopBranchDto>>
{
    private readonly IShopRepository _shopRepository;

    public GetBranchesQueryHandler(IShopRepository shopRepository)
    {
        _shopRepository = shopRepository;
    }

    public async Task<IReadOnlyList<ShopBranchDto>> Handle(GetBranchesQuery request, CancellationToken cancellationToken)
    {
        var shop = await _shopRepository.GetByIdAsync(request.ShopId, cancellationToken);
        if (shop == null)
            return [];

        return shop.Branches.Select(b => new ShopBranchDto
        {
            Id = b.Id,
            Name = b.Name,
            Code = b.Code,
            Address = new AddressDto
            {
                Street = b.Address.Street,
                Ward = b.Address.Ward,
                District = b.Address.District,
                City = b.Address.City,
                Province = b.Address.Province,
                FullAddress = b.Address.FullAddress
            },
            Location = b.Location != null ? new GeoLocationDto
            {
                Latitude = b.Location.Latitude,
                Longitude = b.Location.Longitude
            } : null,
            Phone = b.Phone,
            IsActive = b.IsActive
        }).ToList();
    }
}

#endregion

#region Get Nearby Shops Query

/// <summary>
/// EN: Query to find shops with branches near a location.
/// VI: Query để tìm shops có chi nhánh gần vị trí.
/// </summary>
public record GetNearbyShopsQuery(
    double Latitude,
    double Longitude,
    double RadiusKm = 5.0,
    int Limit = 20) : IRequest<IReadOnlyList<NearbyShopDto>>;

/// <summary>
/// EN: DTO for nearby shop result.
/// VI: DTO cho kết quả tìm shop gần đây.
/// </summary>
public record NearbyShopDto
{
    public Guid ShopId { get; init; }
    public string Name { get; init; } = null!;
    public string Slug { get; init; } = null!;
    public string Category { get; init; } = null!;
    public string? LogoUrl { get; init; }
    public Guid BranchId { get; init; }
    public string BranchName { get; init; } = null!;
    public AddressDto Address { get; init; } = null!;
    public double DistanceKm { get; init; }
}

/// <summary>
/// EN: Handler for GetNearbyShopsQuery.
/// VI: Handler cho GetNearbyShopsQuery.
/// </summary>
public class GetNearbyShopsQueryHandler : IRequestHandler<GetNearbyShopsQuery, IReadOnlyList<NearbyShopDto>>
{
    private readonly IShopRepository _shopRepository;

    public GetNearbyShopsQueryHandler(IShopRepository shopRepository)
    {
        _shopRepository = shopRepository;
    }

    public async Task<IReadOnlyList<NearbyShopDto>> Handle(GetNearbyShopsQuery request, CancellationToken cancellationToken)
    {
        // EN: Get all active shops with their branches
        // VI: Lấy tất cả shops active với các chi nhánh
        var shops = await _shopRepository.GetActiveShopsWithBranchesAsync(cancellationToken);
        
        var results = new List<NearbyShopDto>();

        foreach (var shop in shops)
        {
            foreach (var branch in shop.Branches.Where(b => b.IsActive && b.Location != null))
            {
                var distance = CalculateDistance(
                    request.Latitude, request.Longitude,
                    branch.Location!.Latitude, branch.Location.Longitude);

                if (distance <= request.RadiusKm)
                {
                    results.Add(new NearbyShopDto
                    {
                        ShopId = shop.Id,
                        Name = shop.Name,
                        Slug = shop.Slug,
                        Category = shop.Category.Name,
                        LogoUrl = shop.LogoUrl,
                        BranchId = branch.Id,
                        BranchName = branch.Name,
                        Address = new AddressDto
                        {
                            Street = branch.Address.Street,
                            Ward = branch.Address.Ward,
                            District = branch.Address.District,
                            City = branch.Address.City,
                            Province = branch.Address.Province,
                            FullAddress = branch.Address.FullAddress
                        },
                        DistanceKm = Math.Round(distance, 2)
                    });
                }
            }
        }

        return results
            .OrderBy(r => r.DistanceKm)
            .Take(request.Limit)
            .ToList();
    }

    /// <summary>
    /// EN: Calculate distance between two coordinates using Haversine formula.
    /// VI: Tính khoảng cách giữa hai tọa độ sử dụng công thức Haversine.
    /// </summary>
    private static double CalculateDistance(double lat1, double lon1, double lat2, double lon2)
    {
        const double R = 6371; // Earth's radius in km

        var dLat = ToRadians(lat2 - lat1);
        var dLon = ToRadians(lon2 - lon1);

        var a = Math.Sin(dLat / 2) * Math.Sin(dLat / 2) +
                Math.Cos(ToRadians(lat1)) * Math.Cos(ToRadians(lat2)) *
                Math.Sin(dLon / 2) * Math.Sin(dLon / 2);

        var c = 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1 - a));
        return R * c;
    }

    private static double ToRadians(double degrees) => degrees * Math.PI / 180;
}

#endregion
