using System.Collections.Immutable;
using System.IO.Abstractions;
using System.Text;
using System.Text.RegularExpressions;
using ErsatzTV.Core;
using ErsatzTV.Core.Interfaces.Metadata;
using ErsatzTV.Core.Iptv;
using ErsatzTV.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.IO;

namespace ErsatzTV.Application.Channels;

public partial class GetChannelGuideHandler(
    IDbContextFactory<TvContext> dbContextFactory,
    RecyclableMemoryStreamManager recyclableMemoryStreamManager,
    IFileSystem fileSystem,
    ILocalFileSystem localFileSystem)
    : IRequestHandler<GetChannelGuide, Either<BaseError, ChannelGuide>>
{
    public async Task<Either<BaseError, ChannelGuide>> Handle(
        GetChannelGuide request,
        CancellationToken cancellationToken)
    {
        await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
        var hiddenChannelNumbers = dbContext.Channels
            .Where(c => c.ShowInEpg == false)
            .Select(c => c.Number)
            .AsEnumerable()
            .Select(n => $"{n}.xml")
            .ToImmutableHashSet();

        string channelsFile = fileSystem.Path.Combine(FileSystemLayout.ChannelGuideCacheFolder, "channels.xml");
        if (!fileSystem.File.Exists(channelsFile))
        {
            return BaseError.New($"Required file {channelsFile} is missing");
        }

        long mtime = fileSystem.File.GetLastWriteTime(channelsFile).Ticks;

        var accessTokenUri = $"?v={mtime}";
        if (!string.IsNullOrWhiteSpace(request.AccessToken))
        {
            accessTokenUri += $"&amp;access_token={request.AccessToken}";
        }

        string channelsFragment = await ReadAllTextShared(channelsFile, cancellationToken);

        // TODO: is regex faster?
        channelsFragment = channelsFragment
            .Replace("{RequestBase}", $"{request.Scheme}://{request.Host}{request.BaseUrl}")
            .Replace("{AccessTokenUri}", accessTokenUri);

        var channelDataFragments = new Dictionary<string, string>();

        foreach (string fileName in localFileSystem.ListFiles(FileSystemLayout.ChannelGuideCacheFolder))
        {
            if (fileName.Contains("channels"))
            {
                continue;
            }

            if (hiddenChannelNumbers.Contains(fileSystem.Path.GetFileName(fileName)))
            {
                continue;
            }

            try
            {
                string channelDataFragment = await ReadAllTextShared(fileName, cancellationToken);

                channelDataFragment = channelDataFragment
                    .Replace("{RequestBase}", $"{request.Scheme}://{request.Host}{request.BaseUrl}")
                    .Replace("{AccessTokenUri}", accessTokenUri);

                channelDataFragment = EtvTagRegex().Replace(channelDataFragment, string.Empty);

                channelDataFragments.Add(fileSystem.Path.GetFileNameWithoutExtension(fileName), channelDataFragment);
            }
            catch (FileNotFoundException)
            {
                // ignore this channel fragment
            }
            catch (IOException)
            {
                // ignore this channel fragment
            }
        }

        return new ChannelGuide(recyclableMemoryStreamManager, channelsFragment, channelDataFragments);
    }

    private async Task<string> ReadAllTextShared(string fileName, CancellationToken cancellationToken)
    {
        await using var stream = fileSystem.FileStream.New(
            fileName,
            FileMode.Open,
            FileAccess.Read,
            FileShare.ReadWrite);
        using var reader = new StreamReader(stream, Encoding.UTF8, leaveOpen: true);
        return await reader.ReadToEndAsync(cancellationToken);
    }

    [GeneratedRegex(@"<etv:[^>]+?>.*?<\/etv:[^>]+?>|<etv:[^>]+?\/>", RegexOptions.Singleline)]
    private static partial Regex EtvTagRegex();
}
