using FastEndpoints;

namespace TaxBaik.Web.Endpoints.ClientLogs;

public sealed class RecentClientLogEntry
{
    public string? Timestamp { get; set; }
    public DateTimeOffset? OccurredAt { get; set; }
    public string? Level { get; set; }
    public string? Source { get; set; }
    public string? Message { get; set; }
    public string? Route { get; set; }
    public string? Screen { get; set; }
    public string? Feature { get; set; }
    public string? Action { get; set; }
    public string? Step { get; set; }
    public string? BuildVersion { get; set; }
}

public sealed class GetRecentLogsEndpoint : EndpointWithoutRequest<IReadOnlyList<RecentClientLogEntry>>
{
    private readonly IWebHostEnvironment _environment;

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

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

    public override async Task HandleAsync(CancellationToken ct)
    {
        var level = Query<string>("level");
        var source = Query<string>("source");
        var route = Query<string>("route");
        var term = Query<string>("q");
        var start = ParseDate(Query<string>("start"));
        var end = ParseDate(Query<string>("end"));

        var logDir = Path.Combine(_environment.ContentRootPath, "logs");
        if (!Directory.Exists(logDir))
        {
            await SendAsync(Array.Empty<RecentClientLogEntry>(), cancellation: ct);
            return;
        }

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

        var entries = new List<RecentClientLogEntry>();

        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);
                if (Matches(entry, level, source, route, term, start, end))
                    entries.Add(entry);
            }
        }

        await SendAsync(entries.TakeLast(50).Reverse().ToList(), cancellation: ct);
    }

    private static RecentClientLogEntry Parse(string line)
    {
        var timestamp = ExtractTimestamp(line);
        return new RecentClientLogEntry
        {
            Timestamp = timestamp,
            OccurredAt = ParseDate(timestamp),
            Level = ExtractBetween(line, "] [", "]"),
            Source = ExtractTokenAfter(line, "ClientLog ", 1),
            Route = ExtractNamedValue(line, "Route="),
            Screen = ExtractNamedValue(line, "Screen="),
            Feature = ExtractNamedValue(line, "Feature="),
            Action = ExtractNamedValue(line, "Action="),
            Step = ExtractNamedValue(line, "Step="),
            BuildVersion = ExtractNamedValue(line, "BuildVersion="),
            Message = ExtractMessage(line)
        };
    }

    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 messageIndex = line.IndexOf("Stack=", StringComparison.OrdinalIgnoreCase);
        if (messageIndex >= 0)
        {
            return line[..messageIndex].Trim();
        }

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

    private static bool Matches(RecentClientLogEntry entry, string? level, string? source, string? route, string? term, DateTimeOffset? start, DateTimeOffset? end)
    {
        if (!string.IsNullOrWhiteSpace(level) &&
            !string.Equals(entry.Level, level, StringComparison.OrdinalIgnoreCase))
        {
            return false;
        }

        if (!string.IsNullOrWhiteSpace(source) &&
            !(entry.Source?.Contains(source, StringComparison.OrdinalIgnoreCase) ?? false))
        {
            return false;
        }

        if (!string.IsNullOrWhiteSpace(route) &&
            !(entry.Route?.Contains(route, StringComparison.OrdinalIgnoreCase) ?? false))
        {
            return false;
        }

        if (!string.IsNullOrWhiteSpace(term) &&
            !(entry.Message?.Contains(term, StringComparison.OrdinalIgnoreCase) ?? false) &&
            !(entry.Source?.Contains(term, StringComparison.OrdinalIgnoreCase) ?? false))
        {
            return false;
        }

        if (start is not null && (entry.OccurredAt is null || entry.OccurredAt < start))
        {
            return false;
        }

        if (end is not null && (entry.OccurredAt is null || entry.OccurredAt > end))
        {
            return false;
        }

        return true;
    }

    private static DateTimeOffset? ParseDate(string? value)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            return null;
        }

        return DateTimeOffset.TryParse(value, out var parsed) ? parsed : null;
    }

    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];
    }
}
