using Microsoft.Extensions.DependencyInjection;
using Npgsql;
using Tiku.Application.Tenancy;
using Tiku.Domain.Tenancy;
using Tiku.Infrastructure.Caching;
using ZiggyCreatures.Caching.Fusion;

namespace Tiku.Infrastructure.Tenancy;

internal sealed record TenantDirectoryCacheEntry(bool Found, TenantDirectoryEntry? Entry);

public sealed class TenantDirectory(
    NpgsqlDataSource dataSource,
    [FromKeyedServices(BusinessCachingServiceCollectionExtensions.CacheName)]
    IFusionCache cache) : ITenantDirectory
{
    public Task<TenantDirectoryEntry?> FindByHostAsync(
        string host,
        CancellationToken cancellationToken = default)
    {
        const string sql = """
                           select t.id, t.slug, t.name, t.status, t.mode, d.host
                           from tenant_domains d
                           join tenants t on t.id = d.tenant_id
                           where d.host = @lookup
                             and d.status = 'active'
                             and t.status = 'active'
                           limit 1
                           """;
        return FindAsync(sql, "host", Normalize(host), cancellationToken);
    }

    public Task<TenantDirectoryEntry?> FindByCodeAsync(
        string tenantCode,
        CancellationToken cancellationToken = default)
    {
        const string sql = """
                           select t.id, t.slug, t.name, t.status, t.mode, null::text as host
                           from tenants t
                           where t.slug = @lookup
                             and t.status = 'active'
                           limit 1
                           """;
        return FindAsync(sql, "code", Normalize(tenantCode), cancellationToken);
    }

    private async Task<TenantDirectoryEntry?> FindAsync(
        string sql,
        string kind,
        string lookup,
        CancellationToken cancellationToken)
    {
        var cacheKey = CacheKey(kind, lookup);
        var cached = await cache.GetOrSetAsync<TenantDirectoryCacheEntry>(
            cacheKey,
            async (context, token) =>
            {
                await using var command = dataSource.CreateCommand(sql);
                command.Parameters.AddWithValue("lookup", lookup);
                await using var reader = await command.ExecuteReaderAsync(token);
                if (!await reader.ReadAsync(token))
                {
                    context.Options.Duration = TimeSpan.FromSeconds(20);
                    context.Options.MemoryCacheDuration = TimeSpan.FromSeconds(20);
                    return new TenantDirectoryCacheEntry(false, null);
                }

                return new TenantDirectoryCacheEntry(true, new TenantDirectoryEntry(
                    reader.GetGuid(0),
                    reader.GetString(1),
                    reader.GetString(2),
                    ParseEnum<TenantStatus>(reader.GetString(3)),
                    ParseEnum<TenantMode>(reader.GetString(4)),
                    reader.IsDBNull(5) ? null : reader.GetString(5)));
            },
            options =>
            {
                options.Duration = TimeSpan.FromSeconds(300);
                options.MemoryCacheDuration = TimeSpan.FromSeconds(30);
            },
            token: cancellationToken);
        return cached.Entry;
    }

    private static TEnum ParseEnum<TEnum>(string value)
        where TEnum : struct, Enum
    {
        return Enum.Parse<TEnum>(value.Replace("_", string.Empty, StringComparison.Ordinal), true);
    }

    private static string Normalize(string value)
    {
        return value.Trim().ToLowerInvariant();
    }

    internal static string CacheKey(string kind, string lookup)
    {
        return $"tenant-directory:v1:{kind}:{Normalize(lookup)}";
    }
}
