using Baya.Application.Contracts.Common;
using Baya.Application.Contracts.Search;
using Baya.Domain.Entities.Catalog;
using Baya.Domain.Entities.Geography;
using Baya.Domain.Entities.Identity;
using Baya.Domain.Entities.User;
using Baya.Domain.Entities.Verification;
using Baya.Infrastructure.Persistence;
using Baya.Infrastructure.Persistence.Services.Search;
using Baya.Tests.Setup.Setups;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using NSubstitute;

namespace Baya.Test.Foundation.Search;

/// <summary>
/// A self-contained SQLite host for the search-index maintainer + SqlNurseSearch, exercising the real EF
/// model (schema, filtered indexes, query filters) end-to-end. Seeds a shared province/city/two districts
/// and one active service category; <see cref="SeedNurse"/> builds a full nurse (user + profile +
/// verification + variant + area) so a test can drive the maintainer and assert what search returns.
/// </summary>
public sealed class SearchIndexTestHost : IDisposable
{
    public static readonly DateTimeOffset Now = new(2026, 7, 5, 12, 0, 0, TimeSpan.Zero);

    private readonly SqliteConnection _connection;
    public ApplicationDbContext Db { get; }
    public ISearchIndexMaintainer Maintainer { get; }
    public INurseSearch Search { get; }

    public long CategoryId { get; }
    public long OtherCategoryId { get; }
    public long CityId { get; }
    public long District3Id { get; }
    public long District7Id { get; }

    private int _phoneSeed = 90000000;

    public SearchIndexTestHost()
    {
        _connection = new SqliteConnection("DataSource=:memory:");
        _connection.Open();

        var options = new DbContextOptionsBuilder<ApplicationDbContext>()
            .UseSqlite(_connection)
            .Options;

        Db = new ApplicationDbContext(options, TestFieldEncryptor.Instance);
        Db.Database.EnsureCreated();

        var clock = Substitute.For<IDateTimeProvider>();
        clock.UtcNow.Returns(Now);
        Maintainer = new SearchIndexMaintainer(Db, clock);
        Search = new SqlNurseSearch(Db);

        var province = new Province { NameFa = "تهران", NameEn = "Tehran", SortOrder = 1, IsActive = true };
        Db.Set<Province>().Add(province);
        Db.SaveChanges();

        var city = new City { ProvinceId = province.Id, NameFa = "تهران", NameEn = "Tehran", SortOrder = 1, IsActive = true };
        Db.Set<City>().Add(city);
        Db.SaveChanges();
        CityId = city.Id;

        var d3 = new District { CityId = city.Id, NameFa = "منطقه ۳", NameEn = "District 3", SortOrder = 3, IsActive = true };
        var d7 = new District { CityId = city.Id, NameFa = "منطقه ۷", NameEn = "District 7", SortOrder = 7, IsActive = true };
        Db.Set<District>().AddRange(d3, d7);
        Db.SaveChanges();
        District3Id = d3.Id;
        District7Id = d7.Id;

        var category = new ServiceCategory { NameFa = "سالمند", NameEn = "Elderly", SortOrder = 1, IsActive = true };
        var other = new ServiceCategory { NameFa = "نوزاد", NameEn = "Infant", SortOrder = 2, IsActive = true };
        Db.Set<ServiceCategory>().AddRange(category, other);
        Db.SaveChanges();
        CategoryId = category.Id;
        OtherCategoryId = other.Id;
    }

    public sealed record SeededNurse(long NurseId, NurseProfile Profile, NurseVerification Verification, NurseServiceVariant Variant, NurseServiceArea Area);

    /// <summary>Seeds one nurse with a single active variant + a single service area, in a chosen bookability
    /// state, and does NOT project it yet (the caller drives the maintainer).</summary>
    public SeededNurse SeedNurse(
        string gender,
        bool verified,
        bool accepting,
        VerificationStatus status,
        long price,
        long? districtId,
        decimal averageRating = 0m,
        int totalReviews = 0,
        long? categoryId = null,
        bool variantActive = true,
        string priceUnit = "per_day")
    {
        var user = new User
        {
            UserName = $"nurse{_phoneSeed}",
            PhoneNumber = $"0912{_phoneSeed++}",
            Gender = gender,
            IsActive = true
        };
        Db.Users.Add(user);
        Db.SaveChanges();

        var profile = new NurseProfile { UserId = user.Id };
        if (verified)
            profile.MarkVerified();
        profile.SetAcceptingBookings(accepting);
        Db.Set<NurseProfile>().Add(profile);
        SetAggregates(profile, averageRating, totalReviews);
        Db.SaveChanges();

        var verification = new NurseVerification { NurseId = profile.Id, Status = status };
        Db.Set<NurseVerification>().Add(verification);
        Db.SaveChanges();

        var variant = new NurseServiceVariant
        {
            NurseId = profile.Id,
            ServiceCategoryId = categoryId ?? CategoryId,
            Price = price,
            PriceUnit = priceUnit,
            SessionCount = null,
            DisplayName = "variant",
            OptionSetHash = $"hash-{profile.Id}",
            IsActive = variantActive
        };
        Db.Set<NurseServiceVariant>().Add(variant);
        Db.SaveChanges();

        var area = new NurseServiceArea { NurseId = profile.Id, CityId = CityId, DistrictId = districtId, IsActive = true };
        Db.Set<NurseServiceArea>().Add(area);
        Db.SaveChanges();

        return new SeededNurse(profile.Id, profile, verification, variant, area);
    }

    // The aggregate setters on NurseProfile are private (recomputed by b9/b14). Tests seed them via EF's
    // backing fields so a nurse can carry a rating without the (not-yet-built) review pipeline.
    private void SetAggregates(NurseProfile profile, decimal averageRating, int totalReviews)
    {
        var entry = Db.Entry(profile);
        entry.Property(nameof(NurseProfile.AverageRating)).CurrentValue = averageRating;
        entry.Property(nameof(NurseProfile.TotalReviews)).CurrentValue = totalReviews;
    }

    /// <summary>Adds a real service-area row (as the b4 handler would) so a later fan-out and a full rebuild
    /// derive from the same source.</summary>
    public NurseServiceArea AddArea(long nurseId, long? districtId)
    {
        var area = new NurseServiceArea { NurseId = nurseId, CityId = CityId, DistrictId = districtId, IsActive = true };
        Db.Set<NurseServiceArea>().Add(area);
        Db.SaveChanges();
        return area;
    }

    public int LiveRowCount() => Db.Set<Domain.Entities.Search.NurseSearchIndex>().Count();

    public int SearchableRowCount() =>
        Db.Set<Domain.Entities.Search.NurseSearchIndex>().Count(r => r.IsSearchable);

    public void Dispose()
    {
        Db.Dispose();
        _connection.Dispose();
    }
}
