#nullable enable

using System.Collections.ObjectModel;
using System.Data;

namespace MMoneyCoderSharp.Data;

public enum OperatorCatalogMutationKind
{
    CreateTheme,
    ReplaceTheme,
    DeleteTheme,
    CreateExpert,
    ReplaceExpert,
    DeleteExpert
}

public enum OperatorCatalogDbCommandKind
{
    InsertTheme,
    UpdateTheme,
    DeleteThemeItems,
    InsertThemeItem,
    DeleteTheme,
    InsertExpert,
    UpdateExpert,
    DeleteRecommendations,
    InsertRecommendation,
    DeleteExpert
}

/// <summary>
/// The mutation executor must evaluate this expectation immediately after the
/// command, before it executes the next command or commits the transaction.
/// </summary>
public enum OperatorCatalogAffectedRowsRule
{
    AnyNonNegative,
    ExactlyOne
}

/// <summary>
/// One bound input value in the closed theme/expert mutation command set.
/// Values are deliberately omitted from diagnostics.
/// </summary>
public sealed class OperatorCatalogDbParameter
{
    internal OperatorCatalogDbParameter(string name, object? value, DbType dbType)
    {
        if (!DataQueryParameter.IsValidName(name) || !Enum.IsDefined(dbType))
        {
            throw new ArgumentException("An operator-catalog parameter is invalid.");
        }

        Name = name;
        Value = value;
        DbType = dbType;
    }

    public string Name { get; }

    public object? Value { get; }

    public DbType DbType { get; }

    public override string ToString() => "Operator-catalog database parameter";
}

/// <summary>
/// A command from a closed, provider-specific SQL set. Arbitrary SQL cannot be
/// supplied by WebView or application callers because construction is internal
/// to the Core assembly.
/// </summary>
public sealed class OperatorCatalogDbCommand
{
    internal OperatorCatalogDbCommand(
        OperatorCatalogDbCommandKind kind,
        string sql,
        OperatorCatalogAffectedRowsRule affectedRowsRule,
        IEnumerable<OperatorCatalogDbParameter> parameters)
    {
        Kind = kind;
        Sql = sql;
        AffectedRowsRule = affectedRowsRule;
        Parameters = new ReadOnlyCollection<OperatorCatalogDbParameter>(parameters.ToArray());
    }

    public OperatorCatalogDbCommandKind Kind { get; }

    public string Sql { get; }

    public OperatorCatalogAffectedRowsRule AffectedRowsRule { get; }

    public IReadOnlyList<OperatorCatalogDbParameter> Parameters { get; }

    public override string ToString() => $"Operator-catalog {Kind} command";
}

/// <summary>
/// An ordered mutation that must execute exactly once on one connection under
/// one serializable transaction. Executors must not retry any command or batch.
/// </summary>
public sealed class OperatorCatalogDbTransaction
{
    internal OperatorCatalogDbTransaction(
        Guid operationId,
        OperatorCatalogMutationKind kind,
        DataSourceKind source,
        string identityCode,
        IEnumerable<OperatorCatalogDbCommand> commands)
    {
        OperationId = operationId;
        Kind = kind;
        Source = source;
        IdentityCode = identityCode;
        Commands = new ReadOnlyCollection<OperatorCatalogDbCommand>(commands.ToArray());
    }

    public Guid OperationId { get; }

    public OperatorCatalogMutationKind Kind { get; }

    public DataSourceKind Source { get; }

    public string IdentityCode { get; }

    public IReadOnlyList<OperatorCatalogDbCommand> Commands { get; }

    public override string ToString() => $"Operator-catalog {Kind} transaction";
}

public sealed record OperatorCatalogDbTransactionResult(
    Guid OperationId,
    IReadOnlyList<int> AffectedRows,
    DateTimeOffset CommittedAt);

public sealed record OperatorCatalogMutationReceipt(
    Guid OperationId,
    OperatorCatalogMutationKind Kind,
    DateTimeOffset CommittedAt);

/// <summary>
/// Infrastructure seam for state-changing theme/expert work. Returning means
/// commit completed. Implementations must execute once, check every affected-row
/// rule before continuing, and never apply a transient retry policy.
/// </summary>
public interface IOperatorCatalogMutationExecutor
{
    Task<OperatorCatalogDbTransactionResult> ExecuteTransactionAsync(
        OperatorCatalogDbTransaction transaction,
        CancellationToken cancellationToken = default);
}

public class OperatorCatalogMutationException : Exception
{
    public OperatorCatalogMutationException(
        string message,
        bool outcomeUnknown,
        Exception? innerException = null)
        : base(message, innerException)
    {
        OutcomeUnknown = outcomeUnknown;
    }

    /// <summary>
    /// True means the operator must reconcile the database manually. The
    /// application must never retry that operation automatically.
    /// </summary>
    public bool OutcomeUnknown { get; }
}

/// <summary>
/// A serializable transaction was rolled back because its bound identity,
/// uniqueness precondition, or affected-row expectation no longer matched.
/// The caller must refresh and make a new explicit edit decision.
/// </summary>
public sealed class OperatorCatalogConflictException : OperatorCatalogMutationException
{
    public OperatorCatalogConflictException(
        OperatorCatalogMutationKind kind,
        OperatorCatalogDbCommandKind commandKind,
        Exception? innerException = null)
        : base(
            $"The {kind} operation conflicted with current catalog data and was rolled back; refresh before trying a new operation.",
            outcomeUnknown: false,
            innerException)
    {
        Kind = kind;
        CommandKind = commandKind;
    }

    public OperatorCatalogMutationKind Kind { get; }

    public OperatorCatalogDbCommandKind CommandKind { get; }
}
