using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using PhotoboothUploader.Models;

namespace PhotoboothUploader.Services;

public sealed class PhotoboothConnectClient
{
    private const int MaxRetries = 2;
    private static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(10);
    private readonly HttpClient _httpClient;
    private readonly JsonSerializerOptions _jsonOptions = new()
    {
        PropertyNameCaseInsensitive = true,
    };

    public PhotoboothConnectClient(string baseUrl, string userAgent)
    {
        _httpClient = new HttpClient
        {
            BaseAddress = new Uri(baseUrl),
            Timeout = DefaultTimeout,
        };

        _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(userAgent);
    }

    public async Task<PhotoboothConnectResponse> RedeemAsync(string code, CancellationToken cancellationToken = default)
    {
        var request = new { code };

        for (var attempt = 0; attempt <= MaxRetries; attempt++)
        {
            try
            {
                using var response = await _httpClient.PostAsJsonAsync("/api/v1/photobooth/connect", request, cancellationToken);
                var payload = await ReadPayloadAsync(response, cancellationToken);

                if (response.IsSuccessStatusCode)
                {
                    return payload ?? Fail(response.ReasonPhrase ?? "Verbindung fehlgeschlagen.");
                }

                if (response.StatusCode is HttpStatusCode.UnprocessableEntity or HttpStatusCode.Conflict or HttpStatusCode.Unauthorized)
                {
                    return payload ?? Fail(response.ReasonPhrase ?? "Verbindung fehlgeschlagen.");
                }

                if (attempt < MaxRetries && IsTransientStatus(response.StatusCode))
                {
                    await Task.Delay(GetRetryDelay(attempt), cancellationToken);
                    continue;
                }

                return payload ?? Fail(response.ReasonPhrase ?? "Verbindung fehlgeschlagen.");
            }
            catch (TaskCanceledException) when (!cancellationToken.IsCancellationRequested)
            {
                if (attempt < MaxRetries)
                {
                    await Task.Delay(GetRetryDelay(attempt), cancellationToken);
                    continue;
                }

                return Fail("Zeitüberschreitung bei der Verbindung.");
            }
            catch (HttpRequestException)
            {
                if (attempt < MaxRetries)
                {
                    await Task.Delay(GetRetryDelay(attempt), cancellationToken);
                    continue;
                }

                return Fail("Netzwerkfehler. Bitte Verbindung prüfen.");
            }
            catch (JsonException)
            {
                return Fail("Serverantwort konnte nicht gelesen werden.");
            }
        }

        return Fail("Verbindung fehlgeschlagen.");
    }

    private async Task<PhotoboothConnectResponse?> ReadPayloadAsync(HttpResponseMessage response, CancellationToken cancellationToken)
    {
        if (response.Content.Headers.ContentLength == 0)
        {
            return null;
        }

        return await response.Content.ReadFromJsonAsync<PhotoboothConnectResponse>(_jsonOptions, cancellationToken);
    }

    private static bool IsTransientStatus(HttpStatusCode statusCode)
    {
        return statusCode is HttpStatusCode.RequestTimeout or HttpStatusCode.TooManyRequests
            or HttpStatusCode.BadGateway or HttpStatusCode.ServiceUnavailable or HttpStatusCode.GatewayTimeout
            or HttpStatusCode.InternalServerError;
    }

    private static TimeSpan GetRetryDelay(int attempt)
    {
        return TimeSpan.FromMilliseconds(500 * (attempt + 1));
    }

    private static PhotoboothConnectResponse Fail(string message)
    {
        return new PhotoboothConnectResponse
        {
            Message = message,
        };
    }
}
