using DeepDrftContent.FileDatabase.Models;

namespace DeepDrftContent.Processors;

/// <summary>
/// Extracts metadata from a FLAC file and wraps its <b>unmodified</b> bytes in an
/// <see cref="AudioBinary"/> tagged <c>.flac</c>. No transcoding — the vault stores the original
/// stream; duration and average bitrate come from the mandatory STREAMINFO metadata block.
/// </summary>
public class FlacAudioProcessor
{
    private const double FallbackDuration = 180.0;
    private const int FallbackBitrate = 1411;

    public async Task<AudioBinary?> ProcessFlacFileAsync(string filePath)
    {
        if (!File.Exists(filePath))
        {
            throw new FileNotFoundException($"FLAC file not found: {filePath}");
        }

        if (!Path.GetExtension(filePath).Equals(".flac", StringComparison.OrdinalIgnoreCase))
        {
            throw new ArgumentException("File must be a FLAC file", nameof(filePath));
        }

        var buffer = await File.ReadAllBytesAsync(filePath);
        var meta = ExtractFlacMetadata(buffer);

        var parameters = new AudioBinaryParams(
            Buffer: buffer,
            Size: buffer.Length,
            Extension: ".flac",
            Duration: meta.Duration,
            Bitrate: meta.Bitrate);

        return new AudioBinary(parameters);
    }

    /// <summary>
    /// Validates the <c>fLaC</c> magic and the leading STREAMINFO block, then computes duration from
    /// total-samples / sample-rate and average bitrate from file size. On any parse failure, logs a
    /// warning and returns synthetic defaults — never throws.
    /// </summary>
    private static FlacMetadata ExtractFlacMetadata(byte[] buffer)
    {
        try
        {
            // Magic (4) + metadata block header (4) + STREAMINFO data (34) = 42 bytes minimum.
            if (buffer.Length < 42)
            {
                throw new InvalidDataException("File too short for FLAC STREAMINFO");
            }

            if (buffer[0] != 'f' || buffer[1] != 'L' || buffer[2] != 'a' || buffer[3] != 'C')
            {
                throw new InvalidDataException("Invalid fLaC magic");
            }

            // Metadata block header at offset 4: bits 6-0 of byte 0 are the block type (0 = STREAMINFO).
            var blockType = buffer[4] & 0x7F;
            if (blockType != 0)
            {
                throw new InvalidDataException($"First metadata block is not STREAMINFO (type {blockType})");
            }

            // STREAMINFO data begins at offset 8. Layout (bit-packed, big-endian):
            //   bytes 10-12 + top nibble of 13: sample rate (20 bits)
            //   bits 3-1 of byte 12: channels - 1
            //   bit 0 of byte 12 + top 4 bits of byte 13: bits per sample - 1
            //   low nibble of byte 13 + bytes 14-17: total samples (36 bits)
            var d = 8;
            var sampleRate = (buffer[d + 10] << 12) | (buffer[d + 11] << 4) | (buffer[d + 12] >> 4);
            var totalSamples = ((long)(buffer[d + 13] & 0x0F) << 32)
                | ((long)buffer[d + 14] << 24)
                | ((long)buffer[d + 15] << 16)
                | ((long)buffer[d + 16] << 8)
                | buffer[d + 17];

            if (sampleRate <= 0)
            {
                throw new InvalidDataException("Invalid FLAC sample rate");
            }

            var duration = (double)totalSamples / sampleRate;
            var bitrate = duration > 0
                ? (int)(buffer.LongLength * 8L / (duration * 1000))
                : FallbackBitrate;

            return new FlacMetadata { Duration = duration, Bitrate = bitrate };
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Warning: FLAC parsing failed, using defaults: {ex.Message}");
            return new FlacMetadata { Duration = FallbackDuration, Bitrate = FallbackBitrate };
        }
    }

    private sealed class FlacMetadata
    {
        public double Duration { get; init; }
        public int Bitrate { get; init; }
    }
}
