using Microsoft.EntityFrameworkCore;
using Tiku.Application.PlatformAdmin;
using Tiku.Application.Security;
using Tiku.Domain.Platform;

namespace Tiku.Infrastructure.PlatformAdmin;

internal sealed partial class PlatformAdminService
{
    public async Task<PlatformBillingDunningChannelList> GetBillingDunningChannelsAsync(
        PlatformAdminActor actor,
        PlatformAdminQuery query,
        CancellationToken cancellationToken = default)
    {
        await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformBillingNotification, cancellationToken);
        return await ExecuteSystemAsync("platform dunning channel list", async dbContext =>
        {
            var channels = dbContext.PlatformBillingDunningNotificationChannels.AsNoTracking();
            if (!string.IsNullOrWhiteSpace(query.Search))
            {
                var search = query.Search.Trim();
                channels = channels.Where(channel =>
                    channel.ChannelCode.Contains(search) ||
                    channel.Name.Contains(search));
            }

            if (!string.IsNullOrWhiteSpace(query.Status))
            {
                var enabled = ParseEnabledStatus(query.Status);
                channels = channels.Where(channel => channel.Enabled == enabled);
            }

            return new PlatformBillingDunningChannelList(await channels
                .OrderByDescending(channel => channel.Enabled)
                .ThenBy(channel => channel.MinReminderLevel)
                .ThenBy(channel => channel.ChannelCode)
                .Take(Limit(query.Limit))
                .Select(channel => ToDunningChannelItem(channel))
                .ToArrayAsync(cancellationToken));
        }, cancellationToken);
    }

    public async Task<PlatformBillingDunningChannelItem> UpsertBillingDunningChannelAsync(
        PlatformAdminActor actor,
        UpsertPlatformBillingDunningChannelCommand command,
        CancellationToken cancellationToken = default)
    {
        await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformBillingNotification, cancellationToken);
        return await ExecuteSystemAsync("platform dunning channel upsert", async dbContext =>
        {
            var code = NormalizeCode(command.ChannelCode);
            var channel = command.ChannelId.HasValue
                ? await dbContext.PlatformBillingDunningNotificationChannels.SingleOrDefaultAsync(
                    item => item.Id == command.ChannelId.Value, cancellationToken)
                : await dbContext.PlatformBillingDunningNotificationChannels.SingleOrDefaultAsync(
                    item => item.ChannelCode == code, cancellationToken);
            if (channel is null)
            {
                channel = new PlatformBillingDunningNotificationChannel { ChannelCode = code };
                dbContext.PlatformBillingDunningNotificationChannels.Add(channel);
            }

            channel.ChannelCode = code;
            channel.Name = command.Name.Trim();
            channel.Description = Normalize(command.Description);
            channel.Enabled = command.Enabled;
            channel.Provider = command.Provider;
            channel.WebhookUrl = command.WebhookUrl.Trim();
            channel.SecretRef = Normalize(command.SecretRef);
            channel.ReminderTypes = NormalizeArray(command.ReminderTypes, ["overdue", "final_notice"]);
            channel.ReminderChannels = NormalizeArray(command.ReminderChannels, ["internal"]);
            channel.MinReminderLevel = Math.Clamp(command.MinReminderLevel, 1, 20);
            channel.TenantIds = command.TenantIds.Where(id => id != Guid.Empty).Distinct().ToArray();
            channel.TimeoutSeconds = Math.Clamp(command.TimeoutSeconds, 1, 60);
            channel.Metadata = JsonObjectOrDefault(command.Metadata);
            AddAudit(dbContext, actor, "platform.billing_dunning_channel.upserted", channel.Id, new
            {
                channel.ChannelCode,
                channel.Name,
                channel.Enabled,
                channel.Provider,
                Webhook = MaskWebhook(channel.WebhookUrl),
                channel.SecretRef
            });
            await dbContext.SaveChangesAsync(cancellationToken);
            return ToDunningChannelItem(channel);
        }, cancellationToken);
    }

    public async Task<PlatformBillingDunningChannelItem> DisableBillingDunningChannelAsync(
        PlatformAdminActor actor,
        DisablePlatformBillingDunningChannelCommand command,
        CancellationToken cancellationToken = default)
    {
        await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformBillingNotification, cancellationToken);
        return await ExecuteSystemAsync("platform dunning channel disable", async dbContext =>
        {
            var channel =
                await dbContext.PlatformBillingDunningNotificationChannels.SingleOrDefaultAsync(
                    item => item.Id == command.ChannelId, cancellationToken)
                ?? throw new PlatformAdminException("Platform dunning channel was not found.",
                    "dunning_channel_not_found");
            channel.Enabled = false;
            AddAudit(dbContext, actor, "platform.billing_dunning_channel.disabled", channel.Id, new
            {
                channel.ChannelCode,
                command.Reason
            });
            await dbContext.SaveChangesAsync(cancellationToken);
            return ToDunningChannelItem(channel);
        }, cancellationToken);
    }

    public async Task<PlatformBillingDunningEventList> GetBillingDunningEventsAsync(
        PlatformAdminActor actor,
        PlatformAdminQuery query,
        CancellationToken cancellationToken = default)
    {
        await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformBillingNotification, cancellationToken);
        return await ExecuteSystemAsync("platform dunning event list", async dbContext =>
        {
            var events = dbContext.PlatformBillingDunningNotificationEvents.AsNoTracking();
            if (!string.IsNullOrWhiteSpace(query.Status))
                events = events.Where(item => item.Status == ParseDunningEventStatus(query.Status));

            return new PlatformBillingDunningEventList(await events
                .OrderByDescending(item => item.CreatedAt)
                .Take(Limit(query.Limit))
                .Select(item => ToDunningEventItem(item))
                .ToArrayAsync(cancellationToken));
        }, cancellationToken);
    }

    public async Task<PlatformBillingDunningEventItem> GetBillingDunningEventDetailAsync(
        PlatformAdminActor actor,
        Guid eventId,
        CancellationToken cancellationToken = default)
    {
        await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformBillingNotification, cancellationToken);
        return await ExecuteSystemAsync("platform dunning event detail", async dbContext =>
        {
            var item = await dbContext.PlatformBillingDunningNotificationEvents.AsNoTracking()
                           .SingleOrDefaultAsync(item => item.Id == eventId, cancellationToken)
                       ?? throw new PlatformAdminException("Platform dunning event was not found.",
                           "dunning_event_not_found");
            return ToDunningEventItem(item);
        }, cancellationToken);
    }

    public async Task<PlatformBillingDunningEventItem> RetryBillingDunningEventAsync(
        PlatformAdminActor actor,
        RetryPlatformBillingDunningEventCommand command,
        CancellationToken cancellationToken = default)
    {
        await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformBillingNotification, cancellationToken);
        return await ExecuteSystemAsync("platform dunning event retry", async dbContext =>
        {
            var item = await dbContext.PlatformBillingDunningNotificationEvents.SingleOrDefaultAsync(
                           item => item.Id == command.EventId, cancellationToken)
                       ?? throw new PlatformAdminException("Platform dunning event was not found.",
                           "dunning_event_not_found");
            item.Status = PlatformBillingDunningNotificationStatus.Pending;
            item.NextAttemptAt = DateTimeOffset.UtcNow;
            item.LastError = null;
            AddAudit(dbContext, actor, "platform.billing_dunning_event.retry_requested", item.Id, new
            {
                item.TenantId,
                item.ChannelId,
                item.ReminderId,
                item.InvoiceId,
                command.Reason
            });
            await dbContext.SaveChangesAsync(cancellationToken);
            return ToDunningEventItem(item);
        }, cancellationToken);
    }

    public Task<PlatformBillingDunningEventItem> AcknowledgeBillingDunningEventAsync(
        PlatformAdminActor actor,
        ResolvePlatformBillingDunningEventCommand command,
        CancellationToken cancellationToken = default)
    {
        return ResolveBillingDunningEventAsync(actor, command, PlatformBillingDunningNotificationStatus.Acknowledged,
            cancellationToken);
    }

    public Task<PlatformBillingDunningEventItem> IgnoreBillingDunningEventAsync(
        PlatformAdminActor actor,
        ResolvePlatformBillingDunningEventCommand command,
        CancellationToken cancellationToken = default)
    {
        return ResolveBillingDunningEventAsync(actor, command, PlatformBillingDunningNotificationStatus.Ignored,
            cancellationToken);
    }

    private async Task<PlatformBillingDunningEventItem> ResolveBillingDunningEventAsync(
        PlatformAdminActor actor,
        ResolvePlatformBillingDunningEventCommand command,
        PlatformBillingDunningNotificationStatus status,
        CancellationToken cancellationToken)
    {
        await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformBillingNotification, cancellationToken);
        return await ExecuteSystemAsync("platform dunning event resolve", async dbContext =>
        {
            var item = await dbContext.PlatformBillingDunningNotificationEvents
                           .SingleOrDefaultAsync(value => value.Id == command.EventId, cancellationToken)
                       ?? throw new PlatformAdminException("Platform dunning event was not found.",
                           "dunning_event_not_found");
            if (item.Status == PlatformBillingDunningNotificationStatus.Processing ||
                item.Status is PlatformBillingDunningNotificationStatus.Acknowledged
                    or PlatformBillingDunningNotificationStatus.Ignored)
                throw new PlatformAdminException("Platform dunning event cannot be resolved from its current status.",
                    "dunning_event_status_invalid");
            var fromStatus = item.Status;
            item.Status = status;
            item.NextAttemptAt = null;
            AddAudit(dbContext, actor,
                status == PlatformBillingDunningNotificationStatus.Acknowledged
                    ? "platform.billing_dunning_event.acknowledged"
                    : "platform.billing_dunning_event.ignored",
                item.Id,
                new { item.TenantId, FromStatus = fromStatus, ToStatus = status, command.Reason });
            await dbContext.SaveChangesAsync(cancellationToken);
            return ToDunningEventItem(item);
        }, cancellationToken);
    }
}