using MemberCenter.Application.Abstractions;
using MemberCenter.Application.Models.Admin;
using MemberCenter.Domain.Entities;
using MemberCenter.Infrastructure.Persistence;
using Microsoft.EntityFrameworkCore;

namespace MemberCenter.Infrastructure.Services;

public sealed class TenantService : ITenantService
{
    private const string SendEngineWebhookClientKeyPrefix = "send_engine:webhook_client:";

    private readonly MemberCenterDbContext _dbContext;

    public TenantService(MemberCenterDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public async Task<IReadOnlyList<TenantDto>> ListAsync()
    {
        var tenants = await _dbContext.Tenants.ToListAsync();
        var mappings = await LoadWebhookClientMappingsAsync(tenants.Select(t => t.Id));
        return tenants.Select(t => MapTenant(t, mappings.TryGetValue(t.Id, out var clientId) ? clientId : null)).ToList();
    }

    public async Task<IReadOnlyList<TenantDto>> ListForUserAsync(Guid userId)
    {
        var tenants = await _dbContext.TenantManagers
            .AsNoTracking()
            .Where(manager => manager.UserId == userId)
            .Join(
                _dbContext.Tenants.AsNoTracking(),
                manager => manager.TenantId,
                tenant => tenant.Id,
                (_, tenant) => tenant)
            .OrderBy(tenant => tenant.Name)
            .ToListAsync();

        var mappings = await LoadWebhookClientMappingsAsync(tenants.Select(t => t.Id));
        return tenants.Select(t => MapTenant(t, mappings.TryGetValue(t.Id, out var clientId) ? clientId : null)).ToList();
    }

    public async Task<TenantDto?> GetAsync(Guid id)
    {
        var tenant = await _dbContext.Tenants.FindAsync(id);
        if (tenant is null)
        {
            return null;
        }

        var mapping = await LoadWebhookClientMappingAsync(id);
        return MapTenant(tenant, mapping);
    }

    public async Task<TenantDto?> GetForUserAsync(Guid id, Guid userId)
    {
        var tenant = await _dbContext.TenantManagers
            .AsNoTracking()
            .Where(manager => manager.UserId == userId && manager.TenantId == id)
            .Join(
                _dbContext.Tenants.AsNoTracking(),
                manager => manager.TenantId,
                tenant => tenant.Id,
                (_, tenant) => tenant)
            .FirstOrDefaultAsync();
        if (tenant is null)
        {
            return null;
        }

        var mapping = await LoadWebhookClientMappingAsync(id);
        return MapTenant(tenant, mapping);
    }

    public async Task<TenantDto> CreateAsync(string name, List<string> domains, string status, Guid? sendEngineWebhookClientId = null)
    {
        var tenant = new Tenant
        {
            Id = Guid.NewGuid(),
            Name = name,
            Domains = domains,
            Status = status
        };

        _dbContext.Tenants.Add(tenant);
        await _dbContext.SaveChangesAsync();
        await SetWebhookClientMappingInternalAsync(tenant.Id, sendEngineWebhookClientId);
        return MapTenant(tenant, sendEngineWebhookClientId);
    }

    public async Task<TenantDto?> UpdateAsync(Guid id, string name, List<string> domains, string status, Guid? sendEngineWebhookClientId = null)
    {
        var tenant = await _dbContext.Tenants.FindAsync(id);
        if (tenant is null)
        {
            return null;
        }

        tenant.Name = name;
        tenant.Domains = domains;
        tenant.Status = status;
        await _dbContext.SaveChangesAsync();
        await SetWebhookClientMappingInternalAsync(id, sendEngineWebhookClientId);
        return MapTenant(tenant, sendEngineWebhookClientId);
    }

    public async Task<bool> SetSendEngineWebhookClientAsync(Guid tenantId, Guid? webhookClientId)
    {
        var exists = await _dbContext.Tenants.AnyAsync(t => t.Id == tenantId);
        if (!exists)
        {
            return false;
        }

        await SetWebhookClientMappingInternalAsync(tenantId, webhookClientId);
        return true;
    }

    public async Task<bool> DeleteAsync(Guid id)
    {
        var tenant = await _dbContext.Tenants.FindAsync(id);
        if (tenant is null)
        {
            return false;
        }

        var mapping = await _dbContext.SystemFlags.FirstOrDefaultAsync(x => x.Key == BuildWebhookClientKey(id));
        if (mapping is not null)
        {
            _dbContext.SystemFlags.Remove(mapping);
        }

        _dbContext.Tenants.Remove(tenant);
        await _dbContext.SaveChangesAsync();
        return true;
    }

    private static TenantDto MapTenant(Tenant tenant, Guid? webhookClientId)
    {
        return new TenantDto(tenant.Id, tenant.Name, tenant.Domains, tenant.Status, webhookClientId);
    }

    private async Task<Dictionary<Guid, Guid>> LoadWebhookClientMappingsAsync(IEnumerable<Guid> tenantIds)
    {
        var keys = tenantIds.Select(BuildWebhookClientKey).ToHashSet(StringComparer.Ordinal);
        if (keys.Count == 0)
        {
            return new Dictionary<Guid, Guid>();
        }

        var flags = await _dbContext.SystemFlags
            .Where(f => keys.Contains(f.Key))
            .ToListAsync();

        var result = new Dictionary<Guid, Guid>();
        foreach (var flag in flags)
        {
            var tenantPart = flag.Key.Replace(SendEngineWebhookClientKeyPrefix, string.Empty, StringComparison.Ordinal);
            if (!Guid.TryParse(tenantPart, out var tenantId))
            {
                continue;
            }

            if (!Guid.TryParse(flag.Value, out var webhookClientId))
            {
                continue;
            }

            result[tenantId] = webhookClientId;
        }

        return result;
    }

    private async Task<Guid?> LoadWebhookClientMappingAsync(Guid tenantId)
    {
        var key = BuildWebhookClientKey(tenantId);
        var flag = await _dbContext.SystemFlags.FirstOrDefaultAsync(x => x.Key == key);
        if (flag is null || !Guid.TryParse(flag.Value, out var webhookClientId))
        {
            return null;
        }

        return webhookClientId;
    }

    private async Task SetWebhookClientMappingInternalAsync(Guid tenantId, Guid? webhookClientId)
    {
        var key = BuildWebhookClientKey(tenantId);
        var existing = await _dbContext.SystemFlags.FirstOrDefaultAsync(x => x.Key == key);

        if (!webhookClientId.HasValue)
        {
            if (existing is not null)
            {
                _dbContext.SystemFlags.Remove(existing);
                await _dbContext.SaveChangesAsync();
            }

            return;
        }

        var value = webhookClientId.Value.ToString();
        if (existing is null)
        {
            _dbContext.SystemFlags.Add(new Domain.Entities.SystemFlag
            {
                Id = Guid.NewGuid(),
                Key = key,
                Value = value,
                UpdatedAt = DateTimeOffset.UtcNow
            });
        }
        else
        {
            existing.Value = value;
            existing.UpdatedAt = DateTimeOffset.UtcNow;
        }

        await _dbContext.SaveChangesAsync();
    }

    private static string BuildWebhookClientKey(Guid tenantId)
    {
        return $"{SendEngineWebhookClientKeyPrefix}{tenantId:D}";
    }
}
