using System.Collections.Concurrent;
using System.Text.RegularExpressions;
using Dpz.Core.Entity.Base.ExpressTreeQuery;
using Dpz.Core.Public.ViewModel.Request;
using Dpz.Core.Public.ViewModel.RequestEvent;
using Dpz.Core.Public.ViewModel.Response;

namespace Dpz.Core.Service.RepositoryServiceImpl;

public class CodeFileSystemEntryService(
    IRepository<CodeFileSystemEntry> repository,
    IConfiguration configuration,
    ILogger<CodeFileSystemEntryService> logger,
    IMediator mediator,
    IFusionCache fusionCache
) : AbstractCacheService(fusionCache), ICodeFileSystemEntryService
{
    protected override TimeSpan CacheDefaultExpiration => TimeSpan.FromDays(1);

    private readonly Lazy<CodeViewOption> _codeViewCfg = new(() =>
    {
        var codeView = configuration.GetSection("CodeView").Get<CodeViewOption>();
        if (
            codeView == null
            || string.IsNullOrEmpty(codeView.SourceCodeRoot)
            || !Directory.Exists(codeView.SourceCodeRoot)
        )
        {
            logger.LogError("代码查看配置错误:{Cfg}", JsonSerializer.Serialize(codeView));
            throw new BusinessException("appsettings.json 配置错误,源代码目录不存在");
        }

        return codeView;
    });

    public async Task<CodeFileSystemEntryResponse?> FindByPathAsync(
        IReadOnlyCollection<string>? pathSegments,
        CancellationToken cancellationToken = default
    )
    {
        var segments = NormalizeSegments(pathSegments);
        var cacheKey = BuildPathKey(segments);
        return await GetOrSetCacheAsync<CodeFileSystemEntryResponse?>(
            nameof(FindByPathAsync),
            async (_, ct) => await FindByPathCoreAsync(segments, ct),
            new { Path = cacheKey },
            cancellationToken: cancellationToken
        );
    }

    public async Task<CodeFileSystemEntryResponse?> FindByPathWithoutCacheAsync(
        IReadOnlyCollection<string>? pathSegments,
        CancellationToken cancellationToken = default
    )
    {
        var segments = NormalizeSegments(pathSegments);
        return await FindByPathCoreAsync(segments, cancellationToken);
    }

    public async Task<List<CodeFileSystemEntryResponse>> GetChildrenAsync(
        IReadOnlyCollection<string>? parentPathSegments,
        CancellationToken cancellationToken = default
    )
    {
        var segments = NormalizeSegments(parentPathSegments);
        var cacheKey = BuildPathKey(segments);
        return await GetOrSetCacheAsync<List<CodeFileSystemEntryResponse>>(
            nameof(GetChildrenAsync),
            async (_, ct) =>
            {
                var filter = Builders<CodeFileSystemEntry>.Filter.Eq(
                    x => x.ParentPathSegments,
                    segments
                );
                var entries = await repository.SearchFor(filter).ToListAsync(ct);
                return await MapToResponseListAsync(entries, ct);
            },
            new { Parent = cacheKey },
            cancellationToken: cancellationToken
        );
    }

    public async Task<List<CodeFileSystemEntryResponse>> SearchAsync(
        string keyword,
        CancellationToken cancellationToken = default
    )
    {
        if (string.IsNullOrWhiteSpace(keyword))
        {
            return [];
        }

        return await GetOrSetCacheAsync<List<CodeFileSystemEntryResponse>>(
            nameof(SearchAsync),
            async (_, ct) =>
            {
                var filter = Builders<CodeFileSystemEntry>.Filter.Regex(
                    x => x.Name,
                    new BsonRegularExpression(keyword, "i")
                );
                var entries = await repository.SearchFor(filter).ToListAsync(ct);
                return await MapToResponseListAsync(entries, ct);
            },
            new { Keyword = keyword },
            cancellationToken: cancellationToken
        );
    }

    public async Task<IPagedList<CodeFileSystemEntryListResponse>> GetPagedListAsync(
        CodeFlatRequest request,
        CancellationToken cancellationToken = default
    )
    {
        // 生成查询条件
        var filter = GenerateConditionFilter.GenerateConditionExpressionTree<
            CodeFileSystemEntry,
            CodeFlatRequest
        >(request);

        var query = repository.SearchFor(filter);

        // 应用排序
        if (request.SortField.HasValue)
        {
            query = request.SortField.Value switch
            {
                CodeFileSortField.Size => request.IsDescending
                    ? query.OrderByDescending(x => x.Size)
                    : query.OrderBy(x => x.Size),
                CodeFileSortField.CreatedTime => request.IsDescending
                    ? query.OrderByDescending(x => x.CreatedTime)
                    : query.OrderBy(x => x.CreatedTime),
                CodeFileSortField.LastWriteTime => request.IsDescending
                    ? query.OrderByDescending(x => x.LastWriteTime)
                    : query.OrderBy(x => x.LastWriteTime),
                CodeFileSortField.LastUpdateTime => request.IsDescending
                    ? query.OrderByDescending(x => x.LastUpdateTime)
                    : query.OrderBy(x => x.LastUpdateTime),
                CodeFileSortField.AiAnalyzeTime => request.IsDescending
                    ? query.OrderByDescending(x => x.AiAnalyzeTime)
                    : query.OrderBy(x => x.AiAnalyzeTime),
                _ => query,
            };
        }

        // 执行分页查询
        var entityPagedList = await query
            .Select(x => new CodeFileSystemEntryListResponse
            {
                Id = x.Id.ToString(),
                PathSegments = x.PathSegments,
                Name = x.Name,
                ParentPathSegments = x.ParentPathSegments,
                IsDirectory = x.IsDirectory,
                Extension = x.Extension,
                Size = x.Size,
                Hash = x.Hash,
                CodeFileContentType = x.CodeFileContentType,
                CodeLanguage = x.CodeLanguage,
                Tags = x.Tags,
                CreatedTime = x.CreatedTime,
                LastWriteTime = x.LastWriteTime,
                LastUpdateTime = x.LastUpdateTime,
                Description = x.Description,
                AiAnalyzeTime = x.AiAnalyzeTime,
                AiAnalyzeHash = x.AiAnalyzeHash,
            })
            .ToPagedListAsync(request.PageIndex, request.PageSize, cancellationToken);

        entityPagedList.ForEach(x =>
            x.CodeLanguage = ResolveCodeLanguage(x.IsDirectory, x.Extension, x.Name)
        );

        return entityPagedList;
    }

    public async Task<List<string[]>> GetAllDirectoriesAsync(
        string[]? pathSegments = null,
        CancellationToken cancellationToken = default
    )
    {
        var normalizedPath = NormalizeSegments(pathSegments);
        var pathKey = BuildPathKey(normalizedPath);

        return await GetOrSetCacheAsync<List<string[]>>(
            nameof(GetAllDirectoriesAsync),
            async (_, ct) =>
            {
                var filterBuilder = Builders<CodeFileSystemEntry>.Filter;
                var filter = filterBuilder.Eq(x => x.IsDirectory, true);

                // 如果指定了路径,则只查询该路径及其所有子目录
                if (normalizedPath.Count > 0)
                {
                    // 查询所有 PathSegments 以 normalizedPath 开头的目录
                    // 先查询所有目录,然后在内存中过滤
                    var allDirectories = await repository
                        .SearchFor(filter)
                        .ToListAsync(cancellationToken: ct);

                    var filteredDirectories = allDirectories
                        .Where(x =>
                            x.PathSegments.Count >= normalizedPath.Count
                            && x.PathSegments.Take(normalizedPath.Count)
                                .SequenceEqual(normalizedPath)
                        )
                        .ToList();

                    return filteredDirectories
                        .Select(x => x.PathSegments.ToArray())
                        .OrderBy(x => x.Length)
                        .ThenBy(x => string.Join("/", x))
                        .ToList();
                }

                // 没有指定路径,返回所有目录
                var directories = await repository
                    .SearchFor(filter)
                    .ToListAsync(cancellationToken: ct);

                return directories
                    .Select(x => x.PathSegments.ToArray())
                    .OrderBy(x => x.Length)
                    .ThenBy(x => string.Join("/", x))
                    .ToList();
            },
            new { Path = pathKey },
            cancellationToken: cancellationToken
        );
    }

    public async Task<bool> SaveDescriptionAsync(
        string[]? path,
        string name,
        string? description,
        CancellationToken cancellationToken = default
    )
    {
        if (string.IsNullOrWhiteSpace(name))
        {
            return false;
        }

        var segments = NormalizeSegments(path).ToList();
        segments.Add(name);
        var filter = Builders<CodeFileSystemEntry>.Filter.Eq(x => x.PathSegments, segments);
        var entry = await repository.SearchFor(filter).FirstOrDefaultAsync(cancellationToken);
        if (entry == null)
        {
            return false;
        }

        entry.Description = description;
        entry.LastUpdateTime = DateTime.Now;
        await repository.UpdateAsync(entry, cancellationToken);
        await InvalidateEntryCachesAsync(entry);
        await InvalidateSearchCacheAsync(name);
        return true;
    }

    public async Task SaveAiAnalyzeResultAsync(
        string[]? path,
        string name,
        string analyzeResult,
        string? analyzedHash = null,
        CancellationToken cancellationToken = default
    )
    {
        if (string.IsNullOrWhiteSpace(name))
        {
            return;
        }

        var segments = NormalizeSegments(path).ToList();
        segments.Add(name);
        var filter = Builders<CodeFileSystemEntry>.Filter.Eq(x => x.PathSegments, segments);
        var entry = await repository.SearchFor(filter).FirstOrDefaultAsync(cancellationToken);
        if (entry == null)
        {
            return;
        }

        entry.AiAnalyzeResult = analyzeResult;
        entry.AiAnalyzeHash = analyzedHash ?? entry.Hash;
        entry.AiAnalyzeTime = DateTime.Now;
        entry.LastUpdateTime = DateTime.Now;
        await repository.UpdateAsync(entry, cancellationToken);
        await InvalidateEntryCachesAsync(entry);
        await InvalidateSearchCacheAsync(name);
    }

    public async Task<bool> ShouldAnalyzeAsync(
        string[]? path,
        string? name,
        CodeContainer? codeContainer,
        CancellationToken cancellationToken = default
    )
    {
        var useAiAnalyze = configuration.GetValue("CodeUseAIAnalyze", false);
        if (!useAiAnalyze)
        {
            return false;
        }

        if (
            string.IsNullOrWhiteSpace(name) || string.IsNullOrWhiteSpace(codeContainer?.CodeContent)
        )
        {
            return false;
        }

        // 检查代码行数,少于50行不分析
        var lineCount = codeContainer
            .CodeContent.Split(["\r\n", "\r", "\n"], StringSplitOptions.None)
            .Length;
        if (lineCount < 50)
        {
            return false;
        }

        if (name.Contains(".min"))
        {
            return false;
        }

        var language = codeContainer.Language?.ToLowerInvariant();
        var isSupportedLanguage = language is "csharp" or "javascript" or "typescript";
        if (!isSupportedLanguage)
        {
            return false;
        }

        var segments = NormalizeSegments(path).ToList();
        var filter = Builders<CodeFileSystemEntry>.Filter.Eq(x => x.PathSegments, segments);
        var entry = await repository.SearchFor(filter).FirstOrDefaultAsync(cancellationToken);
        if (entry == null)
        {
            return false;
        }

        if (string.IsNullOrWhiteSpace(entry.AiAnalyzeResult))
        {
            return true;
        }

        if (!string.Equals(entry.AiAnalyzeHash, entry.Hash, StringComparison.OrdinalIgnoreCase))
        {
            return true;
        }

        return false;
    }

    public async Task SyncAsync(CancellationToken cancellationToken = default)
    {
        var syncStopwatch = Stopwatch.StartNew();
        var codeView = _codeViewCfg.Value;
        var sourceRoot = codeView.SourceCodeRoot;

        logger.LogInformation("开始同步代码,源路径: {SourceRoot}", sourceRoot);

        // 构建文件系统映射表
        var fileSystemInfos = GetAllFileSystemInfos(sourceRoot, codeView);
        var fileSystemMap = new Dictionary<string, FileSystemInfo>(
            StringComparer.OrdinalIgnoreCase
        );

        foreach (var item in fileSystemInfos)
        {
            var relativeSegments = GetRelativeSegments(sourceRoot, item.FullName);
            if (relativeSegments.Count == 0)
            {
                continue;
            }

            var key = BuildPathKey(relativeSegments);
            fileSystemMap[key] = item;
        }

        logger.LogInformation("扫描完成,共 {Count} 个文件系统项", fileSystemMap.Count);

        // 构建数据库映射表
        var dbEntries = await repository.SearchFor(x => true).ToListAsync(cancellationToken);
        var dbMap = dbEntries.ToDictionary(
            x => BuildPathKey(x.PathSegments),
            StringComparer.OrdinalIgnoreCase
        );

        var insertList = new ConcurrentBag<CodeFileSystemEntry>();
        var updateList = new ConcurrentBag<CodeFileSystemEntry>();
        var deleteIds = new List<ObjectId>();

        // 并行处理文件:比对文件系统与数据库,找出需要新增或更新的项
        var maxDegreeOfParallelism = Math.Max(1, Environment.ProcessorCount / 2);
        await Parallel.ForEachAsync(
            fileSystemMap,
            new ParallelOptions
            {
                MaxDegreeOfParallelism = maxDegreeOfParallelism,
                CancellationToken = cancellationToken,
            },
            async (pair, ct) =>
            {
                if (!dbMap.TryGetValue(pair.Key, out var exist))
                {
                    var entry = await BuildEntryAsync(codeView, pair.Value, ct);
                    if (entry != null)
                    {
                        insertList.Add(entry);
                    }

                    return;
                }

                var updated = await BuildUpdatedEntryAsync(codeView, exist, pair.Value, ct);
                if (updated != null)
                {
                    updateList.Add(updated);
                }
            }
        );

        // 找出需要删除的项(DB 中存在但文件系统中已不存在)
        foreach (var entry in dbEntries)
        {
            var key = BuildPathKey(entry.PathSegments);
            if (!fileSystemMap.ContainsKey(key))
            {
                deleteIds.Add(entry.Id);
            }
        }

        var insertListFinal = insertList.ToList();
        var updateListFinal = updateList.ToList();

        // 批量执行数据库操作
        if (insertListFinal.Count > 0)
        {
            await repository.InsertAsync(insertListFinal, cancellationToken);
            logger.LogInformation("已新增 {Count} 条记录", insertListFinal.Count);
        }

        if (updateListFinal.Count > 0)
        {
            await repository.UpdateAsync(updateListFinal, cancellationToken);
            logger.LogInformation("已更新 {Count} 条记录", updateListFinal.Count);
        }

        if (deleteIds.Count > 0)
        {
            var filter = Builders<CodeFileSystemEntry>.Filter.In(x => x.Id, deleteIds);
            await repository.DeleteAsync(filter, cancellationToken);
            logger.LogInformation("已删除 {Count} 条记录", deleteIds.Count);
        }

        syncStopwatch.Stop();
        logger.LogInformation(
            "代码同步完成,新增:{InsertCount} 条,更新:{UpdateCount} 条,删除:{DeleteCount} 条,耗时:{ElapsedMs}ms",
            insertListFinal.Count,
            updateListFinal.Count,
            deleteIds.Count,
            syncStopwatch.ElapsedMilliseconds
        );

        if (insertListFinal.Count > 0)
        {
            await mediator.Send(
                new CodeCompatibleRequest
                {
                    NewEntries = insertListFinal
                        .Select(x => new CodeCompatibleEntry
                        {
                            ParentPathSegments = x.ParentPathSegments,
                            Name = x.Name,
                        })
                        .ToList(),
                },
                cancellationToken
            );
        }

        await RemoveByMethodAsync(nameof(FindByPathAsync), cancellationToken);
        await RemoveByMethodAsync(nameof(GetChildrenAsync), cancellationToken);
        await RemoveByMethodAsync(nameof(SearchAsync), cancellationToken);
    }

    private async Task InvalidateEntryCachesAsync(CodeFileSystemEntry entry)
    {
        var entryKey = BuildPathKey(entry.PathSegments);
        await RemoveCacheAsync(nameof(FindByPathAsync), new { Path = entryKey });

        var parentKey = BuildPathKey(entry.ParentPathSegments);
        await RemoveCacheAsync(nameof(GetChildrenAsync), new { Parent = parentKey });
    }

    private async Task<CodeFileSystemEntryResponse?> FindByPathCoreAsync(
        List<string> segments,
        CancellationToken cancellationToken
    )
    {
        // 先尝试精确匹配(区分大小写)
        var filter = Builders<CodeFileSystemEntry>.Filter.Eq(x => x.PathSegments, segments);
        var entry = await repository.SearchFor(filter).FirstOrDefaultAsync(cancellationToken);

        if (entry == null && segments.Count > 0)
        {
            // 如果精确匹配失败,则构建不区分大小写的正则过滤器
            var regexes = segments
                .Select(x => new BsonRegularExpression($"^{Regex.Escape(x)}$", "i"))
                .ToArray();
            var caseInsensitiveFilter = new BsonDocumentFilterDefinition<CodeFileSystemEntry>(
                new BsonDocument
                {
                    {
                        nameof(CodeFileSystemEntry.PathSegments),
                        new BsonDocument
                        {
                            { "$size", segments.Count },
                            { "$all", new BsonArray(regexes) },
                        }
                    },
                }
            );

#if DEBUG
            var serializerRegistry = BsonSerializer.SerializerRegistry;
            var documentSerializer = serializerRegistry.GetSerializer<CodeFileSystemEntry>();
            var serializerArgs = new RenderArgs<CodeFileSystemEntry>(
                documentSerializer,
                serializerRegistry
            );
            var bsonDocumentQuery = caseInsensitiveFilter.Render(serializerArgs);
            logger.LogInformation(
                "构建查询条件: {BsonDocumentQuery}",
                bsonDocumentQuery.ToString()
            );
#endif
            entry = await repository
                .SearchFor(caseInsensitiveFilter)
                .FirstOrDefaultAsync(cancellationToken);
        }

        if (entry == null)
        {
            return null;
        }

        var readmeContent = entry.IsDirectory
            ? await ResolveReadmeContentAsync(entry.PathSegments, cancellationToken)
            : null;
        return MapToResponse(entry, readmeContent);
    }

    private async Task InvalidateSearchCacheAsync(string name)
    {
        if (string.IsNullOrWhiteSpace(name))
        {
            return;
        }

        await RemoveCacheAsync(nameof(SearchAsync), new { Keyword = name });
    }

    private static List<string> NormalizeSegments(IReadOnlyCollection<string>? segments)
    {
        return segments?.Where(x => !string.IsNullOrWhiteSpace(x)).ToList() ?? [];
    }

    private CodeFileSystemEntryResponse MapToResponse(
        CodeFileSystemEntry entry,
        string? readmeContent
    )
    {
        return new CodeFileSystemEntryResponse
        {
            Id = entry.Id.ToString(),
            PathSegments = entry.PathSegments.ToList(),
            Name = entry.Name,
            ParentPathSegments = entry.ParentPathSegments.ToList(),
            IsDirectory = entry.IsDirectory,
            Extension = entry.Extension,
            Size = entry.Size,
            Hash = entry.Hash,
            CodeFileContentType = entry.CodeFileContentType,
            FileContent = entry.FileContent,
            CodeLanguage = ResolveCodeLanguage(entry.IsDirectory, entry.Extension, entry.Name),
            Tags = entry.Tags.ToList(),
            CreatedTime = entry.CreatedTime,
            LastWriteTime = entry.LastWriteTime,
            LastUpdateTime = entry.LastUpdateTime,
            Description = entry.Description,
            AiAnalyzeResult = entry.AiAnalyzeResult,
            AiAnalyzeTime = entry.AiAnalyzeTime,
            AiAnalyzeHash = entry.AiAnalyzeHash,
            ReadmeContent = readmeContent,
        };
    }

    private async Task<List<CodeFileSystemEntryResponse>> MapToResponseListAsync(
        List<CodeFileSystemEntry> entries,
        CancellationToken cancellationToken
    )
    {
        if (entries.Count == 0)
        {
            return [];
        }

        var directories = entries.Where(x => x.IsDirectory).ToList();
        var readmeMap = await ResolveReadmeContentMapAsync(directories, cancellationToken);

        return entries
            .Select(entry =>
            {
                string? readmeContent = null;
                if (
                    entry.IsDirectory
                    && readmeMap.TryGetValue(BuildPathKey(entry.PathSegments), out var content)
                )
                {
                    readmeContent = content;
                }

                return MapToResponse(entry, readmeContent);
            })
            .ToList();
    }

    private string? ResolveCodeLanguage(bool isDirectory, string? extension, string? name)
    {
        if (isDirectory)
        {
            return null;
        }

        var ext = string.IsNullOrWhiteSpace(extension) ? name : extension;
        if (string.IsNullOrWhiteSpace(ext))
        {
            return null;
        }

        var normalized = ext.ToLower();
        return
            _codeViewCfg.Value.ExtensionToLanguage?.TryGetValue(normalized, out var language)
            == true
            ? language
            : null;
    }

    private async Task<Dictionary<string, string?>> ResolveReadmeContentMapAsync(
        List<CodeFileSystemEntry> directories,
        CancellationToken cancellationToken
    )
    {
        if (directories.Count == 0)
        {
            return new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
        }

        var parentSegmentsList = directories.Select(x => x.PathSegments).ToList();
        var filter = Builders<CodeFileSystemEntry>.Filter.And(
            Builders<CodeFileSystemEntry>.Filter.In(x => x.ParentPathSegments, parentSegmentsList),
            Builders<CodeFileSystemEntry>.Filter.Regex(
                x => x.Name,
                new BsonRegularExpression("^readme\\.md$", "i")
            )
        );

        var readmes = await repository.SearchFor(filter).ToListAsync(cancellationToken);
        return readmes.ToDictionary(
            x => BuildPathKey(x.ParentPathSegments),
            x => x.FileContent,
            StringComparer.OrdinalIgnoreCase
        );
    }

    private async Task<string?> ResolveReadmeContentAsync(
        List<string> parentPathSegments,
        CancellationToken cancellationToken
    )
    {
        var filter = Builders<CodeFileSystemEntry>.Filter.And(
            Builders<CodeFileSystemEntry>.Filter.Eq(x => x.ParentPathSegments, parentPathSegments),
            Builders<CodeFileSystemEntry>.Filter.Regex(
                x => x.Name,
                new BsonRegularExpression("^readme\\.md$", "i")
            )
        );

        var readme = await repository.SearchFor(filter).FirstOrDefaultAsync(cancellationToken);
        return readme?.FileContent;
    }

    private static string BuildPathKey(IEnumerable<string> segments)
    {
        return string.Join("/", segments);
    }

    /// <summary>
    /// 获取相对路径
    /// </summary>
    /// <param name="root"></param>
    /// <param name="fullPath"></param>
    /// <returns></returns>
    private static List<string> GetRelativeSegments(string root, string fullPath)
    {
        var relativePath = Path.GetRelativePath(root, fullPath);
        if (relativePath == ".")
        {
            return [];
        }

        return relativePath
            .Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
            .Where(x => !string.IsNullOrWhiteSpace(x))
            .ToList();
    }

    /// <summary>
    /// 文件/目录是否过滤
    /// </summary>
    /// <param name="codeView"></param>
    /// <param name="name"></param>
    /// <returns></returns>
    private static bool IsFiltered(CodeViewOption codeView, string name)
    {
        return codeView.Filters?.Any(x => x.WildCardMatch(name)) == true;
    }

    /// <summary>
    /// 获取文件扩展名
    /// </summary>
    /// <param name="fileInfo"></param>
    /// <returns></returns>
    private static string GetFileExtension(FileInfo fileInfo)
    {
        var extension = fileInfo.Extension;
        return (string.IsNullOrEmpty(extension) ? fileInfo.Name : extension).ToLower();
    }

    /// <summary>
    /// 计算文件哈希值
    /// </summary>
    private async Task<string> ComputeHashAsync(
        FileInfo fileInfo,
        CancellationToken cancellationToken
    )
    {
        try
        {
            await using var stream = fileInfo.OpenRead();
            var bytes = await MD5.HashDataAsync(stream, cancellationToken);
            var builder = new StringBuilder(bytes.Length * 2);
            foreach (var item in bytes)
            {
                builder.Append(item.ToString("x2"));
            }

            return builder.ToString();
        }
        catch (Exception ex)
        {
            logger.LogWarning(ex, "读取文件哈希失败: {FilePath}", fileInfo.FullName);
            return string.Empty;
        }
    }

    /// <summary>
    /// 读取文本文件内容
    /// </summary>
    private async Task<string?> ReadTextContentAsync(
        FileInfo fileInfo,
        CancellationToken cancellationToken
    )
    {
        try
        {
            using var reader = fileInfo.OpenText();
            return await reader.ReadToEndAsync(cancellationToken);
        }
        catch (Exception ex)
        {
            logger.LogWarning(ex, "读取文件内容失败: {FilePath}", fileInfo.FullName);
            return null;
        }
    }

    /// <summary>
    /// 生成文件系统项
    /// </summary>
    /// <param name="codeView"></param>
    /// <param name="info"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    private async Task<CodeFileSystemEntry?> BuildEntryAsync(
        CodeViewOption codeView,
        FileSystemInfo info,
        CancellationToken cancellationToken
    )
    {
        if (IsFiltered(codeView, info.Name))
        {
            return null;
        }

        var relativeSegments = GetRelativeSegments(codeView.SourceCodeRoot, info.FullName);
        if (relativeSegments.Count == 0)
        {
            return null;
        }

        var parentSegments = relativeSegments.Take(relativeSegments.Count - 1).ToList();

        if (info is DirectoryInfo directoryInfo)
        {
            return new CodeFileSystemEntry
            {
                PathSegments = relativeSegments,
                ParentPathSegments = parentSegments,
                Name = directoryInfo.Name,
                IsDirectory = true,
                Extension = null,
                Size = null,
                Hash = null,
                CodeFileContentType = CodeFileContentType.Unknown,
                FileContent = null,
                CreatedTime = directoryInfo.CreationTime,
                LastWriteTime = directoryInfo.LastWriteTime,
                LastUpdateTime = DateTime.Now,
            };
        }

        if (info is FileInfo fileInfo)
        {
            var extension = GetFileExtension(fileInfo);
            var hash = await ComputeHashAsync(fileInfo, cancellationToken);
            var shouldPreview = codeView.ExtensionToLanguage?.ContainsKey(extension) == true;
            var content = shouldPreview
                ? await ReadTextContentAsync(fileInfo, cancellationToken)
                : null;

            return new CodeFileSystemEntry
            {
                PathSegments = relativeSegments,
                ParentPathSegments = parentSegments,
                Name = fileInfo.Name,
                IsDirectory = false,
                Extension = extension,
                Size = fileInfo.Length,
                Hash = hash,
                CodeFileContentType = shouldPreview
                    ? CodeFileContentType.Text
                    : CodeFileContentType.Unknown,
                FileContent = content,
                CreatedTime = fileInfo.CreationTime,
                LastWriteTime = fileInfo.LastWriteTime,
                LastUpdateTime = DateTime.Now,
            };
        }

        return null;
    }

    /// <summary>
    /// 生成需要更新的文件系统项
    /// </summary>
    /// <param name="codeView"></param>
    /// <param name="exist"></param>
    /// <param name="info"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    private async Task<CodeFileSystemEntry?> BuildUpdatedEntryAsync(
        CodeViewOption codeView,
        CodeFileSystemEntry exist,
        FileSystemInfo info,
        CancellationToken cancellationToken
    )
    {
        if (IsFiltered(codeView, info.Name))
        {
            return null;
        }

        if (info is DirectoryInfo directoryInfo)
        {
            if (
                exist.IsDirectory
                && DateTimeAreEqual(exist.LastWriteTime, directoryInfo.LastWriteTime)
                && exist.Name == directoryInfo.Name
            )
            {
                return null;
            }

            exist.IsDirectory = true;
            exist.Name = directoryInfo.Name;
            exist.Extension = null;
            exist.Size = null;
            exist.Hash = null;
            exist.CodeFileContentType = CodeFileContentType.Unknown;
            exist.FileContent = null;
            exist.LastWriteTime = directoryInfo.LastWriteTime;
            exist.LastUpdateTime = DateTime.Now;
            return exist;
        }

        if (info is FileInfo fileInfo)
        {
            var extension = GetFileExtension(fileInfo);
            var hash = await ComputeHashAsync(fileInfo, cancellationToken);
            var shouldPreview = codeView.ExtensionToLanguage?.ContainsKey(extension) == true;

            var needUpdate =
                exist.IsDirectory
                || !string.Equals(exist.Hash, hash, StringComparison.OrdinalIgnoreCase)
                || exist.Size != fileInfo.Length
                || !DateTimeAreEqual(exist.LastWriteTime, fileInfo.LastWriteTime)
                || !string.Equals(exist.Extension, extension, StringComparison.OrdinalIgnoreCase);

            if (!needUpdate)
            {
                return null;
            }

            exist.IsDirectory = false;
            exist.Extension = extension;
            exist.Size = fileInfo.Length;
            exist.Hash = hash;
            exist.LastWriteTime = fileInfo.LastWriteTime;
            exist.LastUpdateTime = DateTime.Now;
            exist.CodeFileContentType = shouldPreview
                ? CodeFileContentType.Text
                : CodeFileContentType.Unknown;
            exist.FileContent = shouldPreview
                ? await ReadTextContentAsync(fileInfo, cancellationToken)
                : null;

            // 如果文件已有AI分析结果,但修改后行数少于50行,清空分析结果
            if (
                !string.IsNullOrWhiteSpace(exist.AiAnalyzeResult)
                && !string.IsNullOrWhiteSpace(exist.FileContent)
            )
            {
                var lineCount = exist
                    .FileContent.Split(["\r\n", "\r", "\n"], StringSplitOptions.None)
                    .Length;
                if (lineCount < 50)
                {
                    exist.AiAnalyzeResult = null;
                    exist.AiAnalyzeHash = null;
                    exist.AiAnalyzeTime = null;
                }
            }

            return exist;
        }

        return null;
    }

    private static bool DateTimeAreEqual(DateTime dt1, DateTime dt2)
    {
        // 秒级精度,忽略小数部分
        var diffSeconds = (int)(dt1 - dt2).TotalSeconds;
        return diffSeconds == 0;
    }

    /// <summary>
    /// 递归获取目录下所有文件和子目录(自动过滤)
    /// </summary>
    private List<FileSystemInfo> GetAllFileSystemInfos(string path, CodeViewOption codeView)
    {
        if (string.IsNullOrEmpty(path))
        {
            return [];
        }

        var dir = new DirectoryInfo(path);
        if (!dir.Exists)
        {
            return [];
        }

        var allFiles = new ConcurrentBag<FileSystemInfo>();

        try
        {
            var fileSystemInfos = dir.GetFileSystemInfos();
            var subDirectories = new List<DirectoryInfo>();
            var currentFiles = new List<FileSystemInfo>();

            // 先分类:子目录 vs 文件
            foreach (var item in fileSystemInfos)
            {
                if (IsFiltered(codeView, item.Name))
                {
                    continue;
                }

                if (item is DirectoryInfo subDir)
                {
                    currentFiles.Add(subDir);
                    subDirectories.Add(subDir);
                }
                else
                {
                    currentFiles.Add(item);
                }
            }

            // 当前层级的文件/目录加入结果
            foreach (var file in currentFiles)
            {
                allFiles.Add(file);
            }

            // 并行扫描子目录(控制并行度避免磁盘抖动)
            if (subDirectories.Count > 0)
            {
                Parallel.ForEach(
                    subDirectories,
                    new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount / 2 },
                    subDir =>
                    {
                        var subFiles = GetAllFileSystemInfos(subDir.FullName, codeView);
                        foreach (var subFile in subFiles)
                        {
                            allFiles.Add(subFile);
                        }
                    }
                );
            }
        }
        catch (UnauthorizedAccessException ex)
        {
            logger.LogWarning(ex, "无权限访问目录: {Directory}", dir.FullName);
        }

        return allFiles.ToList();
    }
}
⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

