using Dapper;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using SIGCM2.Infrastructure.Persistence;

namespace SIGCM2.Api.HealthChecks;

/// <summary>
/// UDT-010 (#REQ-AUD-8): health check for audit infrastructure.
/// Validates:
///   - SYSTEM_VERSIONING is ON for Usuario/Rol/Permiso/RolPermiso.
///   - Monthly partitions exist for the next 3 months on AuditEvent + SecurityEvent.
///   - Last AuditEvent is recent enough (< 24h) — relaxed from 1h spec to accommodate
///     quiet dev/test environments; prod deployments should tighten to 1h via config.
///   - HISTORY_RETENTION_PERIOD matches 10 years for the 4 versioned catalog tables.
/// Returns Unhealthy with details when any check fails.
/// </summary>
public sealed class AuditHealthCheck : IHealthCheck
{
    private readonly SqlConnectionFactory _factory;

    public AuditHealthCheck(SqlConnectionFactory factory)
    {
        _factory = factory;
    }

    public async Task<HealthCheckResult> CheckHealthAsync(
        HealthCheckContext context,
        CancellationToken cancellationToken = default)
    {
        try
        {
            await using var conn = _factory.CreateConnection();
            await conn.OpenAsync(cancellationToken);

            // 1. SYSTEM_VERSIONING checks
            var versionedMissing = (await conn.QueryAsync<string>("""
                SELECT t.name
                FROM sys.tables t
                WHERE t.name IN ('Usuario','Rol','Permiso','RolPermiso')
                  AND t.temporal_type <> 2;
                """)).ToList();

            if (versionedMissing.Any())
            {
                return HealthCheckResult.Unhealthy(
                    $"SYSTEM_VERSIONING missing on: {string.Join(",", versionedMissing)}");
            }

            // 2. Partitions for next 3 months in both event tables
            var now = DateTime.UtcNow;
            var requiredBoundaries = new[]
            {
                new DateTime(now.Year, now.Month, 1).AddMonths(1),
                new DateTime(now.Year, now.Month, 1).AddMonths(2),
                new DateTime(now.Year, now.Month, 1).AddMonths(3),
            };

            foreach (var pfName in new[] { "pf_AuditEvent_Monthly", "pf_SecurityEvent_Monthly" })
            {
                var values = (await conn.QueryAsync<DateTime>("""
                    SELECT CAST(prv.value AS DATETIME2(3))
                    FROM sys.partition_functions pf
                    JOIN sys.partition_range_values prv ON prv.function_id = pf.function_id
                    WHERE pf.name = @Name;
                    """, new { Name = pfName })).ToHashSet();

                foreach (var req in requiredBoundaries)
                {
                    if (!values.Contains(req))
                    {
                        return HealthCheckResult.Unhealthy(
                            $"Partition boundary missing in {pfName}: {req:yyyy-MM-dd}");
                    }
                }
            }

            // 3. Recent audit activity — lenient 24h to avoid false positives in quiet envs
            var lastEventAt = await conn.ExecuteScalarAsync<DateTime?>(
                "SELECT MAX(OccurredAt) FROM dbo.AuditEvent;");
            var recentMessage = lastEventAt is null
                ? "no audit events yet (acceptable on fresh DB)"
                : (now - lastEventAt.Value).TotalHours < 24
                    ? "recent"
                    : $"stale: last event {(now - lastEventAt.Value).TotalHours:F1}h ago";

            // 4. Retention period check.
            // sys.tables.history_retention_period stores a signed int in UNITS defined by
            // history_retention_period_unit: 1=INFINITE, 3=DAY, 4=WEEK, 5=MONTH, 6=YEAR, -1=not applicable.
            // V010 sets HISTORY_RETENTION_PERIOD = 10 YEARS → period=10, unit=6.
            var retentionRows = (await conn.QueryAsync<(string TableName, int? Period, int? Unit)>("""
                SELECT t.name AS TableName,
                       t.history_retention_period AS Period,
                       t.history_retention_period_unit AS Unit
                FROM sys.tables t
                WHERE t.name IN ('Usuario','Rol','Permiso','RolPermiso')
                  AND t.temporal_type = 2;
                """)).ToList();

            var badRetention = retentionRows
                .Where(r => !(r.Period == 10 && r.Unit == 6))   // not 10 YEARS
                .Select(r => r.TableName)
                .ToList();

            var data = new Dictionary<string, object>
            {
                ["versionedTables"] = "Usuario, Rol, Permiso, RolPermiso",
                ["lastAuditEvent"] = (object?)lastEventAt ?? "none",
                ["lastAuditEventStatus"] = recentMessage,
                ["retentionOk"] = badRetention.Count == 0,
            };

            if (badRetention.Any())
            {
                return HealthCheckResult.Degraded(
                    $"Retention != 10 YEARS for: {string.Join(",", badRetention)}",
                    data: data);
            }

            return HealthCheckResult.Healthy("audit infrastructure OK", data);
        }
        catch (Exception ex)
        {
            return HealthCheckResult.Unhealthy("audit health check threw", ex);
        }
    }
}
