using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using Tiku.Application.Catalog;
using Tiku.Application.Content;
using Tiku.Application.TenantAdmin;
using Tiku.Domain.Common;
using Tiku.Domain.Operations;
using Tiku.Domain.Tenancy;

namespace Tiku.Infrastructure.TenantAdmin;

public sealed partial class TenantAdminDirectService
{
    public async Task<CatalogList<TenantAdminAuditLogItem>> GetAuditLogsAsync(
        TenantAdminActor actor,
        TenantAdminAuditLogFilter filter,
        CancellationToken cancellationToken = default)
    {
        await RequireAllDataScopeAsync(actor, cancellationToken);
        var query = dbContext.AuditLogs.AsNoTracking().Where(item => item.TenantId == actor.TenantId);
        if (!string.IsNullOrWhiteSpace(filter.Action))
        {
            var action = filter.Action.Trim();
            query = query.Where(item => item.Action.StartsWith(action));
        }

        if (!string.IsNullOrWhiteSpace(filter.TargetType))
            query = query.Where(item => item.TargetType == filter.TargetType.Trim());

        if (filter.ActorUserId.HasValue) query = query.Where(item => item.ActorUserId == filter.ActorUserId.Value);

        var logs = await query
            .OrderByDescending(item => item.CreatedAt)
            .Take(ResolveLimit(filter.Limit))
            .ToArrayAsync(cancellationToken);
        var actorIds = logs.Where(item => item.ActorUserId.HasValue).Select(item => item.ActorUserId!.Value).ToArray();
        var users = await dbContext.Users.AsNoTracking()
            .Where(user => actorIds.Contains(user.Id))
            .ToDictionaryAsync(user => user.Id, cancellationToken);

        return new CatalogList<TenantAdminAuditLogItem>(logs.Select(item =>
        {
            var user = item.ActorUserId.HasValue && users.TryGetValue(item.ActorUserId.Value, out var actorUser)
                ? actorUser
                : null;
            return new TenantAdminAuditLogItem(
                item.Id,
                item.ActorUserId,
                item.Action,
                item.TargetType,
                item.TargetId,
                item.Details,
                item.IpAddress,
                item.UserAgent,
                user?.Name ?? user?.UserName,
                user?.Phone,
                item.CreatedAt);
        }).ToArray());
    }

    public async Task<ContentManagementResult<TenantBrandingItem>> UpsertBrandingAsync(
        TenantAdminActor actor,
        UpsertTenantBrandingCommand command,
        CancellationToken cancellationToken = default)
    {
        await RequireAllDataScopeAsync(actor, cancellationToken);
        ArgumentException.ThrowIfNullOrWhiteSpace(command.BrandName);
        var item = await dbContext.TenantBrandings.FirstOrDefaultAsync(branding => branding.TenantId == actor.TenantId,
            cancellationToken);
        if (item is null)
        {
            item = new TenantBranding { TenantId = actor.TenantId };
            dbContext.TenantBrandings.Add(item);
        }

        item.BrandName = command.BrandName.Trim();
        item.ShortName = Normalize(command.ShortName);
        item.Slogan = Normalize(command.Slogan);
        item.OrganizationName = Normalize(command.OrganizationName);
        item.LogoUrl = Normalize(command.LogoUrl);
        item.FaviconUrl = Normalize(command.FaviconUrl);
        item.ServiceWechat = Normalize(command.ServiceWechat);
        item.ServiceAccountName = Normalize(command.ServiceAccountName);
        await AddAuditAsync(actor, "tenant.branding.updated", "tenant_branding", actor.TenantId, cancellationToken);
        await dbContext.SaveChangesAsync(cancellationToken);
        return new ContentManagementResult<TenantBrandingItem>(ToBrandingItem(item));
    }

    public async Task<ContentManagementResult<TenantSettingsItem>> UpsertSettingsAsync(
        TenantAdminActor actor,
        UpsertTenantSettingsCommand command,
        CancellationToken cancellationToken = default)
    {
        await RequireAllDataScopeAsync(actor, cancellationToken);
        AssertNoSecrets(command.PublicConfig, "public_config");
        var item = await dbContext.TenantSettings.FirstOrDefaultAsync(settings => settings.TenantId == actor.TenantId,
            cancellationToken);
        if (item is null)
        {
            item = new TenantSettings { TenantId = actor.TenantId };
            dbContext.TenantSettings.Add(item);
        }

        item.FeatureFlags = JsonObjectOrDefault(command.FeatureFlags);
        item.AdminFeatureFlags = JsonObjectOrDefault(command.AdminFeatureFlags);
        item.PublicConfig = JsonObjectOrDefault(command.PublicConfig);
        await AddAuditAsync(actor, "tenant.settings.updated", "tenant_settings", actor.TenantId, cancellationToken);
        await dbContext.SaveChangesAsync(cancellationToken);
        return new ContentManagementResult<TenantSettingsItem>(ToSettingsItem(item));
    }

    public async Task<CatalogList<TenantThemeTemplateItem>> GetThemeTemplatesAsync(
        TenantAdminActor actor,
        CancellationToken cancellationToken = default)
    {
        await RequireAllDataScopeAsync(actor, cancellationToken);
        await Task.CompletedTask.WaitAsync(cancellationToken);
        var items = await dbContext.TenantThemeTemplates.AsNoTracking()
            .Where(item => item.Status == TenantThemeTemplateStatus.Active)
            .OrderBy(item => item.SortOrder)
            .ThenBy(item => item.Code)
            .Select(item => new TenantThemeTemplateItem(
                item.Code,
                item.Name,
                item.Description,
                item.PreviewImageUrl,
                item.Theme,
                item.PublicAssets,
                item.SortOrder))
            .ToArrayAsync(cancellationToken);
        return new CatalogList<TenantThemeTemplateItem>(items);
    }

