using System;
using Microsoft.EntityFrameworkCore;
using TakeoutSaaS.Domain.Identity.Entities;
using TakeoutSaaS.Domain.Identity.Enums;
using TakeoutSaaS.Domain.Identity.Repositories;

namespace TakeoutSaaS.Infrastructure.Identity.Persistence;

/// <summary>
/// EF Core 后台用户仓储实现。
/// </summary>
public sealed class EfIdentityUserRepository(IdentityDbContext dbContext) : IIdentityUserRepository
{
    /// <summary>
    /// 根据账号获取后台用户。
    /// </summary>
    /// <param name="portal">Portal 类型。</param>
    /// <param name="tenantId">租户 ID（Portal=Tenant 时必填；Portal=Admin 时必须为空）。</param>
    /// <param name="account">账号。</param>
    /// <param name="cancellationToken">取消标记。</param>
    /// <returns>后台用户或 null。</returns>
    public Task<IdentityUser?> FindByAccountAsync(
        PortalType portal,
        long? tenantId,
        string account,
        CancellationToken cancellationToken = default)
    {
        // 1. 参数校验
        if (string.IsNullOrWhiteSpace(account))
        {
            throw new ArgumentException("账号不能为空。", nameof(account));
        }

        // 2. (空行后) 标准化账号
        var normalized = account.Trim();

        // 3. (空行后) 按 Portal 进行精确匹配
        return portal switch
        {
            PortalType.Admin when tenantId is null => dbContext.IdentityUsers
                .AsNoTracking()
                .FirstOrDefaultAsync(x => x.Portal == PortalType.Admin && x.Account == normalized, cancellationToken),
            PortalType.Admin => throw new ArgumentException("Portal=Admin 时 tenantId 必须为空。", nameof(tenantId)),
            PortalType.Tenant when tenantId.HasValue && tenantId.Value > 0 => dbContext.IdentityUsers
                .AsNoTracking()
                .FirstOrDefaultAsync(x => x.Portal == PortalType.Tenant && x.TenantId == tenantId.Value && x.Account == normalized, cancellationToken),
            PortalType.Tenant => throw new ArgumentException("Portal=Tenant 时必须指定 tenantId。", nameof(tenantId)),
            _ => throw new ArgumentOutOfRangeException(nameof(portal), portal, "未知 Portal 类型。")
        };
    }

    /// <summary>
    /// 判断账号是否存在。
    /// </summary>
    /// <param name="account">账号。</param>
    /// <param name="cancellationToken">取消标记。</param>
    /// <returns>存在返回 true。</returns>
    public Task<bool> ExistsByAccountAsync(string account, CancellationToken cancellationToken = default)
    {
        // 1. 参数校验
        if (string.IsNullOrWhiteSpace(account))
        {
            throw new ArgumentException("账号不能为空。", nameof(account));
        }

        // 2. (空行后) 标准化账号
        var normalized = account.Trim();

        // 3. (空行后) 查询是否存在（包含已删除数据）
        return dbContext.IdentityUsers
            .IgnoreQueryFilters()
            .AsNoTracking()
            .AnyAsync(x => x.Account == normalized, cancellationToken);
    }

    /// <summary>
    /// 判断账号是否存在（租户内，可排除指定用户）。
    /// </summary>
    /// <param name="tenantId">租户 ID。</param>
    /// <param name="account">账号。</param>
    /// <param name="excludeUserId">排除的用户 ID。</param>
    /// <param name="cancellationToken">取消标记。</param>
    /// <returns>存在返回 true。</returns>
    public Task<bool> ExistsByAccountAsync(long tenantId, string account, long? excludeUserId = null, CancellationToken cancellationToken = default)
    {
        // 1. 标准化账号
        var normalized = account.Trim();

        // 2. 构建查询（包含已删除数据）
        var query = dbContext.IdentityUsers
            .IgnoreQueryFilters()
            .AsNoTracking()
            .Where(x => x.Portal == PortalType.Tenant && x.TenantId == tenantId && x.Account == normalized);

        if (excludeUserId.HasValue)
        {
            query = query.Where(x => x.Id != excludeUserId.Value);
        }

        // 3. 返回是否存在
        return query.AnyAsync(cancellationToken);
    }

