using System.Text.Json;
using System.Text.RegularExpressions;
using Microsoft.EntityFrameworkCore;
using Tiku.Application.Backoffice;
using Tiku.Application.PlatformAdmin;
using Tiku.Application.Security;
using Tiku.Domain.Common;
using Tiku.Domain.Operations;
using Tiku.Domain.Platform;
using Tiku.Infrastructure.Persistence;

namespace Tiku.Infrastructure.PlatformAdmin;

internal sealed partial class PlatformGovernanceService(
    TikuDbContext dbContext,
    IOperationAuditService auditService) : IPlatformGovernanceService
{
    public async Task<IReadOnlyCollection<PlatformConfigurationDefinitionItem>> GetConfigurationDefinitionsAsync(
        PlatformApprovalActor actor, CancellationToken cancellationToken = default)
    {
        Require(actor, BackendPermissions.PlatformConfigurationManage);
        return await dbContext.PlatformConfigurationDefinitions.AsNoTracking().OrderBy(item => item.Category)
            .ThenBy(item => item.Code)
            .Select(item => ToItem(item)).ToArrayAsync(cancellationToken);
    }

    public async Task<IReadOnlyCollection<PlatformConfigurationVersionItem>> GetConfigurationVersionsAsync(
        PlatformApprovalActor actor, string definitionCode, string? environment,
        CancellationToken cancellationToken = default)
    {
        Require(actor, BackendPermissions.PlatformConfigurationManage);
        var definitionId = await dbContext.PlatformConfigurationDefinitions.AsNoTracking()
                               .Where(item => item.Code == definitionCode).Select(item => (Guid?)item.Id)
                               .SingleOrDefaultAsync(cancellationToken)
                           ?? throw Error("Configuration definition was not found.",
                               "platform_configuration_not_found");
        var query = dbContext.PlatformConfigurationVersions.AsNoTracking()
            .Where(item => item.DefinitionId == definitionId);
        if (!string.IsNullOrWhiteSpace(environment))
            query = query.Where(item => item.Environment == NormalizeEnvironment(environment));
        return await query.OrderByDescending(item => item.Version).Select(item => ToItem(item))
            .ToArrayAsync(cancellationToken);
    }

    public async Task<PlatformConfigurationVersionItem> SaveConfigurationDraftAsync(
        PlatformApprovalActor actor, SavePlatformConfigurationDraftCommand command,
        CancellationToken cancellationToken = default)
    {
        Require(actor, BackendPermissions.PlatformConfigurationManage);
        var definition =
            await dbContext.PlatformConfigurationDefinitions.SingleOrDefaultAsync(
                item => item.Code == command.DefinitionCode, cancellationToken)
            ?? throw Error("Configuration definition was not found.", "platform_configuration_not_found");
        if (!definition.AllowRuntimeManagement)
            throw Error("Security-controlled configuration cannot be changed at runtime.",
                "platform_configuration_runtime_forbidden");
        var environment = NormalizeEnvironment(command.Environment);
        ValidateValue(definition, command.Value, command.SecretRef);
        var nextVersion = (await dbContext.PlatformConfigurationVersions
            .Where(item => item.DefinitionId == definition.Id && item.Environment == environment)
            .MaxAsync(item => (int?)item.Version, cancellationToken) ?? 0) + 1;
        var item = new PlatformConfigurationVersion
        {
            DefinitionId = definition.Id,
            Environment = environment,
            Version = nextVersion,
            Value = definition.IsSensitive ? JsonDefaults.Object() : command.Value!.Value.Clone(),
            SecretRef = definition.IsSensitive ? command.SecretRef!.Trim() : null,
            CreatedBy = actor.UserId,
            Reason = Required(command.Reason, "reason")
        };
        dbContext.PlatformConfigurationVersions.Add(item);
        await dbContext.SaveChangesAsync(cancellationToken);
        await AuditAsync(actor.UserId, "platform.configuration.draft_saved", "platform_configuration_versions", item.Id,
            new { definition.Code, item.Environment, item.Version }, cancellationToken);
        return ToItem(item);
    }

    public async Task<PlatformConfigurationVersionItem> PublishConfigurationAsync(
        PlatformApprovalActor actor, Guid versionId, CancellationToken cancellationToken = default)
    {
        Require(actor, BackendPermissions.PlatformConfigurationManage);
        var item = await dbContext.PlatformConfigurationVersions.SingleOrDefaultAsync(value => value.Id == versionId,
                       cancellationToken)
                   ?? throw Error("Configuration version was not found.", "platform_configuration_version_not_found");
        if (item.Status != PlatformConfigurationVersionStatus.Draft)
            throw Error("Only a draft configuration can be published.", "platform_configuration_not_draft");
        var current = await dbContext.PlatformConfigurationVersions.Where(value =>
                value.DefinitionId == item.DefinitionId &&
                value.Environment == item.Environment && value.Status == PlatformConfigurationVersionStatus.Published)
            .ToArrayAsync(cancellationToken);
        foreach (var published in current) published.Status = PlatformConfigurationVersionStatus.Retired;
        item.Status = PlatformConfigurationVersionStatus.Published;
        item.PublishedBy = actor.UserId;
        item.PublishedAt = DateTimeOffset.UtcNow;
        await dbContext.SaveChangesAsync(cancellationToken);
        await AuditAsync(actor.UserId, "platform.configuration.published", "platform_configuration_versions", item.Id,
            new { item.DefinitionId, item.Environment, item.Version }, cancellationToken);
        return ToItem(item);
    }

    public async Task<PlatformConfigurationVersionItem> RollbackConfigurationAsync(
        PlatformApprovalActor actor, Guid versionId, string reason, CancellationToken cancellationToken = default)
    {
        Require(actor, BackendPermissions.PlatformConfigurationManage);
        var source = await dbContext.PlatformConfigurationVersions.AsNoTracking()
                         .SingleOrDefaultAsync(item => item.Id == versionId, cancellationToken)
                     ?? throw Error("Configuration version was not found.", "platform_configuration_version_not_found");
        var nextVersion = (await dbContext.PlatformConfigurationVersions.Where(item =>
                item.DefinitionId == source.DefinitionId && item.Environment == source.Environment)
            .MaxAsync(item => (int?)item.Version, cancellationToken) ?? 0) + 1;
        var rollback = new PlatformConfigurationVersion
        {
            DefinitionId = source.DefinitionId,
            Environment = source.Environment,
            Version = nextVersion,
            Value = source.Value.Clone(),
            SecretRef = source.SecretRef,
            CreatedBy = actor.UserId,
            RolledBackFromVersionId = source.Id,
            Reason = Required(reason, "reason")
        };
        dbContext.PlatformConfigurationVersions.Add(rollback);
        await dbContext.SaveChangesAsync(cancellationToken);
        return await PublishConfigurationAsync(actor, rollback.Id, cancellationToken);
    }

    public async Task<PagedResult<PlatformNotificationDeliveryItem>> GetNotificationDeliveriesAsync(
        PlatformApprovalActor actor, PagedQuery query, PlatformNotificationDeliveryStatus? status,
        CancellationToken cancellationToken = default)
    {
        Require(actor, BackendPermissions.PlatformNotificationManage);
        var values = dbContext.PlatformNotificationDeliveries.AsNoTracking();
        if (status.HasValue) values = values.Where(item => item.Status == status.Value);
        if (!string.IsNullOrWhiteSpace(query.Search))
            values = values.Where(item =>
                item.Subject.Contains(query.Search) || item.RecipientRoleCode.Contains(query.Search));
        var total = await values.CountAsync(cancellationToken);
        var items = await values.OrderByDescending(item => item.CreatedAt)
            .Skip((query.SafePage - 1) * query.SafePageSize).Take(query.SafePageSize)
            .Select(item => ToItem(item)).ToArrayAsync(cancellationToken);
        return new PagedResult<PlatformNotificationDeliveryItem>(items, total, query.SafePage, query.SafePageSize);
    }

    public async Task<IReadOnlyCollection<PlatformNotificationTemplateItem>> GetNotificationTemplatesAsync(
        PlatformApprovalActor actor, CancellationToken cancellationToken = default)
    {
        Require(actor, BackendPermissions.PlatformNotificationManage);
        return await dbContext.PlatformNotificationTemplates.AsNoTracking().OrderBy(item => item.Code)
            .Select(item => ToItem(item)).ToArrayAsync(cancellationToken);
    }

    public async Task<PlatformNotificationTemplateItem> UpsertNotificationTemplateAsync(
        PlatformApprovalActor actor, UpsertPlatformNotificationTemplateCommand command,
        CancellationToken cancellationToken = default)
    {
        Require(actor, BackendPermissions.PlatformNotificationManage);
        var code = Required(command.Code, "code").ToLowerInvariant();
        var item = command.Id.HasValue
            ? await dbContext.PlatformNotificationTemplates.SingleOrDefaultAsync(value => value.Id == command.Id,
                cancellationToken)
            : await dbContext.PlatformNotificationTemplates.SingleOrDefaultAsync(value => value.Code == code,
                cancellationToken);
        item ??= new PlatformNotificationTemplate { Code = code };
        if (dbContext.Entry(item).State == EntityState.Detached) dbContext.PlatformNotificationTemplates.Add(item);
        item.Name = Required(command.Name, "name");
        item.Channel = command.Channel;
        item.SubjectTemplate = Required(command.SubjectTemplate, "subjectTemplate");
        item.BodyTemplate = Required(command.BodyTemplate, "bodyTemplate");
        item.Enabled = command.Enabled;
        item.Variables = command.Variables.Clone();
        await dbContext.SaveChangesAsync(cancellationToken);
        await AuditAsync(actor.UserId, "platform.notification_template.upserted", "platform_notification_templates",
            item.Id, new { item.Code, item.Channel }, cancellationToken);
        return ToItem(item);
    }

    public async Task<IReadOnlyCollection<PlatformNotificationDeliveryItem>> SendNotificationAsync(
        PlatformApprovalActor actor, SendPlatformNotificationCommand command,
        CancellationToken cancellationToken = default)
    {
        Require(actor, BackendPermissions.PlatformNotificationManage);
        var template = await dbContext.PlatformNotificationTemplates.AsNoTracking()
                           .SingleOrDefaultAsync(item => item.Id == command.TemplateId, cancellationToken)
                       ?? throw Error("Notification template was not found.",
                           "platform_notification_template_not_found");
        if (!template.Enabled)
            throw Error("Notification template is disabled.", "platform_notification_template_disabled");
        var roles = command.RoleCodes.Select(value => Required(value, "roleCode")).Distinct(StringComparer.Ordinal)
            .ToArray();
        var recipients = await (from binding in dbContext.PlatformBackendUserRoles.AsNoTracking()
            join role in dbContext.PlatformBackendRoles.AsNoTracking() on binding.RoleId equals role.Id
            where roles.Contains(role.Code) && role.Status == BackendRoleStatus.Active
            select new { binding.UserId, RoleCode = role.Code }).Distinct().ToArrayAsync(cancellationToken);
        var subject = Render(template.SubjectTemplate, command.Variables);
        var body = Render(template.BodyTemplate, command.Variables);
        var deliveries = new List<PlatformNotificationDelivery>();
        foreach (var recipient in recipients)
        {
            var existing = await dbContext.PlatformNotificationDeliveries.AsNoTracking().AnyAsync(item =>
                    item.TemplateId == template.Id &&
                    item.RecipientUserId == recipient.UserId && item.IdempotencyKey == command.IdempotencyKey,
                cancellationToken);
            if (existing) continue;
            var delivery = new PlatformNotificationDelivery
            {
                TemplateId = template.Id,
                RecipientUserId = recipient.UserId,
                RecipientRoleCode = recipient.RoleCode,
                Channel = template.Channel,
                Subject = subject,
                Body = body,
                IdempotencyKey = Required(command.IdempotencyKey, "idempotencyKey"),
                CreatedBy = actor.UserId,
                Status = template.Channel == PlatformNotificationChannel.InApp
                    ? PlatformNotificationDeliveryStatus.Sent
                    : PlatformNotificationDeliveryStatus.Pending,
                SentAt = template.Channel == PlatformNotificationChannel.InApp ? DateTimeOffset.UtcNow : null
            };
            dbContext.PlatformNotificationDeliveries.Add(delivery);
            deliveries.Add(delivery);
        }

        await dbContext.SaveChangesAsync(cancellationToken);
        await AuditAsync(actor.UserId, "platform.notification.sent", "platform_notification_templates", template.Id,
            new { template.Code, roles, recipients = deliveries.Count, template.Channel }, cancellationToken);
        return deliveries.Select(ToItem).ToArray();
    }

    public async Task<PlatformNotificationDeliveryItem> RetryNotificationAsync(
        PlatformApprovalActor actor, Guid deliveryId, CancellationToken cancellationToken = default)
    {
        Require(actor, BackendPermissions.PlatformNotificationManage);
        var item = await dbContext.PlatformNotificationDeliveries.SingleOrDefaultAsync(value => value.Id == deliveryId,
                       cancellationToken)
                   ?? throw Error("Notification delivery was not found.", "platform_notification_delivery_not_found");
        if (item.Status is not (PlatformNotificationDeliveryStatus.Failed
            or PlatformNotificationDeliveryStatus.Pending))
            throw Error("Only pending or failed notification delivery can be retried.",
                "platform_notification_not_retryable");
        item.Status = PlatformNotificationDeliveryStatus.Pending;
        item.Attempts++;
        item.LastError = null;
        await dbContext.SaveChangesAsync(cancellationToken);
        await AuditAsync(actor.UserId, "platform.notification.retry_requested", "platform_notification_deliveries",
            item.Id, new { item.Channel, item.Attempts }, cancellationToken);
        return ToItem(item);
    }

    private Task AuditAsync(Guid actor, string action, string targetType, Guid targetId, object details,
        CancellationToken cancellationToken)
    {
        return auditService.WriteAsync(
            new BackofficeOperationAuditCommand(null, actor, action, targetType, targetId.ToString("N"),
                JsonSerializer.SerializeToElement(details)), cancellationToken);
    }

    private static void Require(PlatformApprovalActor actor, string permission)
    {
        if (!actor.Permissions.Contains(permission))
            throw Error("Platform governance access is denied.", "platform_access_denied");
    }

    private static string Required(string? value, string field)
    {
        return string.IsNullOrWhiteSpace(value) ? throw Error($"{field} is required.", "required_field") : value.Trim();
    }

    private static string NormalizeEnvironment(string value)
    {
        value = Required(value, "environment").ToLowerInvariant();
        return EnvironmentPattern().IsMatch(value)
            ? value
            : throw Error("Environment code is invalid.", "platform_configuration_environment_invalid");
    }

    private static void ValidateValue(PlatformConfigurationDefinition definition, JsonElement? value, string? secretRef)
    {
        if (definition.IsSensitive && string.IsNullOrWhiteSpace(secretRef))
            throw Error("Sensitive configuration requires a secret reference.",
                "platform_configuration_secret_ref_required");
        if (definition.IsSensitive && value.HasValue &&
            value.Value.ValueKind is not (JsonValueKind.Null or JsonValueKind.Undefined or JsonValueKind.Object))
            throw Error("Sensitive configuration cannot contain a plain value.",
                "platform_configuration_plain_secret_forbidden");
        if (!definition.IsSensitive && !string.IsNullOrWhiteSpace(secretRef))
            throw Error("Non-sensitive configuration cannot use a secret reference.",
                "platform_configuration_secret_ref_invalid");
        if (!definition.IsSensitive && !value.HasValue)
            throw Error("Configuration value is required.", "platform_configuration_value_required");
    }

    private static string Render(string template, IReadOnlyDictionary<string, string> variables)
    {
        return TokenPattern().Replace(template, match =>
            variables.TryGetValue(match.Groups[1].Value, out var value)
                ? value
                : throw Error($"Notification variable {match.Groups[1].Value} is missing.",
                    "platform_notification_variable_missing"));
    }

    private static PlatformApprovalException Error(string message, string code)
    {
        return new PlatformApprovalException(message, code);
    }

    private static PlatformConfigurationDefinitionItem ToItem(PlatformConfigurationDefinition item)
    {
        return new PlatformConfigurationDefinitionItem(item.Id, item.Code, item.Name, item.Category, item.ValueType,
            item.AllowRuntimeManagement,
            item.IsSensitive, item.Description, item.ValidationSchema);
    }

    private static PlatformConfigurationVersionItem ToItem(PlatformConfigurationVersion item)
    {
        return new PlatformConfigurationVersionItem(item.Id, item.DefinitionId, item.Environment, item.Version,
            item.Status,
            item.SecretRef is null ? item.Value : null, item.SecretRef, item.CreatedBy, item.PublishedBy,
            item.RolledBackFromVersionId, item.Reason, item.PublishedAt, item.CreatedAt);
    }

    private static PlatformNotificationTemplateItem ToItem(PlatformNotificationTemplate item)
    {
        return new PlatformNotificationTemplateItem(item.Id, item.Code, item.Name, item.Channel, item.SubjectTemplate,
            item.BodyTemplate, item.Enabled,
            item.Variables, item.UpdatedAt);
    }

    private static PlatformNotificationDeliveryItem ToItem(PlatformNotificationDelivery item)
    {
        return new PlatformNotificationDeliveryItem(item.Id, item.TemplateId, item.RecipientUserId,
            item.RecipientRoleCode, item.Channel, item.Status,
            item.Subject, item.Body, item.Attempts, item.LastError, item.SentAt, item.CreatedAt);
    }

    [GeneratedRegex("^[a-z0-9][a-z0-9._-]{0,79}$")]
    private static partial Regex EnvironmentPattern();

    [GeneratedRegex("\\{\\{([a-zA-Z][a-zA-Z0-9_.-]*)\\}\\}")]
    private static partial Regex TokenPattern();
}