using System.Diagnostics;
using System.Net.WebSockets;
using System.Text;
using System.Management;
using NAudio.CoreAudioApi;
using NAudio.Wave;
// ReSharper disable PossibleInvalidCastExceptionInForeachLoop

namespace _2dxAutoClip;
#pragma warning disable CA1416

class Program
{
    private static readonly string WebsocketAddress = "localhost";
    private static Process? _ffmpegProcess;
    private static string _ffmpegFolderPath = GetDefaultVideosFolderPath();
    private static WasapiCapture? _waveSource;
    private static WaveFileWriter _writer = null!;
    private static string _audioFilePath = null!;
    private static string _videoFilePath = null!;
    private static string _resolution = "1920x1080"; // Default resolution
    private static int _framerate = 60; // Default framerate
    private static float _crf = 23; // Default CRF value
    private static string _gameProcessName = "spice64"; // Default game process name
    
    private static async Task Main(string[] args)
    {
        // Load settings from prop.txt
        LoadSettingsFromPropFile();

        var gameProcesses = Process.GetProcessesByName(_gameProcessName);
        if (gameProcesses.Length > 0)
        {
            Console.WriteLine($"Found {_gameProcessName}, Attempting connection to TickerHookWS...");
            await TryConnectWebSocket();
        }
        else
        {
            Console.WriteLine($"Unable to find {_gameProcessName}. Is the game running and TickerHook enabled?");
        }
    }
    
    private static void LoadSettingsFromPropFile()
    {
        const string filePath = "prop.txt";
        if (File.Exists(filePath))
        {
            var fileContent = File.ReadAllText(filePath);
            var lines = fileContent.Split('\n');
            foreach (var line in lines)
            {
                if (line.StartsWith("path:"))
                {
                    var path = line["path:".Length..].Trim();
                    if (Directory.Exists(path))
                    {
                        _ffmpegFolderPath = path; // Recording path
                        
                    }
                    else
                    {
                        Console.WriteLine($"The path specified in {filePath} does not exist. Using default recording path.");
                        _ffmpegFolderPath = GetDefaultVideosFolderPath();
                    }
                }
                if (line.StartsWith("resolution:"))
                {
                    _resolution = line["resolution:".Length..].Trim();
                    Console.WriteLine($"Custom Resolution: {_resolution}");
                }
                if (line.StartsWith("framerate:"))
                {
                    _framerate = int.Parse(line["framerate:".Length..].Trim());
                    Console.WriteLine($"Custom framerate: {_framerate}");
                }
                if (line.StartsWith("crf:"))
                {
                    _crf = float.Parse(line["crf:".Length..].Trim());
                    Console.WriteLine($"custom crf: {_crf}");
                }
                if (line.StartsWith("game_process_name:")) 
                {
                    _gameProcessName = line["game_process_name:".Length..].Trim();
                    Console.WriteLine($"custom process name: {_gameProcessName}");
                }
            }
        }
        else
        {
            Console.WriteLine($"The file {filePath} does not exist. Using default values.");
            _ffmpegFolderPath = GetDefaultVideosFolderPath();
        }
    }

    private static string GetDefaultVideosFolderPath()
    {
        var userName = Environment.UserName;
        return Path.Combine($@"C:\Users\{userName}", "Videos");
    }

    private static async Task TryConnectWebSocket()
    {
        const int maxRetries = 5;
        var attempt = 0;
        while (attempt < maxRetries)
        {
            Console.WriteLine($"Attempt {attempt + 1} of {maxRetries} to connect...");
            var success = await ConnectWebSocket();
            if (success) break;
            attempt++;
            if (attempt < maxRetries)
            {
                Console.WriteLine($"Retrying in 10 seconds... {maxRetries - attempt} attempts remaining.");
                await Task.Delay(10000);
            }
        }
        if (attempt == maxRetries)
        {
            Console.WriteLine("Failed to connect after 5 attempts.");
        }
    }

    private static async Task<bool> ConnectWebSocket()
    {
        var tickerUri = new Uri($"ws://{WebsocketAddress}:10573");
        var reconnecting = false;
        var lastMessage = string.Empty;
        var consecutiveMessageCount = 0;
        var isRecording = false;
        var currentSongName = string.Empty;
        var shouldCheckForMusicSelect = false;

        using var clientWebSocket = new ClientWebSocket();
        try
        {
            await clientWebSocket.ConnectAsync(tickerUri, CancellationToken.None);
            Console.WriteLine("Connected to TickerHook WebSocket.");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error connecting to TickerHook WebSocket: {ex.Message}");
            return false;
        }

        var buffer = new byte[1024];
        while (clientWebSocket.State == WebSocketState.Open)
        {
            WebSocketReceiveResult result;
            try
            {
                result = await clientWebSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error receiving message: {ex.Message}");
                reconnecting = true;
                break;
            }

            var message = Encoding.UTF8.GetString(buffer, 0, result.Count).Trim().ToUpper();
            Console.WriteLine($"Received message: {message}");

            if (message == lastMessage && !message.Contains("SELECT FROM"))
            {
                consecutiveMessageCount++;
            }
            else
            {
                consecutiveMessageCount = 1;
                lastMessage = message;
            }

            if (consecutiveMessageCount >= 2 && !message.Contains("SELECT FROM") && !isRecording)
            {
                currentSongName = message;
                Console.WriteLine("Starting recording...");
                StartRecording(currentSongName);
                isRecording = true;
            }

            if (isRecording)
            {
                if (shouldCheckForMusicSelect && message.Contains("MUSIC SELECT!!"))
                {
                    Console.WriteLine("Stopping recording...");
                    StopRecording(currentSongName);
                    isRecording = false;
                    shouldCheckForMusicSelect = false;
                }
                else if (message.EndsWith("CLEAR!") || message.EndsWith("FAILED.."))
                {
                    shouldCheckForMusicSelect = true;
                }
            }
        }

        return !reconnecting;
    }

