using Microsoft.Data.Sqlite;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using ScadaLink.Commons.Messages.RemoteQuery;

namespace ScadaLink.SiteEventLogging;

/// <summary>
/// Processes event log queries locally against SQLite.
/// Supports filtering by event_type, time range, instance_id, severity,
/// and keyword search (LIKE on message and source).
/// Uses keyset pagination with continuation token (last event ID).
/// </summary>
public class EventLogQueryService : IEventLogQueryService
{
    private readonly SiteEventLogger _eventLogger;
    private readonly SiteEventLogOptions _options;
    private readonly ILogger<EventLogQueryService> _logger;

    public EventLogQueryService(
        SiteEventLogger eventLogger,
        IOptions<SiteEventLogOptions> options,
        ILogger<EventLogQueryService> logger)
    {
        // Depend on the concrete recorder directly: queries must funnel database
        // access through its lock-guarded WithConnection. Taking ISiteEventLogger and
        // downcasting would throw InvalidCastException for any other implementation.
        _eventLogger = eventLogger;
        _options = options.Value;
        _logger = logger;
    }

    public EventLogQueryResponse ExecuteQuery(EventLogQueryRequest request)
    {
        try
        {
            var pageSize = request.PageSize > 0 ? request.PageSize : _options.QueryPageSize;

            var whereClauses = new List<string>();
            var parameters = new List<SqliteParameter>();

            // Keyset pagination: only return events with id > continuation token
            if (request.ContinuationToken.HasValue)
            {
                whereClauses.Add("id > $afterId");
                parameters.Add(new SqliteParameter("$afterId", request.ContinuationToken.Value));
            }

            if (request.From.HasValue)
            {
                whereClauses.Add("timestamp >= $from");
                parameters.Add(new SqliteParameter("$from", request.From.Value.ToString("o")));
            }

            if (request.To.HasValue)
            {
                whereClauses.Add("timestamp <= $to");
                parameters.Add(new SqliteParameter("$to", request.To.Value.ToString("o")));
            }

            if (!string.IsNullOrWhiteSpace(request.EventType))
            {
                whereClauses.Add("event_type = $eventType");
                parameters.Add(new SqliteParameter("$eventType", request.EventType));
            }

            if (!string.IsNullOrWhiteSpace(request.Severity))
            {
                whereClauses.Add("severity = $severity");
                parameters.Add(new SqliteParameter("$severity", request.Severity));
            }

            if (!string.IsNullOrWhiteSpace(request.InstanceId))
            {
                whereClauses.Add("instance_id = $instanceId");
                parameters.Add(new SqliteParameter("$instanceId", request.InstanceId));
            }

            if (!string.IsNullOrWhiteSpace(request.KeywordFilter))
            {
                whereClauses.Add("(message LIKE $keyword OR source LIKE $keyword)");
                parameters.Add(new SqliteParameter("$keyword", $"%{request.KeywordFilter}%"));
            }

            var whereClause = whereClauses.Count > 0
                ? "WHERE " + string.Join(" AND ", whereClauses)
                : "";

            // Run the read against the shared connection under the logger's write
            // lock — the connection is not thread-safe and is also used by the
            // recorder and the purge service on other threads.
            var entries = _eventLogger.WithConnection(connection =>
            {
                using var cmd = connection.CreateCommand();

                // Fetch pageSize + 1 to determine if there are more results
                cmd.CommandText = $"""
                    SELECT id, timestamp, event_type, severity, instance_id, source, message, details
                    FROM site_events
                    {whereClause}
                    ORDER BY id ASC
                    LIMIT $limit
                    """;
                cmd.Parameters.AddWithValue("$limit", pageSize + 1);
                foreach (var p in parameters)
                    cmd.Parameters.Add(p);

                var rows = new List<EventLogEntry>();
                using var reader = cmd.ExecuteReader();
                while (reader.Read())
                {
                    rows.Add(new EventLogEntry(
                        Id: reader.GetInt64(0),
                        Timestamp: DateTimeOffset.Parse(reader.GetString(1)),
                        EventType: reader.GetString(2),
                        Severity: reader.GetString(3),
                        InstanceId: reader.IsDBNull(4) ? null : reader.GetString(4),
                        Source: reader.GetString(5),
                        Message: reader.GetString(6),
                        Details: reader.IsDBNull(7) ? null : reader.GetString(7)));
                }

                return rows;
            });

            var hasMore = entries.Count > pageSize;
            if (hasMore)
            {
                entries.RemoveAt(entries.Count - 1);
            }

            var continuationToken = entries.Count > 0 ? entries[^1].Id : (long?)null;

            return new EventLogQueryResponse(
                CorrelationId: request.CorrelationId,
                SiteId: request.SiteId,
                Entries: entries,
                ContinuationToken: continuationToken,
                HasMore: hasMore,
                Success: true,
                ErrorMessage: null,
                Timestamp: DateTimeOffset.UtcNow);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to execute event log query: {CorrelationId}", request.CorrelationId);
            return new EventLogQueryResponse(
                CorrelationId: request.CorrelationId,
                SiteId: request.SiteId,
                Entries: [],
                ContinuationToken: null,
                HasMore: false,
                Success: false,
                ErrorMessage: ex.Message,
                Timestamp: DateTimeOffset.UtcNow);
        }
    }
}