这是一个用于管理代码文件系统条目的服务类,主要功能是同步、查询和管理代码仓库中的文件和目录信息。以下是详细解释:

类的基本结构

public class CodeFileSystemEntryService : AbstractCacheService, ICodeFileSystemEntryService

这个服务类继承自抽象缓存服务,实现了代码文件系统条目服务接口,使用依赖注入获取以下服务:

  • IRepository<CodeFileSystemEntry> - 数据仓储
  • IConfiguration - 配置服务
  • ILogger - 日志服务
  • IMediator - 中介者模式
  • IFusionCache - 缓存服务

主要功能模块

1. 配置管理

private readonly Lazy<CodeViewOption> _codeViewCfg

延迟加载代码查看配置,包括源代码根目录验证。

2. 查询功能

  • FindByPathAsync - 根据路径查找文件/目录(带缓存)
  • FindByPathWithoutCacheAsync - 无缓存查找
  • GetChildrenAsync - 获取子项列表
  • SearchAsync - 关键词搜索
  • GetPagedListAsync - 分页查询,支持多字段排序
  • GetAllDirectoriesAsync - 获取所有目录

3. 数据维护功能

  • SaveDescriptionAsync - 保存描述信息
  • SaveAiAnalyzeResultAsync - 保存AI分析结果
  • ShouldAnalyzeAsync - 判断是否需要AI分析

