using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using Tiku.Application.Tenancy;
using Tiku.Application.TenantAdmin;
using Tiku.Domain.Common;
using Tiku.Domain.Identity;
using Tiku.Domain.Learning;
using Tiku.Domain.Operations;
using Tiku.Domain.Tenancy;

namespace Tiku.Infrastructure.TenantAdmin;

public sealed partial class TenantAdminDirectService
{
    private static TenantAdminClassItem ToClassItem(TenantClass item, string? regionName, int studentCount,
        int staffCount)
    {
        return new TenantAdminClassItem(
            item.Id,
            item.RegionId,
            regionName,
            item.LegacyId,
            item.Code,
            item.Name,
            item.Description,
            item.Status,
            item.SortOrder,
            item.Metadata,
            studentCount,
            staffCount,
            item.CreatedAt,
            item.UpdatedAt);
    }

    private static TenantAdminClassMemberItem ToClassMemberItem(TenantClassMember item, TenantAdminUserSummary user)
    {
        return new TenantAdminClassMemberItem(
            item.Id,
            item.ClassId,
            item.UserId,
            item.MemberType,
            item.Status,
            item.JoinedAt,
            item.LeftAt,
            item.Metadata,
            user);
    }

    private static TenantAdminStudentItem ToStudentItem(
        TenantMembership membership,
        User user,
        StudentProfile? profile,
        IReadOnlyDictionary<Guid, string> regions,
        IReadOnlyDictionary<Guid, string> schools,
        IReadOnlyDictionary<Guid, string> majors,
        IReadOnlyCollection<TenantAdminStudentClassSummary> classes)
    {
        return new TenantAdminStudentItem(
            membership.Id,
            membership.UserId,
            membership.Status,
            ToUserSummary(user),
            profile?.Id,
            profile?.AvatarPreset ?? "male",
            profile?.RegionId,
            profile?.RegionId is Guid regionId && regions.TryGetValue(regionId, out var regionName) ? regionName : null,
            profile?.SelectedSchoolId,
            profile?.SelectedSchoolId is Guid schoolId && schools.TryGetValue(schoolId, out var schoolName)
                ? schoolName
                : null,
            profile?.SelectedMajorId,
            profile?.SelectedMajorId is Guid majorId && majors.TryGetValue(majorId, out var majorName)
                ? majorName
                : null,
            profile?.Stats ?? JsonDefaults.Object(),
            profile?.Progress ?? JsonDefaults.Object(),
            profile?.ModuleSelections ?? JsonDefaults.Object(),
            classes,
            membership.CreatedAt,
            membership.UpdatedAt);
    }

    private static TenantAdminUserSummary ToUserSummary(User user)
    {
        return new TenantAdminUserSummary(
            user.Id,
            user.UserName,
            user.Email,
            user.Phone,
            user.Name,
            user.AvatarUrl,
            user.PrimaryRole);
    }

    private static TenantAdminStudentNoteItem ToNoteItem(TenantStudentNote item)
    {
        return new TenantAdminStudentNoteItem(
            item.Id,
            item.StudentUserId,
            item.NoteType,
            item.Content,
            item.Visibility,
            item.IsPinned,
            item.Metadata,
            item.CreatedBy,
            item.UpdatedBy,
            item.CreatedAt,
            item.UpdatedAt);
    }

    private static TenantAdminStudentFollowupItem ToFollowupItem(TenantStudentFollowup item)
    {
        return new TenantAdminStudentFollowupItem(
            item.Id,
            item.StudentUserId,
            item.AssignedToUserId,
            item.ClassId,
            item.Title,
            item.Description,
            item.FollowupType,
            item.Priority,
            item.Status,
            item.DueAt,
            item.CompletedAt,
            item.CompletedBy,
            item.Metadata,
            item.CreatedBy,
            item.UpdatedBy,
            item.CreatedAt,
            item.UpdatedAt);
    }

    private static TenantAdminMemberItem ToMemberItem(TenantMembership membership, User user)
    {
        return new TenantAdminMemberItem(
            membership.Id,
            membership.UserId,
            membership.Role,
            membership.Status,
            membership.LegacyRole,
            ToUserSummary(user),
            membership.CreatedAt,
            membership.UpdatedAt);
    }

