using Baya.Application.Contracts.Persistence;
using Baya.Application.Models.Geography;
using Baya.Domain.Entities.Geography;
using Microsoft.EntityFrameworkCore;

namespace Baya.Infrastructure.Persistence.Repositories;

internal sealed class GeoRepository : IGeoRepository
{
    private readonly ApplicationDbContext _db;

    public GeoRepository(ApplicationDbContext db) => _db = db;

    public async Task<IReadOnlyList<ProvinceDto>> ListActiveProvincesAsync(CancellationToken cancellationToken)
        => await _db.Set<Province>()
            .AsNoTracking()
            .Where(p => p.IsActive)
            .OrderBy(p => p.SortOrder)
            .Select(p => new ProvinceDto(p.Id, p.NameFa, p.NameEn, p.SortOrder))
            .ToListAsync(cancellationToken);

    public async Task<IReadOnlyList<CityDto>> ListActiveCitiesAsync(long provinceId, CancellationToken cancellationToken)
        => await _db.Set<City>()
            .AsNoTracking()
            .Where(c => c.ProvinceId == provinceId && c.IsActive && c.Province.IsActive)
            .OrderBy(c => c.SortOrder)
            .Select(c => new CityDto(c.Id, c.ProvinceId, c.NameFa, c.NameEn, c.SortOrder))
            .ToListAsync(cancellationToken);

    public async Task<IReadOnlyList<DistrictDto>> ListActiveDistrictsAsync(long cityId, CancellationToken cancellationToken)
        => await _db.Set<District>()
            .AsNoTracking()
            .Where(d => d.CityId == cityId && d.IsActive && d.City.IsActive && d.City.Province.IsActive)
            .OrderBy(d => d.SortOrder)
            .Select(d => new DistrictDto(d.Id, d.CityId, d.NameFa, d.NameEn, d.SortOrder))
            .ToListAsync(cancellationToken);

    public async Task<IReadOnlyList<ProvinceTreeDto>> GetActiveTreeAsync(CancellationToken cancellationToken)
    {
        // Three flat ordered reads assembled in memory — avoids a two-level nested collection projection
        // (which SQLite can't translate) and keeps the payload cheap for a cached, near-static tree.
        var provinces = await _db.Set<Province>()
            .AsNoTracking()
            .Where(p => p.IsActive)
            .OrderBy(p => p.SortOrder)
            .Select(p => new { p.Id, p.NameFa, p.NameEn, p.SortOrder })
            .ToListAsync(cancellationToken);

        var cities = await _db.Set<City>()
            .AsNoTracking()
            .Where(c => c.IsActive && c.Province.IsActive)
            .OrderBy(c => c.SortOrder)
            .Select(c => new { c.Id, c.ProvinceId, c.NameFa, c.NameEn, c.SortOrder })
            .ToListAsync(cancellationToken);

        var districts = await _db.Set<District>()
            .AsNoTracking()
            .Where(d => d.IsActive && d.City.IsActive && d.City.Province.IsActive)
            .OrderBy(d => d.SortOrder)
            .Select(d => new DistrictDto(d.Id, d.CityId, d.NameFa, d.NameEn, d.SortOrder))
            .ToListAsync(cancellationToken);

        var districtsByCity = districts
            .GroupBy(d => d.CityId)
            .ToDictionary(g => g.Key, g => (IReadOnlyList<DistrictDto>)g.ToList());

        var citiesByProvince = cities
            .GroupBy(c => c.ProvinceId)
            .ToDictionary(
                g => g.Key,
                g => (IReadOnlyList<CityTreeDto>)g
                    .Select(c => new CityTreeDto(
                        c.Id,
                        c.NameFa,
                        c.NameEn,
                        c.SortOrder,
                        districtsByCity.TryGetValue(c.Id, out var ds) ? ds : []))
                    .ToList());

        return provinces
            .Select(p => new ProvinceTreeDto(
                p.Id,
                p.NameFa,
                p.NameEn,
                p.SortOrder,
                citiesByProvince.TryGetValue(p.Id, out var cs) ? cs : []))
            .ToList();
    }

    public Task<Province> GetProvinceAsync(long id, CancellationToken cancellationToken)
        => _db.Set<Province>().FirstOrDefaultAsync(p => p.Id == id, cancellationToken);

    public Task<City> GetCityAsync(long id, CancellationToken cancellationToken)
        => _db.Set<City>().FirstOrDefaultAsync(c => c.Id == id, cancellationToken);

    public Task<District> GetDistrictAsync(long id, CancellationToken cancellationToken)
        => _db.Set<District>().FirstOrDefaultAsync(d => d.Id == id, cancellationToken);

    public async Task AddProvinceAsync(Province province, CancellationToken cancellationToken)
        => await _db.Set<Province>().AddAsync(province, cancellationToken);

    public async Task AddCityAsync(City city, CancellationToken cancellationToken)
        => await _db.Set<City>().AddAsync(city, cancellationToken);

    public async Task AddDistrictAsync(District district, CancellationToken cancellationToken)
        => await _db.Set<District>().AddAsync(district, cancellationToken);

    public Task<bool> ProvinceExistsAsync(long provinceId, CancellationToken cancellationToken)
        => _db.Set<Province>().AsNoTracking().AnyAsync(p => p.Id == provinceId, cancellationToken);

    public Task<bool> IsCityActiveAsync(long cityId, CancellationToken cancellationToken)
        => _db.Set<City>().AsNoTracking()
            .AnyAsync(c => c.Id == cityId && c.IsActive && c.Province.IsActive, cancellationToken);

    public Task<bool> IsDistrictInActiveCityAsync(long districtId, long cityId, CancellationToken cancellationToken)
        => _db.Set<District>().AsNoTracking()
            .AnyAsync(
                d => d.Id == districtId && d.CityId == cityId &&
                     d.IsActive && d.City.IsActive && d.City.Province.IsActive,
                cancellationToken);
}
