using System.Diagnostics.CodeAnalysis;
using FrameProcessor.Configuration;
using FrameProcessor.Domain;
using Microsoft.Extensions.Options;

namespace FrameProcessor.Storage;

/// <summary>
/// Persists the latest processed PNG for each frame to <see cref="StorageOptions.ImageDirectory"/>.
/// Writes are atomic via a temp file + <see cref="File.Move(string, string, bool)"/> rename so the
/// fetch endpoint never observes a partial file (see SPEC.md §7.2, CLAUDE.md "Atomic writes only").
/// </summary>
public sealed class ImageStore
{
    private readonly string _directory;

    public ImageStore(IOptions<StorageOptions> options)
    {
        ArgumentNullException.ThrowIfNull(options);
        _directory = options.Value.ImageDirectory;
        Directory.CreateDirectory(_directory);
    }

    public async Task WriteAsync(MacAddress mac, ReadOnlyMemory<byte> bytes, CancellationToken cancellationToken)
    {
        var finalPath = GetPath(mac);
        var tmpPath = finalPath + ".tmp";

        await using (var stream = new FileStream(tmpPath, FileMode.Create, FileAccess.Write, FileShare.None))
        {
            await stream.WriteAsync(bytes, cancellationToken).ConfigureAwait(false);
            await stream.FlushAsync(cancellationToken).ConfigureAwait(false);
        }

        File.Move(tmpPath, finalPath, overwrite: true);
    }

    public bool TryGetPath(MacAddress mac, [NotNullWhen(true)] out string? path)
    {
        var candidate = GetPath(mac);
        if (File.Exists(candidate))
        {
            path = candidate;
            return true;
        }

        path = null;
        return false;
    }

    private string GetPath(MacAddress mac) => Path.Combine(_directory, $"{mac}.png");
}
