// Copyright 2019-2026 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Adapted from server/filestore.go (msgBlock struct and consumerFileStore struct)

using System.Threading.Channels;
using ZB.MOM.NatsNet.Server.Internal.DataStructures;

namespace ZB.MOM.NatsNet.Server;

// ---------------------------------------------------------------------------
// MessageBlock
// ---------------------------------------------------------------------------

/// <summary>
/// Represents a single on-disk message block file together with its
/// in-memory cache and index state.
/// Mirrors the <c>msgBlock</c> struct in filestore.go.
/// </summary>
internal sealed class MessageBlock
{
    // ------------------------------------------------------------------
    // Identity fields — first/last use volatile-style access in Go via
    // atomic.LoadUint64 on the embedded msgId structs.
    // We replicate those as plain fields; callers must acquire _mu before
    // reading/writing unless using the Interlocked helpers on Seq/Ts.
    // ------------------------------------------------------------------

    /// <summary>First message in this block (sequence + nanosecond timestamp).</summary>
    public MsgId First;

    /// <summary>Last message in this block (sequence + nanosecond timestamp).</summary>
    public MsgId Last;

    // ------------------------------------------------------------------
    // Lock
    // ------------------------------------------------------------------

    /// <summary>Guards all mutable fields in this block.</summary>
    public readonly ReaderWriterLockSlim Mu = new(LockRecursionPolicy.NoRecursion);

    // ------------------------------------------------------------------
    // Back-reference
    // ------------------------------------------------------------------

    /// <summary>Owning file store.</summary>
    public JetStreamFileStore? Fs { get; set; }

    // ------------------------------------------------------------------
    // File I/O
    // ------------------------------------------------------------------

    /// <summary>Path to the <c>.blk</c> message data file.</summary>
    public string Mfn { get; set; } = string.Empty;

    /// <summary>Open file stream for the block data file (null when closed/idle).</summary>
    public FileStream? Mfd { get; set; }

    /// <summary>Path to the per-block encryption key file.</summary>
    public string Kfn { get; set; } = string.Empty;

    // ------------------------------------------------------------------
    // Compression
    // ------------------------------------------------------------------

    /// <summary>Effective compression algorithm when the block was last loaded.</summary>
    public StoreCompression Cmp { get; set; }

    /// <summary>
    /// Last index write size in bytes; used to detect whether re-compressing
    /// the block would save meaningful space.
    /// </summary>
    public long Liwsz { get; set; }

    // ------------------------------------------------------------------
    // Block identity
    // ------------------------------------------------------------------

    /// <summary>Monotonically increasing block index number (used in file names).</summary>
    public uint Index { get; set; }

    // ------------------------------------------------------------------
    // Counters
    // ------------------------------------------------------------------

    /// <summary>User-visible byte count (excludes deleted-message bytes).</summary>
    public ulong Bytes { get; set; }

    /// <summary>
    /// Total raw byte count including deleted messages.
    /// Used to decide when to roll to a new block.
    /// </summary>
    public ulong RBytes { get; set; }

    /// <summary>Byte count captured at the last compaction (0 if never compacted).</summary>
    public ulong CBytes { get; set; }

    /// <summary>User-visible message count (excludes deleted messages).</summary>
    public ulong Msgs { get; set; }

    // ------------------------------------------------------------------
    // Per-subject state
    // ------------------------------------------------------------------

    /// <summary>
    /// Optional per-subject state tree for this block.
    /// Lazily populated and expired when idle.
    /// Mirrors <c>mb.fss</c> in filestore.go.
    /// </summary>
    public SubjectTree<SimpleState>? Fss { get; set; }

    // ------------------------------------------------------------------
    // Deleted-sequence tracking
    // ------------------------------------------------------------------

    /// <summary>
    /// Set of deleted sequence numbers within this block.
    /// Uses the AVL-backed <see cref="SequenceSet"/> to match Go's <c>avl.SequenceSet</c>.
    /// </summary>
    public SequenceSet Dmap { get; set; } = new();

    // ------------------------------------------------------------------
    // Timestamps (nanosecond Unix times, matches Go int64)
    // ------------------------------------------------------------------

    /// <summary>Nanosecond timestamp of the last write to this block.</summary>
    public long Lwts { get; set; }

    /// <summary>Nanosecond timestamp of the last load (cache fill) of this block.</summary>
    public long Llts { get; set; }