    /// <summary>
    /// 判断账号是否存在（按 Portal 与租户范围精确匹配，可排除指定用户）。
    /// </summary>
    /// <param name="portal">Portal 类型。</param>
    /// <param name="tenantId">租户 ID（Portal=Tenant 时必填；Portal=Admin 时必须为空）。</param>
    /// <param name="account">账号。</param>
    /// <param name="excludeUserId">排除的用户 ID。</param>
    /// <param name="cancellationToken">取消标记。</param>
    /// <returns>存在返回 true。</returns>
    public Task<bool> ExistsByAccountAsync(
        PortalType portal,
        long? tenantId,
        string account,
        long? excludeUserId = null,
        CancellationToken cancellationToken = default)
    {
        // 1. 参数校验
        if (string.IsNullOrWhiteSpace(account))
        {
            throw new ArgumentException("账号不能为空。", nameof(account));
        }

        // 2. (空行后) 校验 Portal 与 tenantId 组合
        ValidatePortalTenantId(portal, tenantId);

        // 3. (空行后) 标准化账号并构建查询（包含已删除数据）
        var normalized = account.Trim();
        var query = dbContext.IdentityUsers
            .IgnoreQueryFilters()
            .AsNoTracking()
            .Where(x => x.Portal == portal && x.TenantId == tenantId && x.Account == normalized);

        if (excludeUserId.HasValue)
        {
            query = query.Where(x => x.Id != excludeUserId.Value);
        }

        // 4. (空行后) 返回是否存在
        return query.AnyAsync(cancellationToken);
    }

    /// <summary>
    /// 判断手机号是否存在（租户内，可排除指定用户）。
    /// </summary>
    /// <param name="tenantId">租户 ID。</param>
    /// <param name="phone">手机号。</param>
    /// <param name="excludeUserId">排除的用户 ID。</param>
    /// <param name="cancellationToken">取消标记。</param>
    /// <returns>存在返回 true。</returns>
    public Task<bool> ExistsByPhoneAsync(long tenantId, string phone, long? excludeUserId = null, CancellationToken cancellationToken = default)
    {
        // 1. 标准化手机号
        var normalized = phone.Trim();

        // 2. 构建查询（包含已删除数据）
        var query = dbContext.IdentityUsers
            .IgnoreQueryFilters()
            .AsNoTracking()
            .Where(x => x.Portal == PortalType.Tenant && x.TenantId == tenantId && x.Phone == normalized);

        if (excludeUserId.HasValue)
        {
            query = query.Where(x => x.Id != excludeUserId.Value);
        }

        // 3. 返回是否存在
        return query.AnyAsync(cancellationToken);
    }

    /// <summary>
    /// 判断手机号是否存在（按 Portal 与租户范围精确匹配，可排除指定用户）。
    /// </summary>
    /// <param name="portal">Portal 类型。</param>
    /// <param name="tenantId">租户 ID（Portal=Tenant 时必填；Portal=Admin 时必须为空）。</param>
    /// <param name="phone">手机号。</param>
    /// <param name="excludeUserId">排除的用户 ID。</param>
    /// <param name="cancellationToken">取消标记。</param>
    /// <returns>存在返回 true。</returns>
    public Task<bool> ExistsByPhoneAsync(
        PortalType portal,
        long? tenantId,
        string phone,
        long? excludeUserId = null,
        CancellationToken cancellationToken = default)
    {
        // 1. 参数校验
        if (string.IsNullOrWhiteSpace(phone))
        {
            throw new ArgumentException("手机号不能为空。", nameof(phone));
        }

        // 2. (空行后) 校验 Portal 与 tenantId 组合
        ValidatePortalTenantId(portal, tenantId);

        // 3. (空行后) 标准化手机号并构建查询（包含已删除数据）
        var normalized = phone.Trim();
        var query = dbContext.IdentityUsers
            .IgnoreQueryFilters()
            .AsNoTracking()
            .Where(x => x.Portal == portal && x.TenantId == tenantId && x.Phone == normalized);

        if (excludeUserId.HasValue)
        {
            query = query.Where(x => x.Id != excludeUserId.Value);
        }

        // 4. (空行后) 返回是否存在
        return query.AnyAsync(cancellationToken);
    }