    public async Task<ContentManagementResult<TenantThemeItem>> GetThemeAsync(
        TenantAdminActor actor,
        CancellationToken cancellationToken = default)
    {
        await RequireAllDataScopeAsync(actor, cancellationToken);
        var item = await dbContext.TenantThemeConfigs.AsNoTracking()
            .FirstOrDefaultAsync(theme => theme.TenantId == actor.TenantId, cancellationToken);
        if (item is not null) return new ContentManagementResult<TenantThemeItem>(ToThemeItem(item));

        var branding = await dbContext.TenantBrandings.AsNoTracking()
            .FirstOrDefaultAsync(tenantBranding => tenantBranding.TenantId == actor.TenantId, cancellationToken);
        return new ContentManagementResult<TenantThemeItem>(new TenantThemeItem(
            actor.TenantId,
            null,
            branding?.Theme ?? JsonDefaults.Object(),
            branding?.PublicAssets ?? JsonDefaults.Object(),
            null,
            JsonDefaults.Object(),
            JsonDefaults.Object(),
            TenantThemeConfigStatus.Published,
            null,
            null,
            null,
            branding?.UpdatedAt ?? DateTimeOffset.UtcNow));
    }

    public async Task<ContentManagementResult<TenantThemeItem>> PreviewThemeAsync(
        TenantAdminActor actor,
        PreviewTenantThemeCommand command,
        CancellationToken cancellationToken = default)
    {
        await RequireAllDataScopeAsync(actor, cancellationToken);
        ArgumentException.ThrowIfNullOrWhiteSpace(command.TemplateCode);
        var template = await dbContext.TenantThemeTemplates.FirstOrDefaultAsync(
            item => item.Code == command.TemplateCode && item.Status == TenantThemeTemplateStatus.Active,
            cancellationToken);
        if (template is null)
            throw new TenantAdminDirectException("Theme template was not found.", "theme_template_not_found");

        var item = await dbContext.TenantThemeConfigs.FirstOrDefaultAsync(theme => theme.TenantId == actor.TenantId,
            cancellationToken);
        if (item is null)
        {
            item = new TenantThemeConfig { TenantId = actor.TenantId };
            dbContext.TenantThemeConfigs.Add(item);
        }

        item.DraftTemplateCode = template.Code;
        item.DraftTheme = MergeJsonObjects(template.Theme, command.Theme);
        item.DraftPublicAssets = MergeJsonObjects(template.PublicAssets, command.PublicAssets);
        item.Status = TenantThemeConfigStatus.Draft;
        item.DraftUpdatedBy = actor.UserId;
        await AddAuditAsync(actor, "tenant.theme.previewed", "tenant_theme_configs", actor.TenantId, cancellationToken);
        await dbContext.SaveChangesAsync(cancellationToken);
        return new ContentManagementResult<TenantThemeItem>(ToThemeItem(item));
    }

    public async Task<ContentManagementResult<TenantThemeItem>> PublishThemeAsync(
        TenantAdminActor actor,
        PublishTenantThemeCommand command,
        CancellationToken cancellationToken = default)
    {
        await RequireAllDataScopeAsync(actor, cancellationToken);
        var item = await dbContext.TenantThemeConfigs.FirstOrDefaultAsync(theme => theme.TenantId == actor.TenantId,
            cancellationToken);
        JsonElement activeTheme;
        JsonElement activeAssets;
        string? templateCode;
        if (command.UseDraft)
        {
            if (item is null || string.IsNullOrWhiteSpace(item.DraftTemplateCode))
                throw new TenantAdminDirectException("No draft theme to publish.", "theme_draft_not_found");

            activeTheme = item.DraftTheme;
            activeAssets = item.DraftPublicAssets;
            templateCode = item.DraftTemplateCode;
        }
        else
        {
            ArgumentException.ThrowIfNullOrWhiteSpace(command.TemplateCode);
            var template = await dbContext.TenantThemeTemplates.FirstOrDefaultAsync(
                theme => theme.Code == command.TemplateCode && theme.Status == TenantThemeTemplateStatus.Active,
                cancellationToken);
            if (template is null)
                throw new TenantAdminDirectException("Theme template was not found.", "theme_template_not_found");

            item ??= new TenantThemeConfig { TenantId = actor.TenantId };
            if (dbContext.Entry(item).State == EntityState.Detached) dbContext.TenantThemeConfigs.Add(item);

            activeTheme = MergeJsonObjects(template.Theme, command.Theme);
            activeAssets = MergeJsonObjects(template.PublicAssets, command.PublicAssets);
            templateCode = template.Code;
        }

        item ??= new TenantThemeConfig { TenantId = actor.TenantId };
        if (dbContext.Entry(item).State == EntityState.Detached) dbContext.TenantThemeConfigs.Add(item);

        item.ActiveTemplateCode = templateCode;
        item.ActiveTheme = activeTheme.Clone();
        item.ActivePublicAssets = activeAssets.Clone();
        item.DraftTemplateCode = null;
        item.DraftTheme = JsonDefaults.Object();
        item.DraftPublicAssets = JsonDefaults.Object();
        item.Status = TenantThemeConfigStatus.Published;
        item.PublishedAt = DateTimeOffset.UtcNow;
        item.PublishedBy = actor.UserId;
        await EnsureBrandingThemeAsync(actor.TenantId, activeTheme, activeAssets, cancellationToken);
        await AddAuditAsync(actor, "tenant.theme.published", "tenant_theme_configs", actor.TenantId, cancellationToken);
        await dbContext.SaveChangesAsync(cancellationToken);
        return new ContentManagementResult<TenantThemeItem>(ToThemeItem(item));
    }
}