using Microsoft.EntityFrameworkCore;
using Tiku.Application.Catalog;
using Tiku.Application.StudyContent;
using Tiku.Infrastructure.Persistence;

namespace Tiku.Infrastructure.StudyContent;

public sealed class StudyContentQueryService(TikuDbContext dbContext) : IStudyContentQueryService
{
    private const int DefaultLimit = 500;
    private const int MaxLimit = 2000;

    public async Task<CatalogList<VocabularyUnitCatalogItem>> GetVocabularyUnitsAsync(
        StudyContentFilter filter,
        CancellationToken cancellationToken = default)
    {
        var query = dbContext.VocabularyUnits
            .AsNoTracking()
            .Where(unit =>
                unit.TenantId == filter.TenantId &&
                unit.IsActive);

        if (filter.RegionId.HasValue)
        {
            query = query.Where(unit => unit.RegionId == filter.RegionId.Value || unit.RegionId == null);
        }

        if (filter.EntryId.HasValue)
        {
            query = query.Where(unit => unit.EntryId == filter.EntryId.Value);
        }

        if (filter.ContentNodeId.HasValue)
        {
            query = query.Where(unit => unit.ContentNodeId == filter.ContentNodeId.Value);
        }

        query = ApplyNameKeyword(query, filter.Keyword);

        var items = await query
            .OrderBy(unit => unit.SortOrder)
            .ThenBy(unit => unit.CreatedAt)
            .Take(ResolveLimit(filter.Limit))
            .Select(unit => new VocabularyUnitCatalogItem(
                unit.Id,
                unit.LegacyId,
                unit.RegionId,
                unit.EntryId,
                unit.ContentNodeId,
                unit.Name,
                unit.Description,
                unit.WordCount,
                unit.SortOrder,
                unit.Metadata))
            .ToArrayAsync(cancellationToken);

        return new CatalogList<VocabularyUnitCatalogItem>(items);
    }

    public async Task<CatalogList<VocabularyWordCatalogItem>> GetVocabularyWordsAsync(
        StudyContentFilter filter,
        CancellationToken cancellationToken = default)
    {
        var query = dbContext.VocabularyWords
            .AsNoTracking()
            .Where(word =>
                word.TenantId == filter.TenantId &&
                word.IsActive);

        if (filter.UnitId.HasValue)
        {
            query = query.Where(word => word.UnitId == filter.UnitId.Value);
        }

        if (filter.EntryId.HasValue)
        {
            query = query.Where(word => word.EntryId == filter.EntryId.Value);
        }

        if (filter.ContentNodeId.HasValue)
        {
            query = query.Where(word => word.ContentNodeId == filter.ContentNodeId.Value);
        }

        if (!string.IsNullOrWhiteSpace(filter.Keyword))
        {
            var keyword = filter.Keyword.Trim();
            query = query.Where(word =>
                word.Word.Contains(keyword) ||
                (word.Meaning != null && word.Meaning.Contains(keyword)));
        }

        var items = await query
            .OrderBy(word => word.SortOrder)
            .ThenBy(word => word.Word)
            .Take(ResolveLimit(filter.Limit))
            .Select(word => new VocabularyWordCatalogItem(
                word.Id,
                word.LegacyId,
                word.UnitId,
                word.EntryId,
                word.ContentNodeId,
                word.Word,
                word.Phonetic,
                word.Meaning,
                word.Example,
                word.ExampleTranslation,
                word.Difficulty,
                word.Tags,
                word.SortOrder,
                word.Metadata))
            .ToArrayAsync(cancellationToken);

        return new CatalogList<VocabularyWordCatalogItem>(items);
    }

