using FastEndpoints;

namespace TaxBaik.Web.Endpoints.ClientLogs;

public sealed class ClientLogGroupItem
{
    public string Key { get; set; } = "";
    public string? Level { get; set; }
    public string? Source { get; set; }
    public string? Route { get; set; }
    public string? Message { get; set; }
    public int Count { get; set; }
    public string? LastSeen { get; set; }
}

public sealed class GetGroupedLogsEndpoint : EndpointWithoutRequest<IReadOnlyList<ClientLogGroupItem>>
{
    private readonly IWebHostEnvironment _environment;

    public GetGroupedLogsEndpoint(IWebHostEnvironment environment)
    {
        _environment = environment;
    }

    public override void Configure()
    {
        Get("/api/client-logs/groups");
        Policies("AdminOnly");
    }

    public override async Task HandleAsync(CancellationToken ct)
    {
        var logDir = Path.Combine(_environment.ContentRootPath, "logs");
        if (!Directory.Exists(logDir))
        {
            await SendAsync(Array.Empty<ClientLogGroupItem>(), cancellation: ct);
            return;
        }

        var files = Directory.GetFiles(logDir, "taxbaik-*.log")
            .OrderByDescending(File.GetLastWriteTimeUtc)
            .Take(3)
            .ToArray();

        var groups = new Dictionary<string, ClientLogGroupItem>(StringComparer.OrdinalIgnoreCase);

        foreach (var file in files)
        {
            foreach (var line in await File.ReadAllLinesAsync(file, ct))
            {
                if (!line.Contains("ClientLog ", StringComparison.OrdinalIgnoreCase))
                    continue;

                var entry = Parse(line);
                var key = $"{entry.Level}|{entry.Source}|{entry.Route}|{Normalize(entry.Message)}";
                if (!groups.TryGetValue(key, out var group))
                {
                    group = new ClientLogGroupItem
                    {
                        Key = key,
                        Level = entry.Level,
                        Source = entry.Source,
                        Route = entry.Route,
                        Message = entry.Message,
                        Count = 0,
                        LastSeen = entry.Timestamp
                    };
                    groups[key] = group;
                }

                group.Count++;
                group.LastSeen = entry.Timestamp ?? group.LastSeen;
            }
        }

        await SendAsync(groups.Values
            .OrderByDescending(item => item.Count)
            .ThenByDescending(item => item.LastSeen)
            .Take(10)
            .ToList(), cancellation: ct);
    }

    private static RecentClientLogEntry Parse(string line)
    {
        return new RecentClientLogEntry
        {
            Timestamp = ExtractTimestamp(line),
            Level = ExtractBetween(line, "] [", "]"),
            Source = ExtractTokenAfter(line, "ClientLog ", 1),
            Route = ExtractNamedValue(line, "Route="),
            Message = ExtractMessage(line)
        };
    }

    private static string Normalize(string? value)
    {
        if (string.IsNullOrWhiteSpace(value)) return "";
        return value
            .Replace("0x", "0x*", StringComparison.OrdinalIgnoreCase)
            .Replace("\"", string.Empty, StringComparison.OrdinalIgnoreCase);
    }

    private static string? ExtractTimestamp(string line)
    {
        if (!line.StartsWith("[")) return null;
        var end = line.IndexOf("] [", StringComparison.Ordinal);
        return end > 1 ? line[1..end] : null;
    }

    private static string? ExtractBetween(string text, string start, string end)
    {
        var index = text.IndexOf(start, StringComparison.OrdinalIgnoreCase);
        if (index < 0) return null;
        var slice = text[(index + start.Length)..];
        var endIndex = slice.IndexOf(end, StringComparison.OrdinalIgnoreCase);
        return endIndex < 0 ? slice : slice[..endIndex];
    }

    private static string? ExtractTokenAfter(string text, string start, int tokenIndex)
    {
        var index = text.IndexOf(start, StringComparison.OrdinalIgnoreCase);
        if (index < 0) return null;
        var tokens = text[index..].Split(' ', StringSplitOptions.RemoveEmptyEntries);
        return tokens.Length > tokenIndex ? tokens[tokenIndex] : null;
    }

    private static string? ExtractMessage(string line)
    {
        var stackIndex = line.IndexOf("Stack=", StringComparison.OrdinalIgnoreCase);
        if (stackIndex >= 0)
        {
            return line[..stackIndex].Trim();
        }

        var clientIndex = line.IndexOf("ClientLog ", StringComparison.OrdinalIgnoreCase);
        return clientIndex >= 0 ? line[(clientIndex + "ClientLog ".Length)..] : line;
    }

    private static string? ExtractNamedValue(string text, string prefix)
    {
        var index = text.IndexOf(prefix, StringComparison.OrdinalIgnoreCase);
        if (index < 0) return null;
        var slice = text[(index + prefix.Length)..];
        var end = slice.IndexOf(' ');
        return end < 0 ? slice : slice[..end];
    }
}