    /// <summary>
    /// 判断邮箱是否存在（租户内，可排除指定用户）。
    /// </summary>
    /// <param name="tenantId">租户 ID。</param>
    /// <param name="email">邮箱。</param>
    /// <param name="excludeUserId">排除的用户 ID。</param>
    /// <param name="cancellationToken">取消标记。</param>
    /// <returns>存在返回 true。</returns>
    public Task<bool> ExistsByEmailAsync(long tenantId, string email, long? excludeUserId = null, CancellationToken cancellationToken = default)
    {
        // 1. 标准化邮箱
        var normalized = email.Trim();

        // 2. 构建查询（包含已删除数据）
        var query = dbContext.IdentityUsers
            .IgnoreQueryFilters()
            .AsNoTracking()
            .Where(x => x.Portal == PortalType.Tenant && x.TenantId == tenantId && x.Email == normalized);

        if (excludeUserId.HasValue)
        {
            query = query.Where(x => x.Id != excludeUserId.Value);
        }

        // 3. 返回是否存在
        return query.AnyAsync(cancellationToken);
    }

    /// <summary>
    /// 判断邮箱是否存在（按 Portal 与租户范围精确匹配，可排除指定用户）。
    /// </summary>
    /// <param name="portal">Portal 类型。</param>
    /// <param name="tenantId">租户 ID（Portal=Tenant 时必填；Portal=Admin 时必须为空）。</param>
    /// <param name="email">邮箱。</param>
    /// <param name="excludeUserId">排除的用户 ID。</param>
    /// <param name="cancellationToken">取消标记。</param>
    /// <returns>存在返回 true。</returns>
    public Task<bool> ExistsByEmailAsync(
        PortalType portal,
        long? tenantId,
        string email,
        long? excludeUserId = null,
        CancellationToken cancellationToken = default)
    {
        // 1. 参数校验
        if (string.IsNullOrWhiteSpace(email))
        {
            throw new ArgumentException("邮箱不能为空。", nameof(email));
        }

        // 2. (空行后) 校验 Portal 与 tenantId 组合
        ValidatePortalTenantId(portal, tenantId);

        // 3. (空行后) 标准化邮箱并构建查询（包含已删除数据）
        var normalized = email.Trim();
        var query = dbContext.IdentityUsers
            .IgnoreQueryFilters()
            .AsNoTracking()
            .Where(x => x.Portal == portal && x.TenantId == tenantId && x.Email == normalized);

        if (excludeUserId.HasValue)
        {
            query = query.Where(x => x.Id != excludeUserId.Value);
        }

        // 4. (空行后) 返回是否存在
        return query.AnyAsync(cancellationToken);
    }

    /// <summary>
    /// 根据 ID 获取后台用户。
    /// </summary>
    /// <param name="userId">用户 ID。</param>
    /// <param name="cancellationToken">取消标记。</param>
    /// <returns>后台用户或 null。</returns>
    public Task<IdentityUser?> FindByIdAsync(long userId, CancellationToken cancellationToken = default)
        => dbContext.IdentityUsers.AsNoTracking().FirstOrDefaultAsync(x => x.Id == userId, cancellationToken);

    /// <summary>
    /// 根据 ID 获取后台用户（用于更新，返回可跟踪实体）。
    /// </summary>
    /// <param name="userId">用户 ID。</param>
    /// <param name="cancellationToken">取消标记。</param>
    /// <returns>后台用户或 null。</returns>
    public Task<IdentityUser?> GetForUpdateAsync(long userId, CancellationToken cancellationToken = default)
        => dbContext.IdentityUsers.FirstOrDefaultAsync(x => x.Id == userId, cancellationToken);

