using System.Data;
using System.Data.Common;
using Microsoft.Extensions.Logging;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;

namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts;

/// <summary>
/// Thin decorator over the
/// <see cref="DbConnection"/> returned by
/// <see cref="ScriptRuntimeContext.DatabaseHelper.Connection"/>. The decorator
/// itself does no audit work — it simply intercepts
/// <see cref="CreateDbCommand"/> so the <see cref="DbCommand"/> handed back to
/// the script is wrapped in an <see cref="AuditingDbCommand"/> that emits one
/// <c>DbOutbound</c>/<c>DbWrite</c> audit row per execution.
/// </summary>
/// <remarks>
/// <para>
/// All other <see cref="DbConnection"/> members forward to the inner connection
/// unchanged so the script keeps full ADO.NET semantics (transactions, state
/// transitions, server-version queries, etc.). Disposing the wrapper disposes
/// the inner connection — the caller is still responsible for disposal per
/// the <see cref="IDatabaseGateway"/> contract.
/// </para>
/// <para>
/// The audit-write failure contract (alog.md §7) is honoured at the
/// <see cref="AuditingDbCommand"/> layer — see that class for the 3-layer
/// fail-safe pattern (build, write, observe).
/// </para>
/// </remarks>
internal sealed class AuditingDbConnection : DbConnection
{
    private readonly DbConnection _inner;
    private readonly IAuditWriter _auditWriter;
    private readonly string _connectionName;
    private readonly string _siteId;
    private readonly string _instanceName;
    private readonly string? _sourceScript;
    private readonly Guid _executionId;

    /// <summary>
    /// ParentExecutionId: the spawning execution's id when this
    /// run was inbound-API-routed; <c>null</c> for non-routed runs. Threaded
    /// alongside <see cref="_executionId"/> into the
    /// <see cref="AuditingDbCommand"/> so its <c>DbWrite</c> row stamps it.
    /// </summary>
    private readonly Guid? _parentExecutionId;

    private readonly ILogger _logger;

    /// <summary>
    /// Initializes a new auditing database connection decorator.
    /// </summary>
    /// <param name="inner">The inner database connection to wrap and audit commands through.</param>
    /// <param name="auditWriter">Writer for audit log entries.</param>
    /// <param name="connectionName">Name of the database connection being used.</param>
    /// <param name="siteId">Identifier of the site executing commands.</param>
    /// <param name="instanceName">Unique name of the instance executing commands.</param>
    /// <param name="sourceScript">Optional name of the source script for audit trail.</param>
    /// <param name="logger">Logger for diagnostics and warnings.</param>
    /// <param name="executionId">Unique identifier for this script execution.</param>
    /// <param name="parentExecutionId">Optional identifier of the parent execution (for routed calls).</param>
    // Parameter ordering: executionId sits immediately after the ILogger,
    // consistent with the other three audit-threaded ctors (ExternalSystemHelper,
    // DatabaseHelper, AuditingDbCommand). parentExecutionId is a trailing
    // optional param so existing positional callers stay source-compatible.
    public AuditingDbConnection(
        DbConnection inner,
        IAuditWriter auditWriter,
        string connectionName,
        string siteId,
        string instanceName,
        string? sourceScript,
        ILogger logger,
        Guid executionId,
        Guid? parentExecutionId = null)
    {
        _inner = inner ?? throw new ArgumentNullException(nameof(inner));
        _auditWriter = auditWriter ?? throw new ArgumentNullException(nameof(auditWriter));
        _connectionName = connectionName ?? throw new ArgumentNullException(nameof(connectionName));
        _siteId = siteId ?? string.Empty;
        _instanceName = instanceName ?? string.Empty;
        _sourceScript = sourceScript;
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
        _executionId = executionId;
        _parentExecutionId = parentExecutionId;
    }

    /// <summary>
    /// Exposes the wrapped <see cref="DbConnection"/> to the
    /// sibling <see cref="AuditingDbCommand"/> in the same assembly, so the
    /// command's <c>DbConnection</c> setter can unwrap an
    /// <see cref="AuditingDbConnection"/> without reflecting into the
    /// private <c>_inner</c> field. Both classes are <c>internal sealed</c>
    /// in this assembly, so the accessor stays out of the public API and
    /// matches the precedent of preferring proper API surface
    /// over <see cref="System.Reflection"/>.
    /// </summary>
    internal DbConnection Inner => _inner;

    /// <inheritdoc />
    // ConnectionString is settable on DbConnection — forward both halves.
    public override string ConnectionString
    {
        // Some providers throw on get when the connection hasn't been opened
        // with a string set explicitly. The wrapper has no opinion — forward.
#pragma warning disable CS8765 // nullability of overridden member parameter — base setter accepts null in practice
        get => _inner.ConnectionString;
        set => _inner.ConnectionString = value;
#pragma warning restore CS8765
    }

    /// <inheritdoc />
    public override string Database => _inner.Database;

    /// <inheritdoc />
    public override string DataSource => _inner.DataSource;

    /// <inheritdoc />
    public override string ServerVersion => _inner.ServerVersion;

    /// <inheritdoc />
    public override ConnectionState State => _inner.State;

    /// <inheritdoc />
    public override void ChangeDatabase(string databaseName) => _inner.ChangeDatabase(databaseName);

    /// <inheritdoc />
    public override void Close() => _inner.Close();

    /// <inheritdoc />
    public override void Open() => _inner.Open();

    /// <inheritdoc />
    public override Task OpenAsync(CancellationToken cancellationToken) => _inner.OpenAsync(cancellationToken);

    /// <inheritdoc />
    protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel)
        => _inner.BeginTransaction(isolationLevel);

    /// <inheritdoc />
    protected override DbCommand CreateDbCommand()
    {
        var innerCmd = _inner.CreateCommand();
        // Hand the script an auditing wrapper. The wrapper preserves the
        // inner command's identity for parameters / type maps via delegation.
        return new AuditingDbCommand(
            innerCmd,
            _auditWriter,
            _connectionName,
            _siteId,
            _instanceName,
            _sourceScript,
            _logger,
            _executionId,
            // ParentExecutionId: the spawning execution's id,
            // threaded alongside _executionId. Null for non-routed runs.
            _parentExecutionId);
    }

    /// <inheritdoc />
    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            _inner.Dispose();
        }
        base.Dispose(disposing);
    }

    /// <inheritdoc />
    public override ValueTask DisposeAsync()
    {
        // DbConnection.DisposeAsync is virtual; calling base would run the
        // synchronous Dispose path. Forward to the inner connection
        // asynchronously and short-circuit the base.
        var task = _inner.DisposeAsync();
        GC.SuppressFinalize(this);
        return task;
    }
}
