using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using FluentAssertions;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Xunit;
using ZB.MOM.WW.LmxProxy.Host.Domain;
using ZB.MOM.WW.LmxProxy.Host.Health;
using HealthCheckService = ZB.MOM.WW.LmxProxy.Host.Health.HealthCheckService;
using ZB.MOM.WW.LmxProxy.Host.Metrics;
using ZB.MOM.WW.LmxProxy.Host.Subscriptions;

namespace ZB.MOM.WW.LmxProxy.Host.Tests.Health
{
    public class HealthCheckServiceTests
    {
        private class FakeScadaClient : IScadaClient
        {
            public bool IsConnected { get; set; } = true;
            public ConnectionState ConnectionState { get; set; } = ConnectionState.Connected;
            public DateTime ConnectedSince => DateTime.UtcNow;
            public int ReconnectCount => 0;
            public event EventHandler<ConnectionStateChangedEventArgs>? ConnectionStateChanged;
            public Task ConnectAsync(CancellationToken ct = default) => Task.CompletedTask;
            public Task DisconnectAsync(CancellationToken ct = default) => Task.CompletedTask;
            public Task<Vtq> ReadAsync(string address, CancellationToken ct = default) =>
                Task.FromResult(Vtq.Good(42.0));
            public Task<IReadOnlyDictionary<string, Vtq>> ReadBatchAsync(IEnumerable<string> addresses, CancellationToken ct = default) =>
                Task.FromResult<IReadOnlyDictionary<string, Vtq>>(new Dictionary<string, Vtq>());
            public Task WriteAsync(string address, object value, CancellationToken ct = default) => Task.CompletedTask;
            public Task WriteBatchAsync(IReadOnlyDictionary<string, object> values, CancellationToken ct = default) => Task.CompletedTask;
            public Task<(bool flagReached, int elapsedMs)> WriteBatchAndWaitAsync(
                IReadOnlyDictionary<string, object> values, string flagTag, object flagValue,
                int timeoutMs, int pollIntervalMs, CancellationToken ct = default) =>
                Task.FromResult((false, 0));
            public Task UnsubscribeByAddressAsync(IEnumerable<string> addresses) => Task.CompletedTask;
            public Task<IAsyncDisposable> SubscribeAsync(IEnumerable<string> addresses, Action<string, Vtq> callback, CancellationToken ct = default) =>
                Task.FromResult<IAsyncDisposable>(new FakeHandle());
            public ValueTask DisposeAsync() => default;
            internal void FireEvent() => ConnectionStateChanged?.Invoke(this, null!);
            private class FakeHandle : IAsyncDisposable { public ValueTask DisposeAsync() => default; }
        }

        [Fact]
        public async Task ReturnsHealthy_WhenConnectedAndNormalMetrics()
        {
            var client = new FakeScadaClient { IsConnected = true, ConnectionState = ConnectionState.Connected };
            using var sm = new SubscriptionManager(client);
            using var pm = new PerformanceMetrics();
            pm.RecordOperation("Read", TimeSpan.FromMilliseconds(10), true);

            var svc = new HealthCheckService(client, sm, pm);

            var result = await svc.CheckHealthAsync(new HealthCheckContext());

            result.Status.Should().Be(HealthStatus.Healthy);
        }

        [Fact]
        public async Task ReturnsUnhealthy_WhenNotConnected()
        {
            var client = new FakeScadaClient { IsConnected = false, ConnectionState = ConnectionState.Disconnected };
            using var sm = new SubscriptionManager(client);
            using var pm = new PerformanceMetrics();

            var svc = new HealthCheckService(client, sm, pm);

            var result = await svc.CheckHealthAsync(new HealthCheckContext());

            result.Status.Should().Be(HealthStatus.Unhealthy);
            result.Description.Should().Contain("not connected");
        }

        [Fact]
        public async Task ReturnsDegraded_WhenSuccessRateBelow50Percent()
        {
            var client = new FakeScadaClient { IsConnected = true };
            using var sm = new SubscriptionManager(client);
            using var pm = new PerformanceMetrics();

            // Record 200 operations with 40% success rate
            for (int i = 0; i < 80; i++)
                pm.RecordOperation("Read", TimeSpan.FromMilliseconds(10), true);
            for (int i = 0; i < 120; i++)
                pm.RecordOperation("Read", TimeSpan.FromMilliseconds(10), false);

            var svc = new HealthCheckService(client, sm, pm);

            var result = await svc.CheckHealthAsync(new HealthCheckContext());

            result.Status.Should().Be(HealthStatus.Degraded);
            result.Description.Should().Contain("success rate");
        }

        [Fact]
        public async Task ReturnsDegraded_WhenClientCountOver100()
        {
            var client = new FakeScadaClient { IsConnected = true };
            using var sm = new SubscriptionManager(client);
            using var pm = new PerformanceMetrics();

            // Create 101 subscriptions to exceed the threshold
            for (int i = 0; i < 101; i++)
            {
                using var cts = new CancellationTokenSource();
                await sm.SubscribeAsync("client-" + i, new[] { "tag1" }, cts.Token);
            }

            var svc = new HealthCheckService(client, sm, pm);

            var result = await svc.CheckHealthAsync(new HealthCheckContext());

            result.Status.Should().Be(HealthStatus.Degraded);
            result.Description.Should().Contain("client count");
        }

        [Fact]
        public async Task DoesNotFlagLowSuccessRate_Under100Operations()
        {
            var client = new FakeScadaClient { IsConnected = true };
            using var sm = new SubscriptionManager(client);
            using var pm = new PerformanceMetrics();

            // Record 50 operations with 0% success rate (under 100 threshold)
            for (int i = 0; i < 50; i++)
                pm.RecordOperation("Read", TimeSpan.FromMilliseconds(10), false);

            var svc = new HealthCheckService(client, sm, pm);

            var result = await svc.CheckHealthAsync(new HealthCheckContext());

            result.Status.Should().Be(HealthStatus.Healthy);
        }
    }

}
