using System.Text.Json;
using System.Text.Json.Serialization;
using LiteCqrs;
using Microsoft.EntityFrameworkCore;
using TeleWave.Application.Broadcast;
using TeleWave.Application.Common.Interfaces;
using TeleWave.Application.Common.Models;
using TeleWave.Application.Programming.Templates;
using TeleWave.Domain.Programming;
using TeleWave.Domain.Programming.Planning;

namespace TeleWave.Application.Programming.Planning.Trace;

public sealed class GetEntryTraceQueryHandler(IAppDbContext dbContext)
    : IQueryHandler<GetEntryTraceQuery, Result<EntryTraceDto>>
{
    /// <summary>Те же настройки, что при записи трейса генератором.</summary>
    private static readonly JsonSerializerOptions TraceJsonOptions = new()
    {
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        Converters = { new JsonStringEnumConverter() },
    };

    public async Task<Result<EntryTraceDto>> Handle(
        GetEntryTraceQuery query,
        CancellationToken cancellationToken
    )
    {
        var entry = await dbContext
            .ScheduleEntries.AsNoTracking()
            .FirstOrDefaultAsync(e => e.Id == query.EntryId, cancellationToken);
        if (entry is null)
            return Result.Failure<EntryTraceDto>(ChannelErrors.NotFound);

        var showName = entry.ShowId is { } showId
            ? await dbContext
                .Shows.AsNoTracking()
                .Where(s => s.Id == showId)
                .Select(s => s.Name)
                .FirstOrDefaultAsync(cancellationToken)
            : null;

        var collectionName = entry.CollectionId is { } collectionId
            ? await dbContext
                .Collections.AsNoTracking()
                .Where(c => c.Id == collectionId)
                .Select(c => c.Name)
                .FirstOrDefaultAsync(cancellationToken)
            : null;

        var trace = Parse(entry.TraceJson);
        if (trace is null)
            return Result.Success(
                new EntryTraceDto(
                    entry.Id,
                    entry.StartsAtUtc,
                    entry.EndsAtUtc,
                    showName,
                    entry.EpisodeIndex,
                    null,
                    null,
                    null,
                    null,
                    null,
                    null,
                    null,
                    null,
                    null,
                    collectionName,
                    null,
                    null,
                    null,
                    0,
                    false,
                    null
                )
            );

        // Слот мог быть удалён или изменён после генерации — трейс от этого не портится, просто
        // часть подписей окажется пустой.
        var slot = trace.SlotId is { } slotId
            ? await dbContext
                .Slots.AsNoTracking()
                .FirstOrDefaultAsync(s => s.Id == slotId, cancellationToken)
            : null;
        var layer = slot is null
            ? null
            : await dbContext
                .GridLayers.AsNoTracking()
                .FirstOrDefaultAsync(l => l.Id == slot.LayerId, cancellationToken);

        var group = slot?.GroupId is { } groupId
            ? await dbContext
                .Groups.AsNoTracking()
                .Where(g => g.Id == groupId)
                .Select(g => new { g.Name, g.ItemCount })
                .FirstOrDefaultAsync(cancellationToken)
            : null;

        var strategy = SlotStrategy.FromJson(slot?.StrategyJson);

        var junctionId = slot?.JunctionAfterId ?? slot?.JunctionBetweenId;
        var junctionName = junctionId is { } id
            ? await dbContext
                .JunctionTemplates.AsNoTracking()
                .Where(j => j.Id == id)
                .Select(j => j.Name)
                .FirstOrDefaultAsync(cancellationToken)
            : null;

        return Result.Success(
            new EntryTraceDto(
                entry.Id,
                entry.StartsAtUtc,
                entry.EndsAtUtc,
                showName,
                entry.EpisodeIndex,
                layer?.Name,
                layer?.Priority,
                slot?.Title,
                trace.SlotKind,
                slot?.Weekday,
                slot?.TargetStart,
                slot?.TargetDurationMinutes,
                group?.Name,
                group?.ItemCount,
                collectionName,
                trace.Strategy,
                strategy?.CooldownDays,
                trace.CandidatesAfterCooldown,
                trace.DriftMinutes,
                trace.Snapped,
                junctionName
            )
        );
    }

    private static PlanTrace? Parse(string? json)
    {
        if (string.IsNullOrWhiteSpace(json))
            return null;

        try
        {
            return JsonSerializer.Deserialize<PlanTrace>(json, TraceJsonOptions);
        }
        catch (JsonException)
        {
            return null;
        }
    }
}
