using System.Net;
using System.Text;
using Jiaowu.Api.Infrastructure.Observability;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging.Abstractions;

namespace Jiaowu.Api.Tests;

public sealed class PerformanceReportServiceTests
{
    [Fact]
    public async Task Configured_source_returns_cached_native_report()
    {
        var handler = new PrometheusHandler();
        using var httpClient = new HttpClient(handler);
        using var cache = new MemoryCache(new MemoryCacheOptions());
        var service = new PerformanceReportService(
            httpClient,
            cache,
            new PerformanceReportingOptions
            {
                Enabled = true,
                PrometheusBaseUrl = "https://prometheus.test/",
                BearerToken = "read-only-token",
                GrafanaBaseUrl = "https://grafana.test/",
                CacheSeconds = 30
            },
            new ObservabilityOptions { ServiceName = "jiaowu-api" },
            NullLogger<PerformanceReportService>.Instance);

        var first = await service.GetAsync("1h", CancellationToken.None);
        var second = await service.GetAsync("1h", CancellationToken.None);

        Assert.Equal("ready", first.Status);
        Assert.Equal("prometheus", first.DataSource);
        Assert.Equal("https://grafana.test/", first.DashboardUrl);
        Assert.NotNull(first.Headline);
        Assert.Equal(5, first.Headline.RequestCount);
        Assert.Equal(2, first.Timeline.Count);
        Assert.Equal(
            "/api/timetables/classes/{classId}",
            Assert.Single(first.Endpoints).Name);
        Assert.Equal(
            "Timetable.LoadScheduleEntries",
            Assert.Single(first.DatabaseQueries).Name);
        Assert.Same(first, second);
        Assert.Equal(14, handler.RequestCount);
        Assert.True(handler.AllRequestsAuthenticated);
    }

    [Fact]
    public async Task Missing_source_returns_directed_empty_state()
    {
        using var cache = new MemoryCache(new MemoryCacheOptions());
        var service = new PerformanceReportService(
            new HttpClient(new RejectingHandler()),
            cache,
            new PerformanceReportingOptions(),
            new ObservabilityOptions(),
            NullLogger<PerformanceReportService>.Instance);

        var report = await service.GetAsync("24h", CancellationToken.None);

        Assert.Equal("not_configured", report.Status);
        Assert.Contains("Prometheus", report.Detail);
        Assert.Empty(report.Timeline);
    }

    [Fact]
    public async Task Unsupported_range_is_rejected_before_querying_source()
    {
        using var cache = new MemoryCache(new MemoryCacheOptions());
        var service = new PerformanceReportService(
            new HttpClient(new RejectingHandler()),
            cache,
            new PerformanceReportingOptions(),
            new ObservabilityOptions(),
            NullLogger<PerformanceReportService>.Instance);

        await Assert.ThrowsAsync<ArgumentOutOfRangeException>(() =>
            service.GetAsync("30d", CancellationToken.None));
        await Assert.ThrowsAsync<ArgumentOutOfRangeException>(() =>
            service.GetAsync(null, CancellationToken.None));
    }

    [Fact]
    public async Task Source_timeout_returns_unavailable_report()
    {
        using var cache = new MemoryCache(new MemoryCacheOptions());
        var service = new PerformanceReportService(
            new HttpClient(new TimeoutHandler()),
            cache,
            new PerformanceReportingOptions
            {
                Enabled = true,
                PrometheusBaseUrl = "https://prometheus.test/"
            },
            new ObservabilityOptions(),
            NullLogger<PerformanceReportService>.Instance);

        var report = await service.GetAsync("1h", CancellationToken.None);

        Assert.Equal("unavailable", report.Status);
        Assert.Contains("暂时不可用", report.Detail);
    }

    private sealed class PrometheusHandler : HttpMessageHandler
    {
        private int requestCount;
        private int authenticatedCount;

        public int RequestCount => requestCount;
        public bool AllRequestsAuthenticated =>
            authenticatedCount == requestCount;

        protected override Task<HttpResponseMessage> SendAsync(
            HttpRequestMessage request,
            CancellationToken cancellationToken)
        {
            Interlocked.Increment(ref requestCount);
            if (request.Headers.Authorization?.Scheme == "Bearer" &&
                request.Headers.Authorization.Parameter == "read-only-token")
            {
                Interlocked.Increment(ref authenticatedCount);
            }

            var isRange = request.RequestUri!.AbsolutePath.EndsWith(
                "/query_range",
                StringComparison.Ordinal);
            var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
            var json = isRange
                ? $$"""
                    {
                      "status": "success",
                      "data": {
                        "resultType": "matrix",
                        "result": [{
                          "metric": {},
                          "values": [
                            [{{now - 60}}, "2"],
                            [{{now}}, "3"]
                          ]
                        }]
                      }
                    }
                    """
                : $$"""
                    {
                      "status": "success",
                      "data": {
                        "resultType": "vector",
                        "result": [{
                          "metric": {
                            "http_route": "/api/timetables/classes/{classId}",
                            "db_query_name": "Timetable.LoadScheduleEntries"
                          },
                          "value": [{{now}}, "5"]
                        }]
                      }
                    }
                    """;
            return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)
            {
                Content = new StringContent(
                    json,
                    Encoding.UTF8,
                    "application/json")
            });
        }
    }

    private sealed class RejectingHandler : HttpMessageHandler
    {
        protected override Task<HttpResponseMessage> SendAsync(
            HttpRequestMessage request,
            CancellationToken cancellationToken) =>
            throw new InvalidOperationException(
                "未配置时不应访问外部数据源。");
    }

    private sealed class TimeoutHandler : HttpMessageHandler
    {
        protected override Task<HttpResponseMessage> SendAsync(
            HttpRequestMessage request,
            CancellationToken cancellationToken) =>
            throw new TaskCanceledException("Prometheus query timed out.");
    }
}
