📄 src/Integrations/HomeAssistant/HomeAssistantClient.cs
using System;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;

namespace MMirror.Integrations.HomeAssistant;

public sealed class HomeAssistantClient(HttpClient httpClient, IOptions<HomeAssistantOptions> options)
{
    public async Task<Temperatures> GetTemperatures(CancellationToken cancellationToken)
    {
        var states =
            await httpClient.GetFromJsonAsync(
                "/api/states",
                HomeAssistantJsonSerializerContext.Default.EntityStateArray,
                cancellationToken
            ) ?? throw new Exception("Received literal null from Home Assistant states endpoint.");

        return new Temperatures(
            FindTemperature(states, options.Value.InsideTemperatureEntityId),
            FindTemperature(states, options.Value.OutsideTemperatureEntityId)
        );
    }

    private static double FindTemperature(EntityState[] states, string entityId)
    {
        var state =
            states.FirstOrDefault(entry => entry.EntityId == entityId)?.State
            ?? throw new Exception($"Home Assistant state not found for entity '{entityId}'.");

        if (
            !double.TryParse(state, NumberStyles.Float, CultureInfo.InvariantCulture, out var temperature)
            || double.IsNaN(temperature)
            || double.IsInfinity(temperature)
        )
        {
            throw new Exception($"Home Assistant state '{state}' for entity '{entityId}' is not a valid temperature.");
        }

        return temperature;
    }
}

public sealed record Temperatures(double InsideCelsius, double OutsideCelsius);