    /// <summary>
    /// 根据 ID 获取后台用户（用于更新，包含已删除数据）。
    /// </summary>
    /// <param name="userId">用户 ID。</param>
    /// <param name="cancellationToken">取消标记。</param>
    /// <returns>后台用户或 null。</returns>
    public Task<IdentityUser?> GetForUpdateIncludingDeletedAsync(long userId, CancellationToken cancellationToken = default)
        => dbContext.IdentityUsers
            .IgnoreQueryFilters()
            .FirstOrDefaultAsync(x => x.Id == userId, cancellationToken);

    /// <summary>
    /// 按租户与关键字搜索后台用户（只读）。
    /// </summary>
    /// <param name="tenantId">租户 ID。</param>
    /// <param name="keyword">关键字（账号/名称）。</param>
    /// <param name="cancellationToken">取消标记。</param>
    /// <returns>后台用户列表。</returns>
    public async Task<IReadOnlyList<IdentityUser>> SearchAsync(long tenantId, string? keyword, CancellationToken cancellationToken = default)
    {
        // 1. 构建基础查询
        var query = dbContext.IdentityUsers
            .AsNoTracking()
            .Where(x => x.Portal == PortalType.Tenant && x.TenantId == tenantId);

        // 2. 关键字过滤
        if (!string.IsNullOrWhiteSpace(keyword))
        {
            var normalized = keyword.Trim();
            query = query.Where(x => x.Account.Contains(normalized) || x.DisplayName.Contains(normalized));
        }

        // 3. 返回列表
        return await query.ToListAsync(cancellationToken);
    }

    /// <summary>
    /// 分页查询后台用户列表。
    /// </summary>
    /// <param name="filter">查询过滤条件。</param>
    /// <param name="cancellationToken">取消标记。</param>
    /// <returns>分页结果。</returns>
    public async Task<(IReadOnlyList<IdentityUser> Items, int Total)> SearchPagedAsync(
        IdentityUserSearchFilter filter,
        CancellationToken cancellationToken = default)
    {
        // 1. 校验 Portal 与 tenantId 组合
        ValidatePortalTenantId(filter.Portal, filter.TenantId);

        // 2. (空行后) 构建基础查询（按 Portal + TenantId 精确匹配）
        var query = dbContext.IdentityUsers
            .AsNoTracking()
            .Where(x => x.Portal == filter.Portal && x.TenantId == filter.TenantId);

        // 3. (空行后) 包含软删除数据时忽略全局过滤
        if (filter.IncludeDeleted)
        {
            query = query.IgnoreQueryFilters();
        }

        // 4. (空行后) 关键字筛选
        if (!string.IsNullOrWhiteSpace(filter.Keyword))
        {
            var normalized = filter.Keyword.Trim();
            var likeValue = $"%{normalized}%";
            query = query.Where(x =>
                EF.Functions.ILike(x.Account, likeValue)
                || EF.Functions.ILike(x.DisplayName, likeValue)
                || (x.Phone != null && EF.Functions.ILike(x.Phone, likeValue))
                || (x.Email != null && EF.Functions.ILike(x.Email, likeValue)));
        }

        // 5. (空行后) 状态过滤
        if (filter.Status.HasValue)
        {
            query = query.Where(x => x.Status == filter.Status.Value);
        }

        // 6. (空行后) 角色过滤
        if (filter.RoleId.HasValue)
        {
            var roleId = filter.RoleId.Value;
            var userRoles = dbContext.UserRoles
                .AsNoTracking()
                .Where(x => x.Portal == filter.Portal && x.TenantId == filter.TenantId);

            // 6.1 包含软删除数据时忽略全局过滤
            if (filter.IncludeDeleted)
            {
                userRoles = userRoles.IgnoreQueryFilters();
            }

            // 6.2 (空行后) 用户角色关联过滤
            query = query.Where(user => userRoles.Any(role => role.UserId == user.Id && role.RoleId == roleId));
        }

        // 7. (空行后) 时间范围过滤
        if (filter.CreatedAtFrom.HasValue)
        {
            query = query.Where(x => x.CreatedAt >= filter.CreatedAtFrom.Value);
        }

        if (filter.CreatedAtTo.HasValue)
        {
            query = query.Where(x => x.CreatedAt <= filter.CreatedAtTo.Value);
        }

        if (filter.LastLoginFrom.HasValue)
        {
            query = query.Where(x => x.LastLoginAt >= filter.LastLoginFrom.Value);
        }

        if (filter.LastLoginTo.HasValue)
        {
            query = query.Where(x => x.LastLoginAt <= filter.LastLoginTo.Value);
        }

        // 8. (空行后) 排序
        var sorted = filter.SortBy?.ToLowerInvariant() switch
        {
            "account" => filter.SortDescending
                ? query.OrderByDescending(x => x.Account)
                : query.OrderBy(x => x.Account),
            "displayname" => filter.SortDescending
                ? query.OrderByDescending(x => x.DisplayName)
                : query.OrderBy(x => x.DisplayName),
            "status" => filter.SortDescending
                ? query.OrderByDescending(x => x.Status)
                : query.OrderBy(x => x.Status),
            "lastloginat" => filter.SortDescending
                ? query.OrderByDescending(x => x.LastLoginAt)
                : query.OrderBy(x => x.LastLoginAt),
            _ => filter.SortDescending
                ? query.OrderByDescending(x => x.CreatedAt)
                : query.OrderBy(x => x.CreatedAt)
        };

        // 9. (空行后) 分页
        var page = filter.Page <= 0 ? 1 : filter.Page;
        var pageSize = filter.PageSize <= 0 ? 20 : filter.PageSize;
        var total = await sorted.CountAsync(cancellationToken);
        var items = await sorted
            .Skip((page - 1) * pageSize)
            .Take(pageSize)
            .ToListAsync(cancellationToken);

        return (items, total);
    }

