using System.Text;
using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using Tiku.Application.Catalog;
using Tiku.Application.Content;
using Tiku.Domain.Content;
using Tiku.Infrastructure.Security;

namespace Tiku.Infrastructure.Content;

public sealed partial class ContentManagementService
{
    public async Task<CatalogList<PracticeBlueprintManagementItem>> GetPracticeBlueprintsAsync(
        ContentManagementActor actor,
        ContentManagementFilter filter,
        CancellationToken cancellationToken = default)
    {
        var scope = await RequireDataScopeAsync(actor, cancellationToken);
        var regionIds = scope.RegionIds.ToArray();
        var query = dbContext.PracticeBlueprints
            .AsNoTracking()
            .Where(blueprint => blueprint.TenantId == actor.TenantId)
            .ApplyDataScope(
                scope,
                blueprint => blueprint.CreatedBy == actor.UserId,
                blueprint => blueprint.RegionId.HasValue && regionIds.Contains(blueprint.RegionId.Value));

        if (!filter.IncludeInactive) query = query.Where(blueprint => blueprint.Status == ContentStatus.Active);

        if (filter.RegionId.HasValue) query = query.Where(blueprint => blueprint.RegionId == filter.RegionId.Value);

        if (filter.EntryId.HasValue) query = query.Where(blueprint => blueprint.EntryId == filter.EntryId.Value);

        if (filter.NodeId.HasValue) query = query.Where(blueprint => blueprint.NodeId == filter.NodeId.Value);

        if (filter.CollectionId.HasValue)
            query = query.Where(blueprint => blueprint.CollectionId == filter.CollectionId.Value);

        if (TryParse(filter.Mode, out PracticeMode mode)) query = query.Where(blueprint => blueprint.Mode == mode);

        if (!string.IsNullOrWhiteSpace(filter.Keyword))
        {
            var keyword = filter.Keyword.Trim();
            query = query.Where(blueprint => blueprint.Name.Contains(keyword));
        }

        var items = await query
            .OrderBy(blueprint => blueprint.SortOrder)
            .ThenBy(blueprint => blueprint.CreatedAt)
            .Take(ResolveLimit(filter.Limit))
            .Select(blueprint => ToBlueprintItem(blueprint))
            .ToArrayAsync(cancellationToken);

        return new CatalogList<PracticeBlueprintManagementItem>(items);
    }

    public async Task<ContentManagementResult<PracticeBlueprintManagementItem>> UpsertPracticeBlueprintAsync(
        ContentManagementActor actor,
        UpsertPracticeBlueprintCommand command,
        CancellationToken cancellationToken = default)
    {
        var scope = await RequireDataScopeAsync(actor, cancellationToken);
        ArgumentException.ThrowIfNullOrWhiteSpace(command.Name);
        await AssertRegionAsync(actor.TenantId, command.RegionId, cancellationToken);
        await AssertEntryAsync(actor, scope, command.EntryId, cancellationToken);
        await AssertNodeAsync(actor, scope, command.NodeId, cancellationToken);
        await AssertReferenceAsync<QuestionCollection>(actor.TenantId, command.CollectionId, "collection_not_found",
            cancellationToken);

        var blueprint = await ResolveEntityByIdOrLegacyAsync(
            dbContext.PracticeBlueprints,
            actor.TenantId,
            command.Id,
            command.LegacyId,
            cancellationToken);

        var isNew = blueprint is null;
        if (command.Id.HasValue && (blueprint is null || blueprint.Id != command.Id.Value))
            throw new ContentManagementException("Practice blueprint was not found.", "practice_blueprint_not_found");

        if (blueprint is not null && !scope.AllowsResource(actor.UserId, blueprint.CreatedBy, blueprint.RegionId))
            throw new ContentManagementException("Practice blueprint was not found.", "practice_blueprint_not_found");

        if (blueprint is null && !scope.AllowsResource(actor.UserId, actor.UserId, command.RegionId))
            throw new ContentManagementException("Practice blueprint was not found.", "practice_blueprint_not_found");

        blueprint ??= new PracticeBlueprint
        {
            Id = command.Id ?? Guid.NewGuid(),
            TenantId = actor.TenantId,
            CreatedBy = actor.UserId
        };

        blueprint.RegionId = command.RegionId;
        blueprint.EntryId = command.EntryId;
        blueprint.NodeId = command.NodeId;
        blueprint.CollectionId = command.CollectionId;
        blueprint.LegacyId = Normalize(command.LegacyId);
        blueprint.Name = command.Name.Trim();
        blueprint.Mode = Parse(command.Mode, PracticeMode.Sequential, "practice_mode_invalid");
        blueprint.AssemblyType = Parse(command.AssemblyType, PracticeAssemblyType.Collection,
            "practice_assembly_type_invalid");
        blueprint.QuestionLimit = command.QuestionLimit;
        blueprint.DurationMinutes = command.DurationMinutes;
        blueprint.TotalScore = command.TotalScore;
        blueprint.PassScore = command.PassScore;
        blueprint.Sections = JsonArrayOrDefault(command.Sections);
        blueprint.Rules = JsonObjectOrDefault(command.Rules);
        blueprint.AccessRules = JsonObjectOrDefault(command.AccessRules);
        blueprint.Status = Parse(command.Status, ContentStatus.Active, "content_status_invalid");
        blueprint.SortOrder = command.Order ?? 0;
        blueprint.UpdatedBy = actor.UserId;

        if (isNew) dbContext.PracticeBlueprints.Add(blueprint);

        await dbContext.SaveChangesAsync(cancellationToken);
        return new ContentManagementResult<PracticeBlueprintManagementItem>(ToBlueprintItem(blueprint));
    }

    public ImportFieldMappingItem GetImportFieldMapping(string importType)
    {
        var spec = ResolveImportSpec(importType);
        return new ImportFieldMappingItem(
            spec.ImportType,
            spec.Title,
            spec.Description,
            spec.Fields,
            spec.Fields.Where(field => field.Required).Select(field => field.Field).ToArray());
    }

    public ImportTemplateItem GetImportTemplate(string importType, string? format)
    {
        var spec = ResolveImportSpec(importType);
        var normalizedFormat = string.IsNullOrWhiteSpace(format) ? "json" : format.Trim().ToLowerInvariant();
        var content = normalizedFormat switch
        {
            "json" => JsonSerializer.Serialize(spec.JsonExample, new JsonSerializerOptions { WriteIndented = true }),
            "csv" => string.Join(
                "\n",
                spec.CsvRows.Select(row => string.Join(",", row.Select(EscapeCsv)))),
            _ => throw new ContentManagementException("Import template format is not supported.",
                "import_template_format_invalid")
        };

        return new ImportTemplateItem(
            spec.ImportType,
            normalizedFormat,
            $"{spec.ImportType}-import-template.{normalizedFormat}",
            normalizedFormat == "csv" ? "text/csv" : "application/json",
            Convert.ToBase64String(Encoding.UTF8.GetBytes(content)),
            content,
            spec.Fields);
    }
}