4. 同步功能(核心)

SyncAsync 方法是最重要的功能,执行以下操作:

  1. 文件系统扫描:递归扫描源代码目录,构建文件系统映射表
  2. 数据库对比:获取数据库中现有记录,构建映射表
  3. 差异分析:并行比较文件系统与数据库,找出需要:
    • 新增的项目
    • 更新的项目
    • 删除的项目
  4. 批量操作:执行批量插入、更新、删除操作
  5. 缓存清理:清理相关缓存
  6. 事件通知:发送代码兼容性检查请求

核心算法特点

1. 并行处理

await Parallel.ForEachAsync(fileSystemMap, new ParallelOptions
{
    MaxDegreeOfParallelism = Math.Max(1, Environment.ProcessorCount / 2),
    CancellationToken = cancellationToken,
}, async (pair, ct) => { ... });

使用并行处理提高大量文件的处理效率。

2. 智能缓存

  • 使用FusionCache进行多级缓存
  • 支持缓存失效策略
  • 缓存时间为1天

3. 文件过滤

根据配置自动过滤不需要的文件和目录。

4. 文件内容处理

  • 自动识别文本文件并读取内容
  • 计算文件MD5哈希值用于变更检测
  • 支持多种编程语言识别

5. AI分析集成

  • 判断文件是否需要AI分析(支持C#、JavaScript、TypeScript)
  • 过滤小文件(少于50行)和压缩文件
  • 基于文件哈希值判断是否需要重新分析

性能优化

  1. 并行扫描:文件系统扫描和数据库比较都使用并行处理
  2. 批量操作:数据库操作采用批量模式
  3. 缓存策略:查询结果缓存,减少重复计算
  4. 内存优化:使用ConcurrentBag避免锁竞争

错误处理

  • 文件访问权限异常处理
  • 文件读取失败的降级处理
  • 配置验证和错误提示

这个服务是一个完整的代码仓库管理系统的核心组件,提供了从文件系统同步到智能分析的完整功能链路。

评论加载中...