    private static void StartRecording(string songName)
    {
        Task.Run(() => StartAudioRecording(songName));
        StartFfmpegRecording(songName);
    }

    private static void StartAudioRecording(string songName)
    {
        try
        {
            var date = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
            _audioFilePath = $"{_ffmpegFolderPath}\\{songName}_{date}.wav";
            Directory.CreateDirectory(Path.GetDirectoryName(_audioFilePath)!);
            _waveSource = new WasapiLoopbackCapture();
            _writer = new WaveFileWriter(_audioFilePath, _waveSource.WaveFormat);
            _waveSource.DataAvailable += (sender, args) =>
            {
                _writer.Write(args.Buffer, 0, args.BytesRecorded);
            };
            _waveSource.RecordingStopped += (sender, args) =>
            {
                _writer.Dispose();
                _waveSource.Dispose();
            };
            _waveSource.StartRecording();
            Console.WriteLine("WASAPI Audio recording started.");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error starting audio recording: {ex.Message}");
        }
    }

    private static void StartFfmpegRecording(string songName)
{
    var encoder = GetHardwareEncoder();
    
    var date = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
    _videoFilePath = $"{_ffmpegFolderPath}\\{songName}_{date}.mkv";
    var ffmpegArguments = $"-framerate {_framerate} " +
                          $"-filter_complex \"ddagrab=framerate={_framerate},hwdownload,format=bgra\" " +
                          $"{encoder} -crf {_crf} -video_size {_resolution} -movflags +faststart -y \"{_videoFilePath}\"";
    _ffmpegProcess = new Process
    {
        StartInfo = new ProcessStartInfo
        {
            FileName = "ffmpeg",
            Arguments = ffmpegArguments,
            UseShellExecute = false,
            RedirectStandardError = true,
            CreateNoWindow = true
        }
    };
    _ffmpegProcess.Start();

    Console.WriteLine("FFmpeg recording started.");
}

private static string GetHardwareEncoder()
{
    var graphicsCard = GetGraphicsCard();
    var encoder = "-c:v libx264";

    if (graphicsCard.Contains("NVIDIA"))
    {
        encoder = "-c:v h264_nvenc"; 
        Console.WriteLine("Using NVIDIA hardware encoding (h264_nvenc).");
    }
    else if (graphicsCard.Contains("AMD"))
    {
        encoder = "-c:v h264_amf";
        Console.WriteLine("Using AMD hardware encoding (h264_amf).");
    }
    else if (graphicsCard.Contains("INTEL"))
    {
        encoder = "-c:v h264_qsv";
        Console.WriteLine("Using Intel hardware encoding (h264_qsv).");
    }
    else
    {
        Console.WriteLine("No recognized hardware encoder found, using CPU (libx264).");
    }

    return encoder;
}

private static string GetGraphicsCard()
{
    var searcher = new ManagementObjectSearcher("SELECT * FROM Win32_DisplayConfiguration");
    var graphicsCard = "";

    foreach (ManagementObject obj in searcher.Get())
    {
        foreach (var prop in obj.Properties)
        {
            if (prop.Name == "Description" && prop.Value != null)
            {
                graphicsCard = prop.Value.ToString().ToUpper();
                break;
            }
        }
    }

    return graphicsCard;
}

    private static void StopRecording(string songName)
    {
        StopAudioRecording();
        StopFfmpegRecording();
        CombineAudioAndVideo(_videoFilePath, _audioFilePath, songName);
    }

    private static void StopAudioRecording()
    {
        _waveSource?.StopRecording();
        Console.WriteLine("WASAPI Audio recording stopped.");
    }

    private static void StopFfmpegRecording()
    {
        if (_ffmpegProcess != null && !_ffmpegProcess.HasExited)
        {
            _ffmpegProcess.Kill();
            _ffmpegProcess.WaitForExit();
            _ffmpegProcess = null!;
            Console.WriteLine("FFmpeg recording stopped.");
        }
    }
    private static void CombineAudioAndVideo(string videoFilePath, string audioFilePath, string songName)
    {
        var combinedOutputFilePath = $"{_ffmpegFolderPath}\\{songName}_combined_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.mp4";
        var ffmpegArgs =
            $"-y -i \"{videoFilePath}\" -i \"{audioFilePath}\" -c:v copy -c:a aac -strict experimental -shortest -hwaccel_output_format d3d11 \"{combinedOutputFilePath}\"";

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

        using var process = Process.Start(processInfo);
        if (process == null)
        {
            Console.WriteLine("FFmpeg failed to start.");
            return;
        }

        var error = process.StandardError.ReadToEnd();

        process.WaitForExit();

        Console.WriteLine("FFmpeg Error: " + error);

        Console.WriteLine(File.Exists(combinedOutputFilePath)
            ? "Audio and video have been successfully combined."
            : "Failed to combine audio and video. Check the logs for errors.");
        File.Delete(videoFilePath);
        File.Delete(audioFilePath);
    }
}