using System;
using System.Collections.Concurrent;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
using PhotoboothUploader.Models;

namespace PhotoboothUploader.Services;

public sealed class UploadService
{
    private readonly Channel<string> _queue = Channel.CreateUnbounded<string>();
    private readonly ConcurrentDictionary<string, byte> _pending = new(StringComparer.OrdinalIgnoreCase);
    private CancellationTokenSource? _cts;

    public void Start(
        PhotoboothSettings settings,
        Action<string> onQueued,
        Action<string> onUploading,
        Action<string> onSuccess,
        Action<string> onFailure)
    {
        Stop();

        _cts = new CancellationTokenSource();
        _ = Task.Run(() => WorkerAsync(settings, onQueued, onUploading, onSuccess, onFailure, _cts.Token));
    }

    public void Stop()
    {
        _cts?.Cancel();
        _cts = null;
        _pending.Clear();
    }

    public void Enqueue(string path, Action<string> onQueued)
    {
        if (!_pending.TryAdd(path, 0))
        {
            return;
        }

        _queue.Writer.TryWrite(path);
        onQueued(path);
    }

    private async Task WorkerAsync(
        PhotoboothSettings settings,
        Action<string> onQueued,
        Action<string> onUploading,
        Action<string> onSuccess,
        Action<string> onFailure,
        CancellationToken token)
    {
        if (string.IsNullOrWhiteSpace(settings.UploadUrl))
        {
            return;
        }

        using var client = new HttpClient();

        while (await _queue.Reader.WaitToReadAsync(token))
        {
            while (_queue.Reader.TryRead(out var path))
            {
                try
                {
                    onUploading(path);
                    await WaitForFileReadyAsync(path, token);
                    await UploadAsync(client, settings, path, token);
                    onSuccess(path);
                }
                catch (OperationCanceledException)
                {
                    return;
                }
                catch
                {
                    onFailure(path);
                }
                finally
                {
                    _pending.TryRemove(path, out _);
                }
            }
        }
    }

    private static async Task WaitForFileReadyAsync(string path, CancellationToken token)
    {
        var lastSize = -1L;

        for (var attempts = 0; attempts < 10; attempts++)
        {
            token.ThrowIfCancellationRequested();

            if (!File.Exists(path))
            {
                await Task.Delay(500, token);
                continue;
            }

            var info = new FileInfo(path);
            var size = info.Length;

            if (size > 0 && size == lastSize)
            {
                return;
            }

            lastSize = size;
            await Task.Delay(700, token);
        }
    }

    private static async Task UploadAsync(HttpClient client, PhotoboothSettings settings, string path, CancellationToken token)
    {
        if (!File.Exists(path))
        {
            return;
        }

        using var content = new MultipartFormDataContent();

        if (!string.IsNullOrWhiteSpace(settings.Username))
        {
            content.Add(new StringContent(settings.Username), "username");
        }

        if (!string.IsNullOrWhiteSpace(settings.Password))
        {
            content.Add(new StringContent(settings.Password), "password");
        }

        if (!string.IsNullOrWhiteSpace(settings.ResponseFormat))
        {
            content.Add(new StringContent(settings.ResponseFormat), "format");
        }

        var stream = File.OpenRead(path);
        var fileContent = new StreamContent(stream);
        fileContent.Headers.ContentType = new MediaTypeHeaderValue(ResolveContentType(path));
        content.Add(fileContent, "media", Path.GetFileName(path));

        var response = await client.PostAsync(settings.UploadUrl, content, token);
        response.EnsureSuccessStatusCode();
    }

    private static string ResolveContentType(string path)
    {
        return Path.GetExtension(path)?.ToLowerInvariant() switch
        {
            ".png" => "image/png",
            ".webp" => "image/webp",
            _ => "image/jpeg",
        };
    }
}