    private static TenantBrandingItem ToBrandingItem(TenantBranding item)
    {
        return new TenantBrandingItem(
            item.TenantId,
            item.BrandName,
            item.ShortName,
            item.Slogan,
            item.OrganizationName,
            item.LogoUrl,
            item.FaviconUrl,
            item.ServiceWechat,
            item.ServiceAccountName,
            item.Theme,
            item.PublicAssets,
            item.UpdatedAt);
    }

    private static TenantSettingsItem ToSettingsItem(TenantSettings item)
    {
        return new TenantSettingsItem(
            item.TenantId,
            item.FeatureFlags,
            item.AdminFeatureFlags,
            item.PublicConfig,
            item.UpdatedAt);
    }

    private static TenantThemeItem ToThemeItem(TenantThemeConfig item)
    {
        return new TenantThemeItem(
            item.TenantId,
            item.ActiveTemplateCode,
            item.ActiveTheme,
            item.ActivePublicAssets,
            item.DraftTemplateCode,
            item.DraftTheme,
            item.DraftPublicAssets,
            item.Status,
            item.PublishedAt,
            item.PublishedBy,
            item.DraftUpdatedBy,
            item.UpdatedAt);
    }

    private static TenantDomainItem ToDomainItem(TenantDomain item)
    {
        return new TenantDomainItem(
            item.Id,
            item.Host,
            item.DomainType,
            item.Status,
            item.IsPrimary,
            item.VerificationToken,
            item.VerifiedAt,
            item.LastCheckedAt,
            item.DnsVerifiedAt,
            item.TlsReadyAt,
            item.LastFailureReason,
            item.CreatedAt,
            item.UpdatedAt);
    }

    private static TenantIdentityProviderItem ToAuthProviderItem(TenantExternalProviderItem item)
    {
        return new TenantIdentityProviderItem(
            item.Id,
            item.Provider,
            item.Status,
            item.DisplayName,
            item.SecretRef,
            item.Priority,
            item.ConfigPublic,
            item.CreatedAt,
            item.UpdatedAt);
    }

    private static TenantAdminBadgeItem ToBadgeItem(Badge item)
    {
        return new TenantAdminBadgeItem(
            item.Id,
            item.LegacyId,
            item.Name,
            item.Description,
            item.Category,
            item.IconUrl,
            item.Level,
            item.UnlockType,
            item.ConditionField,
            item.ConditionOperator,
            item.ConditionValue,
            item.ConditionExtra,
            item.SortOrder,
            item.IsActive,
            item.CreatedAt,
            item.UpdatedAt);
    }

    private static TenantAdminBadgeGrantItem ToBadgeGrantItem(UserBadge grant, User? user, User? grantedBy,
        Badge? badge)
    {
        return new TenantAdminBadgeGrantItem(
            grant.Id,
            grant.LegacyId,
            grant.UserId,
            user?.Name ?? user?.UserName,
            user?.Phone,
            grant.BadgeId,
            badge?.Name,
            badge?.Category,
            badge?.IconUrl,
            badge?.Level,
            grant.GrantedBy,
            grantedBy?.Name ?? grantedBy?.UserName,
            grant.Note,
            grant.GrantedAt,
            grant.CreatedAt,
            grant.UpdatedAt);
    }

    private static TenantAdminNotificationItem ToNotificationItem(UserNotification item)
    {
        return new TenantAdminNotificationItem(
            item.Id,
            item.UserId,
            item.NotificationType,
            item.Status,
            item.Severity,
            item.Title,
            item.Message,
            item.ActionLabel,
            item.ActionPath,
            item.SourceType,
            item.SourceId,
            item.DedupeKey,
            item.Metadata,
            item.CreatedBy,
            item.ReadAt,
            item.CreatedAt,
            item.UpdatedAt);
    }

    private static TenantAdminFeedbackItem ToFeedbackItem(Report report, User? user)
    {
        return new TenantAdminFeedbackItem(
            report.Id,
            report.UserId,
            user?.Name ?? user?.UserName,
            user?.Phone,
            report.QuestionId,
            report.Type,
            report.Title,
            report.Category,
            report.Description,
            report.Status,
            report.Priority,
            report.HandledBy,
            report.HandledAt,
            report.Resolution,
            report.Contact,
            report.Attachments,
            report.Metadata,
            report.CreatedAt,
            report.UpdatedAt);
    }

    private static int ResolveLimit(int? limit)
    {
        return Math.Clamp(limit ?? 100, 1, 500);
    }

