using System.IO.Ports;

namespace ConeCalorimeter.Services;

public sealed record SerialScaleOptions(
    string PortName,
    int BaudRate,
    Parity Parity,
    int DataBits,
    StopBits StopBits,
    byte UnitId,
    ushort RegisterAddress)
{
    public const string PortEnvironmentVariable = "CONE_SCALE_PORT";
    public const string BaudRateEnvironmentVariable = "CONE_SCALE_BAUD_RATE";
    public const string ParityEnvironmentVariable = "CONE_SCALE_PARITY";
    public const string DataBitsEnvironmentVariable = "CONE_SCALE_DATA_BITS";
    public const string StopBitsEnvironmentVariable = "CONE_SCALE_STOP_BITS";
    public const string UnitIdEnvironmentVariable = "CONE_SCALE_UNIT_ID";
    public const string RegisterEnvironmentVariable = "CONE_SCALE_REGISTER";

    public static SerialScaleOptions Default { get; } = new(
        "COM3",
        9600,
        Parity.None,
        8,
        StopBits.One,
        0,
        0);

    public static SerialScaleOptions FromEnvironment()
    {
        var portName = Environment.GetEnvironmentVariable(PortEnvironmentVariable);
        var baudRateText = Environment.GetEnvironmentVariable(BaudRateEnvironmentVariable);
        var parityText = Environment.GetEnvironmentVariable(ParityEnvironmentVariable);
        var dataBitsText = Environment.GetEnvironmentVariable(DataBitsEnvironmentVariable);
        var stopBitsText = Environment.GetEnvironmentVariable(StopBitsEnvironmentVariable);
        var unitIdText = Environment.GetEnvironmentVariable(UnitIdEnvironmentVariable);
        var registerText = Environment.GetEnvironmentVariable(RegisterEnvironmentVariable);

        var baudRate = int.TryParse(baudRateText, out var parsedBaudRate) && parsedBaudRate > 0
            ? parsedBaudRate
            : Default.BaudRate;
        var parity = ParseParity(parityText);
        var dataBits = int.TryParse(dataBitsText, out var parsedDataBits) && parsedDataBits is >= 5 and <= 8
            ? parsedDataBits
            : Default.DataBits;
        var stopBits = ParseStopBits(stopBitsText);
        var unitId = TryParseByte(unitIdText, out var parsedUnitId)
            ? parsedUnitId
            : Default.UnitId;
        var registerAddress = TryParseUInt16(registerText, out var parsedRegisterAddress)
            ? parsedRegisterAddress
            : Default.RegisterAddress;

        return new SerialScaleOptions(
            string.IsNullOrWhiteSpace(portName) ? Default.PortName : portName.Trim(),
            baudRate,
            parity,
            dataBits,
            stopBits,
            unitId,
            registerAddress);
    }

    private static Parity ParseParity(string? value)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            return Default.Parity;
        }

        return value.Trim().ToUpperInvariant() switch
        {
            "N" => Parity.None,
            "E" => Parity.Even,
            "O" => Parity.Odd,
            "M" => Parity.Mark,
            "S" => Parity.Space,
            _ => Enum.TryParse<Parity>(value, ignoreCase: true, out var parsedParity)
                ? parsedParity
                : Default.Parity
        };
    }

    private static StopBits ParseStopBits(string? value)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            return Default.StopBits;
        }

        return value.Trim() switch
        {
            "1" => StopBits.One,
            "1.5" => StopBits.OnePointFive,
            "2" => StopBits.Two,
            _ => Enum.TryParse<StopBits>(value, ignoreCase: true, out var parsedStopBits)
                && parsedStopBits != StopBits.None
                    ? parsedStopBits
                    : Default.StopBits
        };
    }

    private static bool TryParseByte(string? value, out byte result)
    {
        if (TryParseUInt16(value, out var parsed) && parsed <= byte.MaxValue)
        {
            result = (byte)parsed;
            return true;
        }

        result = 0;
        return false;
    }

    private static bool TryParseUInt16(string? value, out ushort result)
    {
        result = 0;

        if (string.IsNullOrWhiteSpace(value))
        {
            return false;
        }

        var trimmed = value.Trim();
        if (trimmed.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
        {
            return ushort.TryParse(
                trimmed[2..],
                System.Globalization.NumberStyles.HexNumber,
                provider: null,
                out result);
        }

        return ushort.TryParse(trimmed, out result);
    }
}