    /// <summary>Nanosecond timestamp of the last read from this block.</summary>
    public long Lrts { get; set; }

    /// <summary>Nanosecond timestamp of the last subject-state (fss) access.</summary>
    public long Lsts { get; set; }

    /// <summary>Last sequence that was looked up; used to detect linear scans.</summary>
    public ulong Llseq { get; set; }

    // ------------------------------------------------------------------
    // Cache
    // ------------------------------------------------------------------

    /// <summary>
    /// Active in-memory cache. May be null when evicted.
    /// Mirrors <c>mb.cache</c>; the elastic-pointer field (<c>mb.ecache</c>) is
    /// not ported — a plain nullable field is sufficient here.
    /// </summary>
    public Cache? CacheData { get; set; }

    /// <summary>Number of times the cache has been (re)loaded from disk.</summary>
    public ulong Cloads { get; set; }

    /// <summary>Cache buffer expiration duration for this block.</summary>
    public TimeSpan Cexp { get; set; }

    /// <summary>Per-block subject-state (fss) expiration duration.</summary>
    public TimeSpan Fexp { get; set; }

    /// <summary>Timer used to expire the cache buffer when idle.</summary>
    public Timer? Ctmr { get; set; }

    // ------------------------------------------------------------------
    // State flags
    // ------------------------------------------------------------------

    /// <summary>Whether the in-memory cache is currently populated.</summary>
    public bool HaveCache => CacheData != null;

    /// <summary>Whether this block has been closed and must not be written to.</summary>
    public bool Closed { get; set; }

    /// <summary>Whether this block has unflushed data that must be synced to disk.</summary>
    public bool NeedSync { get; set; }

    /// <summary>When true every write is immediately synced (SyncAlways mode).</summary>
    public bool SyncAlways { get; set; }

    /// <summary>When true compaction is suppressed for this block.</summary>
    public bool NoCompact { get; set; }

    /// <summary>
    /// When true the block's messages are not tracked in the per-subject index.
    /// Used for blocks that only contain tombstone or deleted markers.
    /// </summary>
    public bool NoTrack { get; set; }

    /// <summary>Whether a background flusher goroutine equivalent is running.</summary>
    public bool Flusher { get; set; }

    /// <summary>Whether a cache-load is currently in progress.</summary>
    public bool Loading { get; set; }

    // ------------------------------------------------------------------
    // Write error
    // ------------------------------------------------------------------

    /// <summary>Captured write error; non-null means the block is in a bad state.</summary>
    public Exception? Werr { get; set; }

    // ------------------------------------------------------------------
    // TTL / scheduling counters
    // ------------------------------------------------------------------

    /// <summary>Number of messages in this block that have a TTL set.</summary>
    public ulong Ttls { get; set; }

    /// <summary>Number of messages in this block that have a schedule set.</summary>
    public ulong Schedules { get; set; }

    // ------------------------------------------------------------------
    // Channels (equivalent to Go chan struct{})
    // ------------------------------------------------------------------

    /// <summary>Flush-request channel: signals the flusher to run.</summary>
    public Channel<byte>? Fch { get; set; }

    /// <summary>Quit channel: closing signals background goroutine equivalents to stop.</summary>
    public Channel<byte>? Qch { get; set; }

    // ------------------------------------------------------------------
    // Checksum
    // ------------------------------------------------------------------

    /// <summary>
    /// Last-check hash: 8-byte rolling checksum of the last validated record.
    /// Mirrors <c>mb.lchk [8]byte</c>.
    /// </summary>
    public byte[] Lchk { get; set; } = new byte[8];

    // ------------------------------------------------------------------
    // Test hook
    // ------------------------------------------------------------------

    /// <summary>When true, simulates a write failure. Used by unit tests only.</summary>
    public bool MockWriteErr { get; set; }
}

// ---------------------------------------------------------------------------
// ConsumerFileStore
// ---------------------------------------------------------------------------

/// <summary>
/// File-backed implementation of <see cref="IConsumerStore"/>.
/// Persists consumer delivery and ack state to a directory under the stream's
/// <c>obs/</c> subdirectory.
/// Mirrors the <c>consumerFileStore</c> struct in filestore.go.
/// </summary>
public sealed class ConsumerFileStore : IConsumerStore
{
    // ------------------------------------------------------------------
    // Fields — mirrors consumerFileStore struct
    // ------------------------------------------------------------------

