using System.Diagnostics;
using System.Security.Cryptography;
using Journal.Core.Services.Database;

namespace Journal.Core.Services.Vault;

public class VaultStorageService(IVaultCryptoService crypto, IJournalDatabaseService database) : IVaultStorageService
{
    private readonly IVaultCryptoService _crypto = crypto;
    private readonly IJournalDatabaseService _database = database;
    private readonly object _vaultIoLock = new();

    private const string DatabaseVaultPrefix = "_db_";
    private const string DatabaseVaultSuffix = ".vault";

    public bool LoadAllVaults(string password, string vaultDirectory, string dataDirectory)
    {
        EnsureRequiredArguments(password, vaultDirectory, dataDirectory);

        lock (_vaultIoLock)
        {
            var dbDirectory = GetDatabaseDirectory();
            Directory.CreateDirectory(dbDirectory);
            if (!Directory.Exists(vaultDirectory))
                return true;

            return RestoreDatabaseVaults(password, vaultDirectory, dbDirectory);
        }
    }

    public void RebuildAllVaults(string password, string vaultDirectory, string dataDirectory)
    {
        EnsureRequiredArguments(password, vaultDirectory, dataDirectory);

        lock (_vaultIoLock)
        {
            Directory.CreateDirectory(vaultDirectory);
            var dbDirectory = GetDatabaseDirectory();
            if (!Directory.Exists(dbDirectory))
                return;

            SaveDatabaseVaults(password, vaultDirectory, dbDirectory);
        }
    }

    public void ClearDataDirectory(string dataDirectory)
    {
        if (string.IsNullOrWhiteSpace(dataDirectory))
            throw new ArgumentException("Data directory is required.", nameof(dataDirectory));

        lock (_vaultIoLock)
        {
            var normalizedDataDir = Path.GetFullPath(dataDirectory);
            var dbDirectory = GetDatabaseDirectory();
            if (string.Equals(normalizedDataDir, dbDirectory, StringComparison.OrdinalIgnoreCase))
                return;

            DeleteDirectoryWithRetries(normalizedDataDir);
        }
    }

    private void SaveDatabaseVaults(string password, string vaultDirectory, string dataDirectory)
    {
        var dbFiles = Directory.GetFiles(dataDirectory, "*.db");
        foreach (var dbPath in dbFiles)
        {
            try
            {
                var dbFileName = Path.GetFileName(dbPath);
                var vaultFileName = $"{DatabaseVaultPrefix}{dbFileName}{DatabaseVaultSuffix}";
                var vaultPath = Path.Combine(vaultDirectory, vaultFileName);

                var dbBytes = File.ReadAllBytes(dbPath);
                var encrypted = _crypto.EncryptData(dbBytes, password);
                File.WriteAllBytes(vaultPath, encrypted);
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"[VaultStorageService] Failed to save database vault for {Path.GetFileName(dbPath)}: {ex.Message}");
            }
        }
    }

    private bool RestoreDatabaseVaults(string password, string vaultDirectory, string dataDirectory)
    {
        var dbVaultFiles = Directory.GetFiles(vaultDirectory, $"{DatabaseVaultPrefix}*{DatabaseVaultSuffix}");
        if (dbVaultFiles.Length == 0)
            return true;

        var anyRestored = false;
        foreach (var vaultFile in dbVaultFiles)
        {
            try
            {
                var vaultFileName = Path.GetFileName(vaultFile);
                var dbFileName = vaultFileName[DatabaseVaultPrefix.Length..^DatabaseVaultSuffix.Length];
                if (string.IsNullOrWhiteSpace(dbFileName))
                    continue;

                var encrypted = File.ReadAllBytes(vaultFile);
                var dbBytes = _crypto.DecryptData(encrypted, password);
                var targetPath = Path.Combine(dataDirectory, dbFileName);
                File.WriteAllBytes(targetPath, dbBytes);
                anyRestored = true;
            }
            catch (CryptographicException)
            {
                Debug.WriteLine($"[VaultStorageService] Database vault decryption failed for {Path.GetFileName(vaultFile)} (likely wrong password)");
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"[VaultStorageService] Failed to restore database vault {Path.GetFileName(vaultFile)}: {ex.Message}");
            }
        }

        return anyRestored;
    }

    private static void EnsureRequiredArguments(string password, string vaultDirectory, string dataDirectory)
    {
        if (string.IsNullOrWhiteSpace(password))
            throw new ArgumentException("Password cannot be empty.", nameof(password));
        if (string.IsNullOrWhiteSpace(vaultDirectory))
            throw new ArgumentException("Vault directory is required.", nameof(vaultDirectory));
        if (string.IsNullOrWhiteSpace(dataDirectory))
            throw new ArgumentException("Data directory is required.", nameof(dataDirectory));
    }

    private string GetDatabaseDirectory()
    {
        var dbPath = _database.GetDatabasePath();
        var directory = Path.GetDirectoryName(dbPath);
        return string.IsNullOrWhiteSpace(directory)
            ? Path.GetFullPath(".")
            : Path.GetFullPath(directory);
    }

    private static void DeleteDirectoryWithRetries(string dataDirectory, int retries = 5, int delayMs = 200)
    {
        if (!Directory.Exists(dataDirectory))
            return;

        for (var attempt = 0; attempt < retries; attempt++)
        {
            try
            {
                Directory.Delete(dataDirectory, recursive: true);
                return;
            }
            catch (IOException) when (attempt < retries - 1)
            {
                Thread.Sleep(delayMs);
            }
            catch (UnauthorizedAccessException) when (attempt < retries - 1)
            {
                Thread.Sleep(delayMs);
            }
        }

        Directory.Delete(dataDirectory, recursive: true);
    }
}