    private static string? Normalize(string? value)
    {
        return string.IsNullOrWhiteSpace(value) ? null : value.Trim();
    }

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

    private static string? MaskPhone(string? phone)
    {
        var value = Normalize(phone);
        return value is { Length: >= 7 }
            ? $"{value[..3]}****{value[^4..]}"
            : value;
    }

    private static JsonElement JsonObjectOrDefault(JsonElement value)
    {
        return value.ValueKind == JsonValueKind.Object ? value.Clone() : JsonDefaults.Object();
    }

    private static JsonElement PermissionObject(JsonElement value)
    {
        var result = new Dictionary<string, bool>(StringComparer.Ordinal);
        if (value.ValueKind is JsonValueKind.Undefined or JsonValueKind.Null) return JsonDefaults.Object();

        if (value.ValueKind != JsonValueKind.Object)
            throw new TenantAdminDirectException("Permissions must be an object.", "invalid_permission_value");

        foreach (var property in value.EnumerateObject())
        {
            if (property.Value.ValueKind != JsonValueKind.True && property.Value.ValueKind != JsonValueKind.False)
                throw new TenantAdminDirectException("Permission values must be boolean.", "invalid_permission_value");

            if (property.Name != "*" && !IsPermissionKey(property.Name))
                throw new TenantAdminDirectException("Permission key was invalid.", "invalid_permission_key");

            result[property.Name] = property.Value.GetBoolean();
        }

        return JsonSerializer.SerializeToElement(result);
    }

    private static JsonElement AccessMap(JsonElement value, string codePrefix)
    {
        var result = new Dictionary<string, bool>(StringComparer.Ordinal);
        if (value.ValueKind is JsonValueKind.Undefined or JsonValueKind.Null) return JsonDefaults.Object();

        if (value.ValueKind != JsonValueKind.Object)
            throw new TenantAdminDirectException("Access map must be an object.", $"invalid_{codePrefix}");

        foreach (var property in value.EnumerateObject())
        {
            if (property.Value.ValueKind != JsonValueKind.True && property.Value.ValueKind != JsonValueKind.False)
                throw new TenantAdminDirectException("Access map values must be boolean.", $"invalid_{codePrefix}");

            if (!IsAccessKey(property.Name))
                throw new TenantAdminDirectException("Access map key was invalid.", $"invalid_{codePrefix}_key");

            result[property.Name] = property.Value.GetBoolean();
        }

        return JsonSerializer.SerializeToElement(result);
    }

    private static JsonElement DataScope(JsonElement value)
    {
        if (value.ValueKind is JsonValueKind.Undefined or JsonValueKind.Null) return JsonDefaults.Object();

        if (value.ValueKind != JsonValueKind.Object)
            throw new TenantAdminDirectException("Data scope must be an object.", "invalid_data_scope");

        var allowed = new HashSet<string>(StringComparer.Ordinal)
        {
            "mode",
            "regionIds",
            "contentNodeIds",
            "classIds",
            "ownLeadsOnly",
            "teamScope",
            "metadata"
        };
        foreach (var property in value.EnumerateObject())
            if (!allowed.Contains(property.Name))
                throw new TenantAdminDirectException("Data scope key was invalid.", "invalid_data_scope_key");

        return value.Clone();
    }

    private async Task AssertGrantableAsync(
        TenantAdminActor actor,
        TenantRole role,
        CancellationToken cancellationToken)
    {
        if (role is not (TenantRole.TenantOwner or TenantRole.TenantAdmin)) return;

        var isOwnerRoleHolder = await (
                from binding in dbContext.TenantBackendUserRoles.AsNoTracking()
                join backendRole in dbContext.TenantBackendRoles.AsNoTracking()
                    on new { binding.TenantId, binding.RoleId } equals new
                        { backendRole.TenantId, RoleId = backendRole.Id }
                where binding.TenantId == actor.TenantId &&
                      binding.UserId == actor.UserId &&
                      backendRole.Code == "tenant_owner" &&
                      backendRole.IsSystem &&
                      backendRole.Status == BackendRoleStatus.Active
                select binding.Id)
            .AnyAsync(cancellationToken);
        if (!isOwnerRoleHolder)
            throw new TenantAdminDirectException("Only tenant owner can grant owner/admin permissions.",
                "tenant_owner_required");
    }