    private readonly object _mu = new();

    /// <summary>Back-reference to the owning file store.</summary>
    private readonly JetStreamFileStore _fs;

    /// <summary>Consumer metadata (name, created time, config).</summary>
    private FileConsumerInfo _cfg;

    /// <summary>Durable consumer name.</summary>
    private readonly string _name;

    /// <summary>Path to the consumer's state directory.</summary>
    private readonly string _odir;

    /// <summary>Path to the consumer state file (<c>o.dat</c>).</summary>
    private readonly string _ifn;

    /// <summary>Consumer delivery/ack state.</summary>
    private ConsumerState _state = new();

    /// <summary>Flush-request channel.</summary>
    private Channel<byte>? _fch;

    /// <summary>Quit channel.</summary>
    private Channel<byte>? _qch;

    /// <summary>Whether a background flusher is running.</summary>
    private bool _flusher;

    /// <summary>Whether a write is currently in progress.</summary>
    private bool _writing;

    /// <summary>Whether the state is dirty (pending flush).</summary>
    private bool _dirty;

    /// <summary>Whether this consumer store is closed.</summary>
    private bool _closed;

    // ------------------------------------------------------------------
    // Constructor
    // ------------------------------------------------------------------

    /// <summary>
    /// Creates a new file-backed consumer store.
    /// </summary>
    public ConsumerFileStore(JetStreamFileStore fs, FileConsumerInfo cfg, string name, string odir)
    {
        _fs = fs;
        _cfg = cfg;
        _name = name;
        _odir = odir;
        _ifn = Path.Combine(odir, FileStoreDefaults.ConsumerState);
    }

    // ------------------------------------------------------------------
    // IConsumerStore — all methods stubbed
    // ------------------------------------------------------------------

    /// <inheritdoc/>
    public void SetStarting(ulong sseq)
        => throw new NotImplementedException("TODO: session 18 — filestore ConsumerFileStore.SetStarting");

    /// <inheritdoc/>
    public void UpdateStarting(ulong sseq)
        => throw new NotImplementedException("TODO: session 18 — filestore ConsumerFileStore.UpdateStarting");

    /// <inheritdoc/>
    public void Reset(ulong sseq)
        => throw new NotImplementedException("TODO: session 18 — filestore ConsumerFileStore.Reset");

    /// <inheritdoc/>
    public bool HasState()
        => throw new NotImplementedException("TODO: session 18 — filestore ConsumerFileStore.HasState");

    /// <inheritdoc/>
    public void UpdateDelivered(ulong dseq, ulong sseq, ulong dc, long ts)
        => throw new NotImplementedException("TODO: session 18 — filestore ConsumerFileStore.UpdateDelivered");

    /// <inheritdoc/>
    public void UpdateAcks(ulong dseq, ulong sseq)
        => throw new NotImplementedException("TODO: session 18 — filestore ConsumerFileStore.UpdateAcks");

    /// <inheritdoc/>
    public void UpdateConfig(ConsumerConfig cfg)
        => throw new NotImplementedException("TODO: session 18 — filestore ConsumerFileStore.UpdateConfig");

    /// <inheritdoc/>
    public void Update(ConsumerState state)
        => throw new NotImplementedException("TODO: session 18 — filestore ConsumerFileStore.Update");

    /// <inheritdoc/>
    public (ConsumerState? State, Exception? Error) State()
        => throw new NotImplementedException("TODO: session 18 — filestore ConsumerFileStore.State");

    /// <inheritdoc/>
    public (ConsumerState? State, Exception? Error) BorrowState()
        => throw new NotImplementedException("TODO: session 18 — filestore ConsumerFileStore.BorrowState");

    /// <inheritdoc/>
    public byte[] EncodedState()
        => throw new NotImplementedException("TODO: session 18 — filestore ConsumerFileStore.EncodedState");

    /// <inheritdoc/>
    public StorageType Type() => StorageType.FileStorage;

    /// <inheritdoc/>
    public void Stop()
        => throw new NotImplementedException("TODO: session 18 — filestore ConsumerFileStore.Stop");

    /// <inheritdoc/>
    public void Delete()
        => throw new NotImplementedException("TODO: session 18 — filestore ConsumerFileStore.Delete");

    /// <inheritdoc/>
    public void StreamDelete()
        => throw new NotImplementedException("TODO: session 18 — filestore ConsumerFileStore.StreamDelete");
}
