using System;
using System.Threading.Tasks;
using FluentAssertions;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Moq;
using TeleBot.Services;
using Xunit;

namespace TeleBot.Tests.Services
{
    public class PrivacyServiceTests
    {
        private readonly PrivacyService _privacyService;
        private readonly Mock<IConfiguration> _mockConfiguration;
        private readonly Mock<ILogger<PrivacyService>> _mockLogger;

        public PrivacyServiceTests()
        {
            _mockConfiguration = new Mock<IConfiguration>();
            _mockLogger = new Mock<ILogger<PrivacyService>>();
            
            // Set up default configuration
            _mockConfiguration.Setup(x => x["Privacy:HashSalt"])
                .Returns("TestSalt123");
            _mockConfiguration.Setup(x => x["Privacy:EnableTor"])
                .Returns("false");
            
            _privacyService = new PrivacyService(_mockConfiguration.Object, _mockLogger.Object);
        }

        [Fact]
        public void HashIdentifier_ShouldReturnConsistentHash()
        {
            // Arrange
            long telegramId = 123456789;

            // Act
            var hash1 = _privacyService.HashIdentifier(telegramId);
            var hash2 = _privacyService.HashIdentifier(telegramId);

            // Assert
            hash1.Should().NotBeNullOrEmpty();
            hash2.Should().NotBeNullOrEmpty();
            hash1.Should().Be(hash2, "Hash should be consistent for the same input");
        }

        [Fact]
        public void HashIdentifier_DifferentIds_ShouldReturnDifferentHashes()
        {
            // Arrange
            long telegramId1 = 123456789;
            long telegramId2 = 987654321;

            // Act
            var hash1 = _privacyService.HashIdentifier(telegramId1);
            var hash2 = _privacyService.HashIdentifier(telegramId2);

            // Assert
            hash1.Should().NotBe(hash2, "Different IDs should produce different hashes");
        }

        [Fact]
        public void GenerateAnonymousReference_ShouldReturnUniqueReferences()
        {
            // Act
            var ref1 = _privacyService.GenerateAnonymousReference();
            var ref2 = _privacyService.GenerateAnonymousReference();

            // Assert
            ref1.Should().StartWith("ANON-");
            ref2.Should().StartWith("ANON-");
            ref1.Should().HaveLength(17); // ANON- (5) + 12 characters
            ref1.Should().NotBe(ref2, "Each reference should be unique");
        }

        [Fact]
        public void GenerateAnonymousReference_ShouldNotContainSpecialCharacters()
        {
            // Act
            var reference = _privacyService.GenerateAnonymousReference();

            // Assert
            reference.Should().MatchRegex(@"^ANON-[A-Z0-9]+$");
        }

        [Fact]
        public async Task CreateTorHttpClient_WhenTorDisabled_ShouldReturnRegularClient()
        {
            // Arrange
            _mockConfiguration.Setup(x => x["Privacy:EnableTor"])
                .Returns("false");

            // Act
            var client = await _privacyService.CreateTorHttpClient();

            // Assert
            client.Should().NotBeNull();
            client.Timeout.Should().Be(TimeSpan.FromSeconds(100)); // Default timeout
        }

        [Fact]
        public void EncryptData_ShouldEncryptAndDecryptSuccessfully()
        {
            // Arrange
            var originalData = System.Text.Encoding.UTF8.GetBytes("Secret message");
            var key = "TestEncryptionKey123";

            // Act
            var encrypted = _privacyService.EncryptData(originalData, key);
            var decrypted = _privacyService.DecryptData(encrypted, key);

            // Assert
            encrypted.Should().NotBeEquivalentTo(originalData, "Data should be encrypted");
            decrypted.Should().BeEquivalentTo(originalData, "Decrypted data should match original");
        }

        [Fact]
        public void EncryptData_WithDifferentKeys_ShouldProduceDifferentResults()
        {
            // Arrange
            var originalData = System.Text.Encoding.UTF8.GetBytes("Secret message");
            var key1 = "Key1";
            var key2 = "Key2";

            // Act
            var encrypted1 = _privacyService.EncryptData(originalData, key1);
            var encrypted2 = _privacyService.EncryptData(originalData, key2);

            // Assert
            encrypted1.Should().NotBeEquivalentTo(encrypted2, "Different keys should produce different encryptions");
        }

        [Fact]
        public void SanitizeLogMessage_ShouldRemoveEmail()
        {
            // Arrange
            var message = "User email is test@example.com in the system";

            // Act
            _privacyService.SanitizeLogMessage(ref message);

            // Assert
            message.Should().Be("User email is [EMAIL_REDACTED] in the system");
        }

        [Fact]
        public void SanitizeLogMessage_ShouldRemovePhoneNumber()
        {
            // Arrange
            var message = "Contact number: 555-123-4567";

            // Act
            _privacyService.SanitizeLogMessage(ref message);

            // Assert
            message.Should().Be("Contact number: [PHONE_REDACTED]");
        }

        [Fact]
        public void SanitizeLogMessage_ShouldRemoveTelegramId()
        {
            // Arrange
            var message = "Processing request for telegram_id:123456789";

            // Act
            _privacyService.SanitizeLogMessage(ref message);

            // Assert
            message.Should().Be("Processing request for telegram_id=[REDACTED]");
        }

        [Theory]
        [InlineData("")]
        [InlineData("Normal log message without PII")]
        [InlineData("Order ID: 12345")]
        public void SanitizeLogMessage_WithoutPII_ShouldRemainUnchanged(string message)
        {
            // Arrange
            var original = message;

            // Act
            _privacyService.SanitizeLogMessage(ref message);

            // Assert
            message.Should().Be(original);
        }
    }
}