using MMoneyCoderSharp.Data;

namespace MBN_STOCK_WEBVIEW.Infrastructure;

public sealed class DatabaseRuntime
{
    private DatabaseRuntime(
        DatabaseOptions options,
        IDatabaseConnectionFactory connectionFactory,
        IDataQueryExecutor executor,
        IDatabaseHealthService healthService)
    {
        Options = options;
        ConnectionFactory = connectionFactory;
        Executor = executor;
        HealthService = healthService;
    }

    public DatabaseOptions Options { get; }

    public IDatabaseConnectionFactory ConnectionFactory { get; }

    public IDataQueryExecutor Executor { get; }

    public IDatabaseHealthService HealthService { get; }

    public static DatabaseRuntime CreateDefault(string? configurationPath = null)
    {
        var options = new DatabaseOptionsLoader().Load(configurationPath);
        return Create(options);
    }

    public static DatabaseRuntime CreateLegacyExecutableDefault(
        string baseDirectory,
        string? fallbackConfigurationPath = null,
        string? legacyOverridePath = null,
        bool preferLegacyOverride = false)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(baseDirectory);
        if (preferLegacyOverride &&
            !string.IsNullOrWhiteSpace(legacyOverridePath) &&
            File.Exists(legacyOverridePath))
        {
            return Create(new LegacyIniDatabaseOptionsLoader().Load(legacyOverridePath));
        }

        var legacyIniPath = Path.Combine(
            Path.GetFullPath(baseDirectory),
            "Res",
            "MmoneyCoder.ini");
        if (File.Exists(legacyIniPath))
        {
            return Create(new LegacyIniDatabaseOptionsLoader().Load(legacyIniPath));
        }

        var localOverride = legacyOverridePath ?? ResolveLegacyOverridePath();
        if (!string.IsNullOrWhiteSpace(localOverride) && File.Exists(localOverride))
        {
            return Create(new LegacyIniDatabaseOptionsLoader().Load(localOverride));
        }

        return CreateDefault(fallbackConfigurationPath);
    }

    /// <summary>
    /// Creates the Development Live database runtime from the one protected
    /// current-user LocalAppData legacy INI, or from the protected LocalAppData
    /// JSON when that INI is absent. Unlike the ordinary compatibility path, this
    /// never probes executable or operator-selected Res folders and ignores all
    /// database environment overrides.
    /// </summary>
    public static DatabaseRuntime CreateDevelopmentLiveLocalOverride()
    {
        var localApplicationData = Environment.GetFolderPath(
            Environment.SpecialFolder.LocalApplicationData);
        return CreateDevelopmentLiveLocalOverride(localApplicationData);
    }

    internal static DatabaseRuntime CreateDevelopmentLiveLocalOverride(
        string localApplicationData)
    {
        try
        {
            ArgumentException.ThrowIfNullOrWhiteSpace(localApplicationData);
            var localRoot = Path.TrimEndingDirectorySeparator(
                Path.GetFullPath(localApplicationData));
            var resourceDirectory = Path.Combine(
                localRoot,
                "MBN_STOCK_WEBVIEW",
                "Res");
            var legacyIniPath = Path.Combine(resourceDirectory, "MmoneyCoder.ini");
            if (TryEnsureOrdinaryFile(legacyIniPath))
            {
                EnsureNoReparsePointDirectoryChain(resourceDirectory, localRoot);
                return Create(
                    new LegacyIniDatabaseOptionsLoader(_ => null).Load(legacyIniPath));
            }

            var configurationDirectory = Path.Combine(
                localRoot,
                "MBN_STOCK_WEBVIEW",
                "Config");
            EnsureNoReparsePointDirectoryChain(configurationDirectory, localRoot);
            var jsonPath = Path.Combine(
                configurationDirectory,
                "database.local.json");
            EnsureOrdinaryFile(jsonPath);
            var options = new DatabaseOptionsLoader(_ => null).Load(jsonPath);
            if (!options.IsConfigured(MMoneyCoderSharp.Data.DataSourceKind.Oracle) ||
                !options.IsConfigured(MMoneyCoderSharp.Data.DataSourceKind.MariaDb))
            {
                throw new DatabaseConfigurationException(
                    "Development Live database configuration is unavailable.");
            }

            return Create(options);
        }
        catch (DatabaseConfigurationException)
        {
            throw;
        }
        catch (Exception exception) when (
            exception is ArgumentException or IOException or UnauthorizedAccessException or
                InvalidDataException or NotSupportedException or
                System.Security.SecurityException)
        {
            throw new DatabaseConfigurationException(
                "Development Live database configuration is unavailable.");
        }
    }

    private static string? ResolveLegacyOverridePath()
    {
        var localAppData = Environment.GetFolderPath(
            Environment.SpecialFolder.LocalApplicationData);
        return string.IsNullOrWhiteSpace(localAppData)
            ? null
            : Path.Combine(
                localAppData,
                "MBN_STOCK_WEBVIEW",
                "Res",
                "MmoneyCoder.ini");
    }

    private static void EnsureNoReparsePointDirectoryChain(
        string directory,
        string trustedRoot)
    {
        var current = Path.TrimEndingDirectorySeparator(Path.GetFullPath(directory));
        var root = Path.TrimEndingDirectorySeparator(Path.GetFullPath(trustedRoot));
        while (!string.Equals(current, root, StringComparison.OrdinalIgnoreCase))
        {
            if ((File.GetAttributes(current) & FileAttributes.ReparsePoint) != 0)
            {
                throw new InvalidDataException();
            }

            var parent = Path.GetDirectoryName(current);
            if (string.IsNullOrWhiteSpace(parent) ||
                string.Equals(parent, current, StringComparison.OrdinalIgnoreCase))
            {
                throw new InvalidDataException();
            }

            current = Path.TrimEndingDirectorySeparator(Path.GetFullPath(parent));
        }
    }

    private static void EnsureOrdinaryFile(string path)
    {
        var attributes = File.GetAttributes(Path.GetFullPath(path));
        if ((attributes & (FileAttributes.Directory | FileAttributes.ReparsePoint)) != 0)
        {
            throw new InvalidDataException();
        }
    }

    private static bool TryEnsureOrdinaryFile(string path)
    {
        try
        {
            EnsureOrdinaryFile(path);
            return true;
        }
        catch (FileNotFoundException)
        {
            return false;
        }
        catch (DirectoryNotFoundException)
        {
            return false;
        }
    }

    public static DatabaseRuntime Create(DatabaseOptions options)
    {
        ArgumentNullException.ThrowIfNull(options);
        options.Normalize();
        options.ValidateRuntimeSettings();

        var connectionFactory = new ProviderDatabaseConnectionFactory(options);
        var executor = new ResilientDataQueryExecutor(connectionFactory, options.Resilience);
        var healthService = new DatabaseHealthService(executor, options);
        return new DatabaseRuntime(options, connectionFactory, executor, healthService);
    }
}
