using System;
using System.IO;
using System.Runtime.InteropServices;
using PolyTrader.Core.Security;
using Xunit;

namespace PolyTrader.Tests
{
    /// <summary>
    /// Sicherheitsnetz für D-5/D-10 (Master-Key-Erzeugung bei der Erstinstallation). Der wichtigste
    /// Fall ist der negative: ein vorhandener Schlüssel darf NIEMALS überschrieben werden - das
    /// würde jedes bereits verschlüsselte Secret in der Datenbank unwiederbringlich unlesbar machen.
    /// </summary>
    public class MasterKeyResolverTests : IDisposable
    {
        private readonly string _tempDir = Path.Combine(Path.GetTempPath(), "pt-masterkey-tests-" + Guid.NewGuid().ToString("N"));

        public MasterKeyResolverTests() => Directory.CreateDirectory(_tempDir);

        public void Dispose()
        {
            try { Directory.Delete(_tempDir, recursive: true); } catch { /* Best effort */ }
        }

        private string KeyFile => Path.Combine(_tempDir, "master.key");

        [Fact]
        public void Resolve_prefers_environment_variable_and_never_touches_the_file()
        {
            string? result = MasterKeyResolver.Resolve(KeyFile, () => "env-key-value");

            Assert.Equal("env-key-value", result);
            Assert.False(File.Exists(KeyFile));
        }

        [Fact]
        public void Resolve_reads_existing_file_without_modifying_it()
        {
            File.WriteAllText(KeyFile, "existing-key-value");
            var originalWriteTime = File.GetLastWriteTimeUtc(KeyFile);

            string? result = MasterKeyResolver.Resolve(KeyFile, () => null);

            Assert.Equal("existing-key-value", result);
            Assert.Equal(originalWriteTime, File.GetLastWriteTimeUtc(KeyFile));
        }

        [Fact]
        public void Resolve_generates_a_new_key_when_neither_variable_nor_file_exist()
        {
            bool generatedCallbackFired = false;

            string? result = MasterKeyResolver.Resolve(KeyFile, () => null, onGenerated: _ => generatedCallbackFired = true);

            Assert.False(string.IsNullOrWhiteSpace(result));
            Assert.True(File.Exists(KeyFile));
            Assert.True(generatedCallbackFired);
            Assert.Equal(result, File.ReadAllText(KeyFile).Trim());
        }

        [Fact]
        public void Resolve_generated_key_decodes_as_a_256_bit_key()
        {
            string? result = MasterKeyResolver.Resolve(KeyFile, () => null);

            byte[] bytes = Convert.FromBase64String(result!);
            Assert.Equal(32, bytes.Length);
        }

        [Fact]
        public void Resolve_never_regenerates_once_a_file_exists()
        {
            string? first = MasterKeyResolver.Resolve(KeyFile, () => null);
            string? second = MasterKeyResolver.Resolve(KeyFile, () => null);
            string? third = MasterKeyResolver.Resolve(KeyFile, () => null);

            Assert.Equal(first, second);
            Assert.Equal(first, third);
        }

        [Fact]
        public void Resolve_does_not_generate_when_environment_variable_is_set_even_without_a_file()
        {
            _ = MasterKeyResolver.Resolve(KeyFile, () => "from-env");

            Assert.False(File.Exists(KeyFile));
        }

        [Fact]
        public void Resolve_restricts_generated_file_to_owner_on_unix()
        {
            // Kein separates Skip-Paket eingebunden - unter Windows greift ohnehin die
            // NTFS-Vererbung des Profils (siehe FilePermissions), dort gibt es nichts zu prüfen.
            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return;

            MasterKeyResolver.Resolve(KeyFile, () => null);

            var mode = File.GetUnixFileMode(KeyFile);
            Assert.Equal(UnixFileMode.UserRead | UnixFileMode.UserWrite, mode);
        }

        [Fact]
        public void Resolve_returns_null_and_warns_instead_of_throwing_when_generation_fails()
        {
            // Ein Pfad in einem nicht existierenden Verzeichnis lässt File.WriteAllText scheitern.
            string badPath = Path.Combine(_tempDir, "does-not-exist", "master.key");
            string? warning = null;

            string? result = MasterKeyResolver.Resolve(badPath, () => null, onWarning: msg => warning = msg);

            Assert.Null(result);
            Assert.NotNull(warning);
        }
    }
}