    /// <summary>
    /// 根据 ID 集合批量获取后台用户（只读）。
    /// </summary>
    /// <param name="tenantId">租户 ID。</param>
    /// <param name="userIds">用户 ID 集合。</param>
    /// <param name="cancellationToken">取消标记。</param>
    /// <returns>后台用户列表。</returns>
    public async Task<IReadOnlyList<IdentityUser>> GetByIdsAsync(long tenantId, IEnumerable<long> userIds, CancellationToken cancellationToken = default)
    {
        // 1. 去重并快速返回空集合
        var ids = userIds.Distinct().ToArray();
        if (ids.Length == 0)
        {
            return Array.Empty<IdentityUser>();
        }

        // 2. (空行后) 查询并返回列表
        return await dbContext.IdentityUsers
            .AsNoTracking()
            .Where(x => x.Portal == PortalType.Tenant && x.TenantId == tenantId && ids.Contains(x.Id))
            .ToListAsync(cancellationToken);
    }

    /// <summary>
    /// 批量获取后台用户（可用于更新，支持包含已删除数据）。
    /// </summary>
    /// <param name="tenantId">租户 ID。</param>
    /// <param name="userIds">用户 ID 集合。</param>
    /// <param name="includeDeleted">是否包含已删除数据。</param>
    /// <param name="cancellationToken">取消标记。</param>
    /// <returns>后台用户列表。</returns>
    public async Task<IReadOnlyList<IdentityUser>> GetForUpdateByIdsAsync(
        long tenantId,
        IEnumerable<long> userIds,
        bool includeDeleted,
        CancellationToken cancellationToken = default)
    {
        // 1. 去重并快速返回空集合
        var ids = userIds.Distinct().ToArray();
        if (ids.Length == 0)
        {
            return Array.Empty<IdentityUser>();
        }

        // 2. (空行后) 构建查询
        var query = dbContext.IdentityUsers
            .Where(x => x.Portal == PortalType.Tenant && x.TenantId == tenantId && ids.Contains(x.Id));

        // 3. (空行后) 包含软删除数据时忽略全局过滤
        if (includeDeleted)
        {
            query = query.IgnoreQueryFilters();
        }

        // 4. (空行后) 返回列表
        return await query.ToListAsync(cancellationToken);
    }

