using PowderCoating.Core.Entities;
using PowderCoating.Core.Interfaces;

namespace PowderCoating.Application.Services;

/// <summary>
/// Provides cached access to application lookup tables (job statuses,
/// job priorities, quote statuses, appointment statuses/types) to avoid
/// repeated database round-trips on every page load.
/// <para>
/// Lookup data is essentially static for normal operation — statuses and
/// priorities are only changed by SuperAdmins during platform configuration.
/// A 1-hour TTL is therefore appropriate, with <see cref="InvalidateCompanyCache"/>
/// available for immediate invalidation after any admin change.
/// </para>
/// </summary>
public interface ILookupCacheService
{
    /// <summary>Returns all job status lookup rows, from cache if available.</summary>
    /// <param name="companyId">Tenant company ID used to scope the cache key.</param>
    Task<IEnumerable<JobStatusLookup>> GetJobStatusLookupsAsync(int companyId);

    /// <summary>Returns all job priority lookup rows, from cache if available.</summary>
    /// <param name="companyId">Tenant company ID used to scope the cache key.</param>
    Task<IEnumerable<JobPriorityLookup>> GetJobPriorityLookupsAsync(int companyId);

    /// <summary>Returns all quote status lookup rows, from cache if available.</summary>
    /// <param name="companyId">Tenant company ID used to scope the cache key.</param>
    Task<IEnumerable<QuoteStatusLookup>> GetQuoteStatusLookupsAsync(int companyId);

    /// <summary>Returns all appointment status lookup rows, from cache if available.</summary>
    /// <param name="companyId">Tenant company ID used to scope the cache key.</param>
    Task<IEnumerable<AppointmentStatusLookup>> GetAppointmentStatusLookupsAsync(int companyId);

    /// <summary>Returns all appointment type lookup rows, from cache if available.</summary>
    /// <param name="companyId">Tenant company ID used to scope the cache key.</param>
    Task<IEnumerable<AppointmentTypeLookup>> GetAppointmentTypeLookupsAsync(int companyId);

    /// <summary>
    /// Removes all lookup cache entries for the given company.
    /// Call this after any SuperAdmin operation that modifies lookup tables so
    /// subsequent requests reload fresh data from the database.
    /// </summary>
    /// <param name="companyId">The company whose cached lookups should be purged.</param>
    void InvalidateCompanyCache(int companyId);
}

/// <summary>
/// Implementation of <see cref="ILookupCacheService"/> that delegates to
/// <see cref="ICachingService"/> for thread-safe get-or-create semantics and
/// to <see cref="IUnitOfWork"/> for database access.
/// <para>
/// Cache keys are scoped per company (e.g. <c>JobStatus_42</c>) even though
/// lookup tables are currently platform-wide.  The per-company scoping is
/// forward-compatible with future per-tenant lookup customisation and allows
/// <see cref="InvalidateCompanyCache"/> to operate at company granularity
/// without flushing other tenants' cached data.
/// </para>
/// </summary>
public class LookupCacheService : ILookupCacheService
{
    private readonly IUnitOfWork _unitOfWork;
    private readonly ICachingService _cachingService;

    /// <summary>
    /// Initialises the service with the unit-of-work (for DB access on misses)
    /// and the caching service (for in-memory storage).
    /// </summary>
    public LookupCacheService(IUnitOfWork unitOfWork, ICachingService cachingService)
    {
        _unitOfWork = unitOfWork;
        _cachingService = cachingService;
    }

    /// <summary>
    /// Returns all job status rows.  Cached for 1 hour because statuses change
    /// only when a platform admin updates the lookup table configuration.
    /// </summary>
    /// <param name="companyId">Tenant company ID — scopes the cache key.</param>
    public async Task<IEnumerable<JobStatusLookup>> GetJobStatusLookupsAsync(int companyId)
    {
        var cacheKey = $"JobStatus_{companyId}";
        return await _cachingService.GetOrCreateAsync(
            cacheKey,
            async () => await _unitOfWork.JobStatusLookups.GetAllAsync(),
            TimeSpan.FromHours(1) // Lookup data rarely changes
        );
    }

    /// <summary>
    /// Returns all job priority rows.  Cached for 1 hour for the same reason as
    /// <see cref="GetJobStatusLookupsAsync"/>.
    /// </summary>
    /// <param name="companyId">Tenant company ID — scopes the cache key.</param>
    public async Task<IEnumerable<JobPriorityLookup>> GetJobPriorityLookupsAsync(int companyId)
    {
        var cacheKey = $"JobPriority_{companyId}";
        return await _cachingService.GetOrCreateAsync(
            cacheKey,
            async () => await _unitOfWork.JobPriorityLookups.GetAllAsync(),
            TimeSpan.FromHours(1)
        );
    }

    /// <summary>
    /// Returns all quote status rows.  Cached for 1 hour.
    /// </summary>
    /// <param name="companyId">Tenant company ID — scopes the cache key.</param>
    public async Task<IEnumerable<QuoteStatusLookup>> GetQuoteStatusLookupsAsync(int companyId)
    {
        var cacheKey = $"QuoteStatus_{companyId}";
        return await _cachingService.GetOrCreateAsync(
            cacheKey,
            async () => await _unitOfWork.QuoteStatusLookups.GetAllAsync(),
            TimeSpan.FromHours(1)
        );
    }

    /// <summary>
    /// Returns all appointment status rows.  Cached for 1 hour.
    /// </summary>
    /// <param name="companyId">Tenant company ID — scopes the cache key.</param>
    public async Task<IEnumerable<AppointmentStatusLookup>> GetAppointmentStatusLookupsAsync(int companyId)
    {
        var cacheKey = $"AppointmentStatus_{companyId}";
        return await _cachingService.GetOrCreateAsync(
            cacheKey,
            async () => await _unitOfWork.AppointmentStatusLookups.GetAllAsync(),
            TimeSpan.FromHours(1)
        );
    }

    /// <summary>
    /// Returns all appointment type rows.  Cached for 1 hour.
    /// </summary>
    /// <param name="companyId">Tenant company ID — scopes the cache key.</param>
    public async Task<IEnumerable<AppointmentTypeLookup>> GetAppointmentTypeLookupsAsync(int companyId)
    {
        var cacheKey = $"AppointmentType_{companyId}";
        return await _cachingService.GetOrCreateAsync(
            cacheKey,
            async () => await _unitOfWork.AppointmentTypeLookups.GetAllAsync(),
            TimeSpan.FromHours(1)
        );
    }

    /// <summary>
    /// Purges all five lookup cache entries for <paramref name="companyId"/>.
    /// Each key is removed individually (rather than by prefix) because the
    /// key names are known statically, making this more explicit and efficient
    /// than a prefix scan of the key registry.
    /// </summary>
    /// <param name="companyId">The company whose cached lookups should be purged.</param>
    public void InvalidateCompanyCache(int companyId)
    {
        _cachingService.Remove($"JobStatus_{companyId}");
        _cachingService.Remove($"JobPriority_{companyId}");
        _cachingService.Remove($"QuoteStatus_{companyId}");
        _cachingService.Remove($"AppointmentStatus_{companyId}");
        _cachingService.Remove($"AppointmentType_{companyId}");
    }
}
