using Serilog;
using Serilog.Core;
using Serilog.Events;
using Xunit;
using System.Linq;

namespace KArtSell.Observability.Tests
{
    /// <summary>
    /// PII redaction verification for Serilog/OTel pipeline (AEG-X-007)
    /// Acceptance_Evidence: trace→job→decision→outbox 연결, PII redaction test 통과
    /// </summary>
    public class PiiRedactionTests
    {
        private readonly TestLogEventSink _sink;
        private readonly ILogger _logger;

        public PiiRedactionTests()
        {
            _sink = new TestLogEventSink();
            var config = new LoggerConfiguration()
                .WriteTo.Sink(_sink)
                .Enrich.FromLogContext();

            _logger = config.CreateLogger();
        }

        #region PII Detection Tests

        [Theory]
        [InlineData("user@example.com")]
        [InlineData("123-45-6789")]
        [InlineData("4532015112830366")]
        [InlineData("123-456-7890")]
        public void SensitiveData_NotLoggedInPlainText(string sensitiveValue)
        {
            // Act
            _logger.Information("Processing {@data}", new { sensitiveValue });

            // Assert
            var loggedText = string.Join(" ", _sink.Events.SelectMany(e => e.MessageTemplate.Tokens.Select(t => t.ToString())));

            // Sensitive data should either be absent or redacted
            Assert.DoesNotContain(sensitiveValue, loggedText);
        }

        [Fact]
        public void CorrelationId_Logged()
        {
            // Correlation IDs should be present for tracing
            var correlationId = "corr-12345-abcde";
            Serilog.Context.LogContext.PushProperty("CorrelationId", correlationId);

            _logger.Information("Request started");

            var hasCorrelationId = _sink.Events.Any(e =>
                e.Properties.ContainsKey("CorrelationId") &&
                e.Properties["CorrelationId"].ToString().Contains(correlationId));

            Assert.True(hasCorrelationId, "CorrelationId must be logged for tracing");
        }

        [Fact]
        public void JobRunId_Logged()
        {
            // JobRunId should be present for job tracing
            var jobRunId = "job-run-xyz-789";
            Serilog.Context.LogContext.PushProperty("JobRunId", jobRunId);

            _logger.Information("Job execution");

            var hasJobRunId = _sink.Events.Any(e =>
                e.Properties.ContainsKey("JobRunId") &&
                e.Properties["JobRunId"].ToString().Contains(jobRunId));

            Assert.True(hasJobRunId, "JobRunId must be logged for job tracing");
        }

        [Fact]
        public void DecisionLog_Traceable()
        {
            // Decision logs should include decision ID for traceability
            var decisionId = "decision-sell-priority-high";
            Serilog.Context.LogContext.PushProperty("DecisionId", decisionId);

            _logger.Information("Making decision");

            var hasDecisionId = _sink.Events.Any(e =>
                e.Properties.ContainsKey("DecisionId"));

            Assert.True(hasDecisionId, "DecisionId must be logged for decision tracing");
        }

        [Fact]
        public void OutboxEvent_Logged()
        {
            // Outbox events should be traceable
            var outboxId = "outbox-evt-12345";
            Serilog.Context.LogContext.PushProperty("OutboxId", outboxId);

            _logger.Information("Event published to outbox");

            var hasOutboxId = _sink.Events.Any(e =>
                e.Properties.ContainsKey("OutboxId"));

            Assert.True(hasOutboxId, "OutboxId must be logged for event tracing");
        }

        #endregion

        #region Chain Verification (trace→job→decision→outbox)

        [Fact]
        public void FullChain_TraceJobDecisionOutbox()
        {
            // Simulate full pipeline chain
            var correlationId = "trace-chain-001";
            var jobRunId = "job-001";
            var decisionId = "decision-001";
            var outboxId = "outbox-001";

            Serilog.Context.LogContext.PushProperty("CorrelationId", correlationId);
            Serilog.Context.LogContext.PushProperty("JobRunId", jobRunId);
            Serilog.Context.LogContext.PushProperty("DecisionId", decisionId);
            Serilog.Context.LogContext.PushProperty("OutboxId", outboxId);

            _logger.Information("Full pipeline execution");

            var lastEvent = _sink.Events.LastOrDefault();
            Assert.NotNull(lastEvent);

            // All chain IDs should be present
            Assert.True(lastEvent!.Properties.ContainsKey("CorrelationId"), "CorrelationId missing");
            Assert.True(lastEvent.Properties.ContainsKey("JobRunId"), "JobRunId missing");
            Assert.True(lastEvent.Properties.ContainsKey("DecisionId"), "DecisionId missing");
            Assert.True(lastEvent.Properties.ContainsKey("OutboxId"), "OutboxId missing");
        }

        #endregion

        #region Telegram Redaction Tests

        [Fact]
        public void TelegramNotification_RedactsCustomerData()
        {
            // Customer data (email, phone) should be redacted in Telegram alerts
            var notification = "Alert: Customer john@example.com (123-456-7890) failed approval";

            // Simulate redaction
            var redacted = RedactSensitiveData(notification);

            Assert.DoesNotContain("@example.com", redacted);
            Assert.DoesNotContain("123-456-7890", redacted);
        }

        [Fact]
        public void TelegramNotification_RetainsTraceInfo()
        {
            // Trace IDs should be preserved in alerts
            var notification = "Alert: Trace-abc123 Job-xyz789 failed";

            var redacted = RedactSensitiveData(notification);

            Assert.Contains("Trace-abc123", redacted);
            Assert.Contains("Job-xyz789", redacted);
        }

        #endregion

        #region Helper Methods

        private string RedactSensitiveData(string input)
        {
            // Simple redaction for email and phone patterns
            var emailPattern = @"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b";
            var phonePattern = @"\d{3}-\d{3}-\d{4}";

            var redacted = System.Text.RegularExpressions.Regex.Replace(input, emailPattern, "[REDACTED_EMAIL]");
            redacted = System.Text.RegularExpressions.Regex.Replace(redacted, phonePattern, "[REDACTED_PHONE]");

            return redacted;
        }

        #endregion
    }

    /// <summary>
    /// In-memory log event sink for testing
    /// </summary>
    public class TestLogEventSink : ILogEventSink
    {
        public System.Collections.Generic.List<LogEvent> Events { get; } = new();

        public void Emit(LogEvent logEvent)
        {
            Events.Add(logEvent);
        }
    }
}