    /// <summary>
    /// 批量获取后台用户（可用于更新，按 Portal 与租户范围精确匹配，支持包含已删除数据）。
    /// </summary>
    /// <param name="portal">Portal 类型。</param>
    /// <param name="tenantId">租户 ID（Portal=Tenant 时必填；Portal=Admin 时必须为空）。</param>
    /// <param name="userIds">用户 ID 集合。</param>
    /// <param name="includeDeleted">是否包含已删除数据。</param>
    /// <param name="cancellationToken">取消标记。</param>
    /// <returns>后台用户列表。</returns>
    public async Task<IReadOnlyList<IdentityUser>> GetForUpdateByIdsAsync(
        PortalType portal,
        long? tenantId,
        IEnumerable<long> userIds,
        bool includeDeleted,
        CancellationToken cancellationToken = default)
    {
        // 1. 去重并快速返回空集合
        var ids = userIds.Distinct().ToArray();
        if (ids.Length == 0)
        {
            return Array.Empty<IdentityUser>();
        }

        // 2. (空行后) 校验 Portal 与 tenantId 组合
        ValidatePortalTenantId(portal, tenantId);

        // 3. (空行后) 构建查询
        var query = dbContext.IdentityUsers
            .Where(x => x.Portal == portal && x.TenantId == tenantId && ids.Contains(x.Id));

        // 4. (空行后) 包含软删除数据时忽略全局过滤
        if (includeDeleted)
        {
            query = query.IgnoreQueryFilters();
        }

        // 5. (空行后) 返回列表
        return await query.ToListAsync(cancellationToken);
    }

    /// <summary>
    /// 新增后台用户。
    /// </summary>
    /// <param name="user">后台用户实体。</param>
    /// <param name="cancellationToken">取消标记。</param>
    /// <returns>异步任务。</returns>
    public Task AddAsync(IdentityUser user, CancellationToken cancellationToken = default)
    {
        // 1. 添加实体
        dbContext.IdentityUsers.Add(user);
        // 2. 返回完成任务
        return Task.CompletedTask;
    }

    /// <summary>
    /// 删除后台用户（软删除）。
    /// </summary>
    /// <param name="user">后台用户实体。</param>
    /// <param name="cancellationToken">取消标记。</param>
    /// <returns>异步任务。</returns>
    public Task RemoveAsync(IdentityUser user, CancellationToken cancellationToken = default)
    {
        // 1. 标记删除
        dbContext.IdentityUsers.Remove(user);
        // 2. 返回完成任务
        return Task.CompletedTask;
    }

    // Portal 与 tenantId 参数校验。
    private static void ValidatePortalTenantId(PortalType portal, long? tenantId)
    {
        // 1. 按 Portal 规则校验 tenantId
        switch (portal)
        {
            case PortalType.Admin when tenantId is null:
                return;
            case PortalType.Admin:
                throw new ArgumentException("Portal=Admin 时 tenantId 必须为空。", nameof(tenantId));
            case PortalType.Tenant when tenantId.HasValue && tenantId.Value > 0:
                return;
            case PortalType.Tenant:
                throw new ArgumentException("Portal=Tenant 时必须指定 tenantId。", nameof(tenantId));
            default:
                throw new ArgumentOutOfRangeException(nameof(portal), portal, "未知 Portal 类型。");
        }
    }

    /// <summary>
    /// 持久化仓储变更。
    /// </summary>
    /// <param name="cancellationToken">取消标记。</param>
    /// <returns>保存任务。</returns>
    public Task SaveChangesAsync(CancellationToken cancellationToken = default)
        => dbContext.SaveChangesAsync(cancellationToken);
}