    private static string RoleToPrimaryRole(TenantRole role)
    {
        return role switch
        {
            TenantRole.Student => "student",
            TenantRole.Teacher => "teacher",
            TenantRole.Sales => "sales",
            TenantRole.Agent => "agent",
            TenantRole.TenantOperator => "tenant_operator",
            TenantRole.TenantAdmin => "tenant_admin",
            TenantRole.TenantOwner => "tenant_owner",
            _ => "student"
        };
    }

    private static string NormalizeRoleCode(string value)
    {
        var code = new string(value.Trim().ToLowerInvariant()
                .Select(character => char.IsAsciiLetterOrDigit(character) || character is '_' or '-' ? character : '-')
                .ToArray())
            .Trim('-');
        while (code.Contains("--", StringComparison.Ordinal)) code = code.Replace("--", "-", StringComparison.Ordinal);

        if (code.Length is < 2 or > 64 || !char.IsAsciiLetter(code[0]))
            throw new TenantAdminDirectException("Role template code was invalid.", "invalid_role_template_code");

        return code;
    }

    private static string NormalizeDomain(string value)
    {
        var host = Normalize(value)?.ToLowerInvariant()
            .TrimEnd('.') ?? throw new TenantAdminDirectException("Domain host is required.", "domain_host_required");
        if (host.Length > 253 || host.Contains('/', StringComparison.Ordinal) ||
            host.Contains(':', StringComparison.Ordinal) ||
            !host.Contains('.', StringComparison.Ordinal))
            throw new TenantAdminDirectException("Domain host was invalid.", "invalid_domain_host");

        return host;
    }

    private static JsonElement MergeJsonObjects(JsonElement first, JsonElement second)
    {
        var result = new Dictionary<string, JsonElement>(StringComparer.Ordinal);
        if (first.ValueKind == JsonValueKind.Object)
            foreach (var property in first.EnumerateObject())
                result[property.Name] = property.Value.Clone();

        if (second.ValueKind == JsonValueKind.Object)
            foreach (var property in second.EnumerateObject())
                result[property.Name] = property.Value.Clone();

        return JsonSerializer.SerializeToElement(result);
    }

    private static void AssertNoSecrets(JsonElement value, string code)
    {
        if (value.ValueKind is JsonValueKind.Undefined or JsonValueKind.Null) return;

        if (value.ValueKind != JsonValueKind.Object)
            throw new TenantAdminDirectException("Public config must be an object.", $"invalid_{code}");

        foreach (var property in value.EnumerateObject())
        {
            var key = property.Name.ToLowerInvariant();
            if (key.Contains("secret", StringComparison.Ordinal) ||
                key.Contains("password", StringComparison.Ordinal) ||
                key.Contains("token", StringComparison.Ordinal) ||
                key.Contains("private", StringComparison.Ordinal) ||
                key is "appsecret" or "app_secret" or "accesskeysecret")
                throw new TenantAdminDirectException("Public config cannot contain secrets.",
                    "public_config_contains_secret");
        }
    }

    private static bool IsPermissionKey(string key)
    {
        return key.Length <= 100 &&
               key.Contains(':', StringComparison.Ordinal) &&
               key.All(character => char.IsAsciiLetterOrDigit(character) || character is ':' or '*');
    }

    private static bool IsAccessKey(string key)
    {
        return key.Length <= 100 &&
               key.Length > 0 &&
               char.IsAsciiLetter(key[0]) &&
               key.All(character => char.IsAsciiLetterOrDigit(character) || character is '_' or '.' or ':' or '-');
    }

    private static TEnum ParseEnum<TEnum>(string? value, TEnum fallback, string code)
        where TEnum : struct, Enum
    {
        return string.IsNullOrWhiteSpace(value) ? fallback : ParseEnum<TEnum>(value, code);
    }

    private static TEnum ParseEnum<TEnum>(string value, string code)
        where TEnum : struct, Enum
    {
        var normalized = value.Replace("_", string.Empty, StringComparison.Ordinal)
            .Replace("-", string.Empty, StringComparison.Ordinal);
        foreach (var candidate in Enum.GetValues<TEnum>())
            if (string.Equals(candidate.ToString(), normalized, StringComparison.OrdinalIgnoreCase))
                return candidate;

        throw new TenantAdminDirectException("Enum value was invalid.", code);
    }
}