    public async Task<CatalogList<HandbookSubjectCatalogItem>> GetHandbookSubjectsAsync(
        StudyContentFilter filter,
        CancellationToken cancellationToken = default)
    {
        var query = dbContext.HandbookSubjects
            .AsNoTracking()
            .Where(subject =>
                subject.TenantId == filter.TenantId &&
                subject.IsActive);

        if (filter.RegionId.HasValue)
        {
            query = query.Where(subject => subject.RegionId == filter.RegionId.Value || subject.RegionId == null);
        }

        if (filter.EntryId.HasValue)
        {
            query = query.Where(subject => subject.EntryId == filter.EntryId.Value);
        }

        if (filter.ContentNodeId.HasValue)
        {
            query = query.Where(subject => subject.ContentNodeId == filter.ContentNodeId.Value);
        }

        query = ApplyNameKeyword(query, filter.Keyword);

        var items = await query
            .OrderBy(subject => subject.SortOrder)
            .ThenBy(subject => subject.CreatedAt)
            .Take(ResolveLimit(filter.Limit))
            .Select(subject => new HandbookSubjectCatalogItem(
                subject.Id,
                subject.LegacyId,
                subject.RegionId,
                subject.SchoolId,
                subject.MajorId,
                subject.EntryId,
                subject.ContentNodeId,
                subject.Name,
                subject.Type,
                subject.Icon,
                subject.Color,
                subject.Description,
                subject.SortOrder,
                subject.Metadata))
            .ToArrayAsync(cancellationToken);

        return new CatalogList<HandbookSubjectCatalogItem>(items);
    }

    public async Task<CatalogList<HandbookChapterCatalogItem>> GetHandbookChaptersAsync(
        StudyContentFilter filter,
        CancellationToken cancellationToken = default)
    {
        var query = dbContext.HandbookChapters
            .AsNoTracking()
            .Where(chapter =>
                chapter.TenantId == filter.TenantId &&
                chapter.IsActive);

        if (filter.SubjectId.HasValue)
        {
            query = query.Where(chapter => chapter.SubjectId == filter.SubjectId.Value);
        }

        if (filter.EntryId.HasValue)
        {
            query = query.Where(chapter => chapter.EntryId == filter.EntryId.Value);
        }

        if (filter.ContentNodeId.HasValue)
        {
            query = query.Where(chapter => chapter.ContentNodeId == filter.ContentNodeId.Value);
        }

        query = ApplyNameKeyword(query, filter.Keyword);

        var items = await query
            .OrderBy(chapter => chapter.SortOrder)
            .ThenBy(chapter => chapter.CreatedAt)
            .Take(ResolveLimit(filter.Limit))
            .Select(chapter => new HandbookChapterCatalogItem(
                chapter.Id,
                chapter.LegacyId,
                chapter.SubjectId,
                chapter.EntryId,
                chapter.ContentNodeId,
                chapter.Name,
                chapter.Description,
                chapter.SortOrder,
                chapter.Metadata))
            .ToArrayAsync(cancellationToken);

        return new CatalogList<HandbookChapterCatalogItem>(items);
    }

    public async Task<CatalogList<HandbookEntryCatalogItem>> GetHandbookEntriesAsync(
        StudyContentFilter filter,
        CancellationToken cancellationToken = default)
    {
        var query = dbContext.HandbookEntries
            .AsNoTracking()
            .Where(entry =>
                entry.TenantId == filter.TenantId &&
                entry.IsActive);

        if (filter.ChapterId.HasValue)
        {
            query = query.Where(entry => entry.ChapterId == filter.ChapterId.Value);
        }

        if (filter.EntryId.HasValue)
        {
            query = query.Where(entry => entry.EntryId == filter.EntryId.Value);
        }

        if (filter.ContentNodeId.HasValue)
        {
            query = query.Where(entry => entry.ContentNodeId == filter.ContentNodeId.Value);
        }

        if (!string.IsNullOrWhiteSpace(filter.Keyword))
        {
            var keyword = filter.Keyword.Trim();
            query = query.Where(entry =>
                entry.Title.Contains(keyword) ||
                (entry.Summary != null && entry.Summary.Contains(keyword)));
        }

        var items = await query
            .OrderBy(entry => entry.SortOrder)
            .ThenBy(entry => entry.CreatedAt)
            .Take(ResolveLimit(filter.Limit))
            .Select(entry => new HandbookEntryCatalogItem(
                entry.Id,
                entry.LegacyId,
                entry.ChapterId,
                entry.EntryId,
                entry.ContentNodeId,
                entry.Title,
                entry.Summary,
                filter.IncludeContent ? entry.Content : null,
                entry.Tags,
                entry.SortOrder,
                entry.Metadata))
            .ToArrayAsync(cancellationToken);

        return new CatalogList<HandbookEntryCatalogItem>(items);
    }

    private static IQueryable<T> ApplyNameKeyword<T>(IQueryable<T> query, string? keyword)
        where T : class
    {
        if (string.IsNullOrWhiteSpace(keyword))
        {
            return query;
        }

        var trimmed = keyword.Trim();
        return query.Where(entity => EF.Property<string>(entity, "Name").Contains(trimmed));
    }

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