using System.Diagnostics;

namespace splitter.probe;

public sealed class VideoRotationSampler
{
    private readonly FrameRotationDetector _detector = new FrameRotationDetector();

    public static int    RotationDetectorSampleCount  = 10;
    public static double RotationDetectorSampleLength = 0.15;  // seconds to decode per probe
    public static int    RotationDetectorFrameWidth   = 320;
    public static int    RotationDetectorFrameHeight  = 180;

    // --- Zero-allocation buffers ---
    private readonly byte[] _buffer;
    private readonly Mat _frameMat;

    public VideoRotationSampler(IDictionary<string, string>? overrides)
    {
        if (overrides != null)
        {
            if (overrides.TryGetValue("RotationDetectorSampleCount", out var s))
                RotationDetectorSampleCount = int.Parse(s);
            if (overrides.TryGetValue("RotationDetectorSampleLength", out s))
                RotationDetectorSampleLength = double.Parse(s);
            if (overrides.TryGetValue("RotationDetectorFrameWidth", out s))
                RotationDetectorFrameWidth = int.Parse(s);
            if (overrides.TryGetValue("RotationDetectorFrameHeight", out s))
                RotationDetectorFrameHeight = int.Parse(s);
        }

        var w = RotationDetectorFrameWidth;
        var h = RotationDetectorFrameHeight;

        _buffer    = new byte[w * h * 3];                  // raw BGR24 buffer
        _frameMat  = new Mat(h, w, MatType.CV_8UC3);       // wraps buffer
    }

    public async Task<int> DetectRotationAsync(
         string inputFile,
         double videoLengthSeconds,
         CancellationToken token)
    {
        if (videoLengthSeconds <= 0)
            return 0;

        var rotations = new List<int>();

        for (var i = 0; i < RotationDetectorSampleCount; i++)
        {
            var t = videoLengthSeconds * (i + 1) / (RotationDetectorSampleCount + 1);

            var frame = await DecodeSingleFrameAsync(
                inputFile,
                t,
                RotationDetectorSampleLength,
                RotationDetectorFrameWidth,
                RotationDetectorFrameHeight,
                token);

            if (frame != null && !frame.Empty())
            {
                var rot = _detector.GetRotation(frame);
                rotations.Add(rot);
            }
        }

        if (rotations.Count == 0)
            return 0;

        return Majority(rotations);
    }

    private static int Majority(List<int> values)
    {
        var counts = new Dictionary<int, int>();
        foreach (var v in values)
        {
            if (!counts.ContainsKey(v)) counts[v] = 0;
            counts[v]++;
        }

        var best = 0;
        var bestCount = 0;

        foreach (var kv in counts)
        {
            if (kv.Value > bestCount)
            {
                best = kv.Key;
                bestCount = kv.Value;
            }
        }

        return best;
    }

    private async Task<Mat?> DecodeSingleFrameAsync(
        string inputFile,
        double start,
        double length,
        int width,
        int height,
        CancellationToken token)
    {
        var p = StartFfmpegDecode(inputFile, start, length, rotate: null, plainText: false);

        var needed = _buffer.Length;
        var read = 0;

        using var stdout = p.StandardOutput.BaseStream;

        while (read < needed)
        {
            token.ThrowIfCancellationRequested();

            var r = await stdout.ReadAsync(_buffer, read, needed - read, token);
            if (r == 0)
                return null;
            read += r;
        }

        try { p.Kill(); } catch { }

        // Copy buffer → Mat (no new Mat)
        System.Runtime.InteropServices.Marshal.Copy(_buffer, 0, _frameMat.Data, _buffer.Length);

        return _frameMat;
    }

    private Process StartFfmpegDecode(
        string inputFile,
        double start,
        double length,
        int? rotate,
        bool plainText)
    {
        var ss = start.ToString("0.###", System.Globalization.CultureInfo.InvariantCulture);
        var t  = length.ToString("0.###", System.Globalization.CultureInfo.InvariantCulture);

        // FFmpeg does the resize + format conversion
        var args =
            $"-ss {ss} -t {t} -i \"{inputFile}\" " +
            "-an -sn " +
            $"-vf scale={RotationDetectorFrameWidth}:{RotationDetectorFrameHeight},format=bgr24 " +
            "-f rawvideo -";

        var psi = new ProcessStartInfo
        {
            FileName               = "ffmpeg",
            Arguments              = args,
            RedirectStandardOutput = true,
            RedirectStandardError  = true,
            UseShellExecute        = false,
            CreateNoWindow         = true
        };

        var p = new Process { StartInfo = psi };
        p.Start();

        // Optional stderr logging
        _ = Task.Run(() =>
        {
            try
            {
                string? line;
                while ((line = p.StandardError.ReadLine()) != null)
                    if (plainText)
                        Console.WriteLine($"[ffmpeg-decode] {line}");
            }
            catch { }
        });

        return p;
    }
}
