using System.Net.WebSockets;
using System.Threading.Channels;
using Koldan.WebSocket.Sdk;
using NAudio.Wave;

var baseUrl = GetRequiredEnvironmentVariable("KOLDAN_BASE_URL");
var apiKey = GetRequiredEnvironmentVariable("KOLDAN_API_KEY");
var language = Environment.GetEnvironmentVariable("KOLDAN_LANGUAGE") ?? "he";
var allowInvalidTls = string.Equals(
    Environment.GetEnvironmentVariable("KOLDAN_ALLOW_INVALID_TLS"),
    "true",
    StringComparison.OrdinalIgnoreCase);

using var httpClient = CreateHttpClient(allowInvalidTls);
using var client = new KoldanStreamingClient(new KoldanStreamingClientOptions
{
    BaseUrl = new Uri(baseUrl),
    ApiKey = apiKey,
    HttpClient = httpClient,
    ClientWebSocketFactory = allowInvalidTls ? CreateUnsafeClientWebSocket : null
});

var model = Environment.GetEnvironmentVariable("KOLDAN_MODEL");
if (string.IsNullOrWhiteSpace(model))
{
    var models = await client.ListStreamingModelsAsync();
    model = models.FirstOrDefault()?.Id
        ?? throw new InvalidOperationException("No streaming-capable Koldan models are available for this API key.");
}

await using var session = await client.StartSessionAsync(new StreamingSessionOptions
{
    Model = model,
    Language = language,
    EnableEndpointDetection = true
});

using var stop = new CancellationTokenSource();
Console.CancelKeyPress += (_, eventArgs) =>
{
    eventArgs.Cancel = true;
    stop.Cancel();
};

var audioFrames = Channel.CreateUnbounded<byte[]>(new UnboundedChannelOptions
{
    SingleReader = true,
    SingleWriter = false
});

var reader = Task.Run(async () =>
{
    await foreach (var message in session.Messages.ReadAllAsync(stop.Token))
    {
        switch (message)
        {
            case SessionInfoMessage info:
                Console.WriteLine($"Session started: {info.SessionId} (model: {info.ResolvedModel})");
                break;
            case PartialResultMessage partial:
                Console.Write($"\rPartial: {partial.Transcript}   ");
                break;
            case FinalResultMessage final:
                Console.WriteLine();
                Console.WriteLine($"Final[{final.SegmentIndex}]: {final.Transcript}");
                break;
            case SessionCompletedMessage completed:
                Console.WriteLine($"Completed: {completed.TotalSegments} segments, {completed.TotalWords} words");
                return;
            case ErrorMessage error:
                throw new KoldanStreamingProtocolException($"{error.Code}: {error.Message}");
        }
    }
}, stop.Token);

var sender = Task.Run(async () =>
{
    await foreach (var frame in audioFrames.Reader.ReadAllAsync(stop.Token))
    {
        await session.SendAudioAsync(frame, stop.Token);
    }
}, stop.Token);

using var microphone = new WaveInEvent
{
    WaveFormat = new WaveFormat(16000, 16, 1),
    BufferMilliseconds = 100
};

microphone.DataAvailable += (_, args) =>
{
    var frame = new byte[args.BytesRecorded];
    Buffer.BlockCopy(args.Buffer, 0, frame, 0, args.BytesRecorded);
    audioFrames.Writer.TryWrite(frame);
};

microphone.RecordingStopped += (_, args) =>
{
    if (args.Exception != null)
    {
        audioFrames.Writer.TryComplete(args.Exception);
        return;
    }

    audioFrames.Writer.TryComplete();
};

Console.WriteLine($"Streaming microphone audio to Koldan model '{model}'. Press Enter to stop.");
microphone.StartRecording();
Console.ReadLine();
stop.CancelAfter(TimeSpan.FromSeconds(10));
microphone.StopRecording();

await sender;
await session.CompleteAsync(CancellationToken.None);
await reader;

static string GetRequiredEnvironmentVariable(string name)
{
    var value = Environment.GetEnvironmentVariable(name);
    if (string.IsNullOrWhiteSpace(value))
    {
        throw new InvalidOperationException($"Environment variable {name} is required.");
    }

    return value;
}

static HttpClient CreateHttpClient(bool allowInvalidTls)
{
    if (!allowInvalidTls)
    {
        return new HttpClient();
    }

    return new HttpClient(new HttpClientHandler
    {
        ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
    });
}

static ClientWebSocket CreateUnsafeClientWebSocket()
{
    var socket = new ClientWebSocket();
    socket.Options.RemoteCertificateValidationCallback = (_, _, _, _) => true;
    return socket;
}
