/*
 * Copyright (C) 2025 Emeric Poupon
 *
 * This file is part of LMS.
 *
 * LMS is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * LMS is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with LMS.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <variant>

#include <gtest/gtest.h>

#include "core/Utils.hpp"
#include "core/media/Codec.hpp"
#include "core/media/Container.hpp"

#include "endpoints/transcoding/TranscodeDecision.hpp"
#include "payloads/ClientInfo.hpp"

namespace lms::api::subsonic
{
    namespace detail
    {
        std::ostream& operator<<(std::ostream& os, const detail::TranscodeDecisionResult& result)
        {
            std::visit(core::utils::overloads{
                           [&](const detail::DirectPlayResult&) { os << "direct play"; },
                           [&](const detail::FailureResult& res) { os << "failure: " << res.reason; },
                           [&](const detail::TranscodeResult& res) {
                               os << "transcode: reasons = {";

                               bool firstReason{ true };
                               for (TranscodeReason reason : res.reasons)
                               {
                                   if (!firstReason)
                                       os << ", ";
                                   os << transcodeReasonToString(reason);
                                   firstReason = false;
                               }
                               os << "}, target stream = {";
                               os << "protocol = " << res.targetStreamInfo.protocol << ", container = " << res.targetStreamInfo.container << ", codec = " << res.targetStreamInfo.codec;
                               if (res.targetStreamInfo.audioChannels)
                                   os << ", audioChannels = " << *res.targetStreamInfo.audioChannels;
                               if (res.targetStreamInfo.audioBitrate)
                                   os << ", audioBitrate = " << *res.targetStreamInfo.audioBitrate;
                               if (!res.targetStreamInfo.audioProfile.empty())
                                   os << ", audioProfile = " << res.targetStreamInfo.audioProfile;
                               if (res.targetStreamInfo.audioSamplerate)
                                   os << ", audioSamplerate = " << *res.targetStreamInfo.audioSamplerate;
                               if (res.targetStreamInfo.audioBitdepth)
                                   os << ", audioBitdepth = " << *res.targetStreamInfo.audioBitdepth;
                               os << "}";
                           } },
                       result);

            return os;
        } // namespace
    }; // namespace detail

    namespace
    {
        struct TestCase
        {
            ClientInfo clientInfo;
            audio::AudioProperties source;

            detail::TranscodeDecisionResult expected;
        };

        void processTests(std::span<const TestCase> testCases)
        {
            for (std::size_t testCaseIndex{ 0 }; testCaseIndex < std::size(testCases); ++testCaseIndex)
            {
                const auto& testCase{ testCases[testCaseIndex] };
                const detail::TranscodeDecisionResult decision{ detail::computeTranscodeDecision(testCase.clientInfo, testCase.source) };

                EXPECT_EQ(testCase.expected, decision) << "testCaseIndex: " << testCaseIndex;
            }
        }
    } // namespace

    TEST(TranscodeDecision, directPlay)
    {
        const TestCase testCases[]{
            // Direct play
            {
                .clientInfo = {
                    .name = "TestClient",
                    .platform = "TestPlatform",
                    .maxAudioBitrate = 512'000,
                    .maxTranscodingAudioBitrate = 256'000,
                    .directPlayProfiles = {
                        { .containers = { "mp3" }, .audioCodecs = { "mp3" }, .protocols = { "http" }, .maxAudioChannels = 2 },
                    },
                    .transcodingProfiles = {
                        { .container = "mp3", .audioCodec = "mp3", .protocol = "http", .maxAudioChannels = 2 },
                    },
                    .codecProfiles = { { .type = "AudioCodec", .name = "mp3", .limitations = {
                                                                                  { .name = Limitation::Type::AudioBitrate, .comparison = Limitation::ComparisonOperator::LessThanEqual, .values = { "256000" }, .required = true },
                                                                              } } },
                },
                .source = {
                    .container = core::media::Container::MPEG,
                    .codec = core::media::Codec::MP3,
                    .duration = std::chrono::seconds{ 60 },
                    .bitrate = 128'000,
                    .channelCount = 2,
                    .sampleRate = 44'100,
                    .bitsPerSample = std::nullopt,
                },

                .expected = { detail::DirectPlayResult{} },
            },

            // Needs transcode due to codec limitation
            {
                .clientInfo = {
                    .name = "TestClient",
                    .platform = "TestPlatform",
                    .maxAudioBitrate = 512'000,
                    .maxTranscodingAudioBitrate = 96'000,
                    .directPlayProfiles = {
                        { .containers = { "mp3" }, .audioCodecs = { "mp3" }, .protocols = { "http" }, .maxAudioChannels = 2 },
                    },
                    .transcodingProfiles = {
                        { .container = "mp3", .audioCodec = "mp3", .protocol = "http", .maxAudioChannels = 2 },
                    },
                    .codecProfiles = { { .type = "AudioCodec", .name = "mp3", .limitations = {
                                                                                  { .name = Limitation::Type::AudioBitrate, .comparison = Limitation::ComparisonOperator::LessThanEqual, .values = { "96000" }, .required = true },
                                                                              } } },
                },
                .source = {
                    .container = core::media::Container::MPEG,
                    .codec = core::media::Codec::MP3,
                    .duration = std::chrono::seconds{ 60 },
                    .bitrate = 128'000,
                    .channelCount = 2,
                    .sampleRate = 44'100,
                    .bitsPerSample = std::nullopt,
                },

                .expected = { detail::TranscodeResult{ .reasons = { detail::TranscodeReason::AudioBitrateNotSupported }, .targetStreamInfo = { .protocol = "http", .container = "mp3", .codec = "mp3", .audioChannels = std::nullopt, .audioBitrate = 96000, .audioProfile = "", .audioSamplerate = std::nullopt, .audioBitdepth = std::nullopt } } },
            },

            // Needs transcode due to global limitation on the direct play bitrate
            {
                .clientInfo = {
                    .name = "TestClient",
                    .platform = "TestPlatform",
                    .maxAudioBitrate = 96'000,
                    .maxTranscodingAudioBitrate = 96'000,
                    .directPlayProfiles = {
                        { .containers = { "mp3" }, .audioCodecs = { "mp3" }, .protocols = { "http" }, .maxAudioChannels = 2 },
                    },
                    .transcodingProfiles = {
                        { .container = "mp3", .audioCodec = "mp3", .protocol = "http", .maxAudioChannels = 2 },
                    },
                    .codecProfiles = { { .type = "AudioCodec", .name = "mp3", .limitations = {
                                                                                  { .name = Limitation::Type::AudioBitrate, .comparison = Limitation::ComparisonOperator::LessThanEqual, .values = { "256000" }, .required = true },
                                                                              } } },
                },
                .source = {
                    .container = core::media::Container::MPEG,
                    .codec = core::media::Codec::MP3,
                    .duration = std::chrono::seconds{ 60 },
                    .bitrate = 128'000,
                    .channelCount = 2,
                    .sampleRate = 44'100,
                    .bitsPerSample = std::nullopt,
                },

                .expected = { detail::TranscodeResult{ .reasons = { detail::TranscodeReason::AudioBitrateNotSupported }, .targetStreamInfo = { .protocol = "http", .container = "mp3", .codec = "mp3", .audioChannels = std::nullopt, .audioBitrate = 96000, .audioProfile = "", .audioSamplerate = std::nullopt, .audioBitdepth = std::nullopt } } },
            },

            // Needs transcode due to codec limitation, but global limitation is even more restrictive
            {
                .clientInfo = {
                    .name = "TestClient",
                    .platform = "TestPlatform",
                    .maxAudioBitrate = 96'000,
                    .maxTranscodingAudioBitrate = 96'000,
                    .directPlayProfiles = {
                        { .containers = { "mp3" }, .audioCodecs = { "mp3" }, .protocols = { "http" }, .maxAudioChannels = 2 },
                    },
                    .transcodingProfiles = {
                        { .container = "mp3", .audioCodec = "mp3", .protocol = "http", .maxAudioChannels = 2 },
                    },
                    .codecProfiles = { { .type = "AudioCodec", .name = "mp3", .limitations = {
                                                                                  { .name = Limitation::Type::AudioBitrate, .comparison = Limitation::ComparisonOperator::LessThanEqual, .values = { "128000" }, .required = true },
                                                                              } } },
                },
                .source = {
                    .container = core::media::Container::MPEG,
                    .codec = core::media::Codec::MP3,
                    .duration = std::chrono::seconds{ 60 },
                    .bitrate = 192'000,
                    .channelCount = 2,
                    .sampleRate = 44'100,
                    .bitsPerSample = std::nullopt,
                },

                .expected = { detail::TranscodeResult{ .reasons = { detail::TranscodeReason::AudioBitrateNotSupported }, .targetStreamInfo = { .protocol = "http", .container = "mp3", .codec = "mp3", .audioChannels = std::nullopt, .audioBitrate = 96'000, .audioProfile = "", .audioSamplerate = std::nullopt, .audioBitdepth = std::nullopt } } },
            },

            // Needs transcode due to max audio sample rate not handle by codec limitation
            {
                .clientInfo = {
                    .name = "TestClient",
                    .platform = "TestPlatform",
                    .maxAudioBitrate = 320'000,
                    .maxTranscodingAudioBitrate = 320'000,
                    .directPlayProfiles = {
                        { .containers = { "mp3" }, .audioCodecs = { "mp3" }, .protocols = { "http" }, .maxAudioChannels = 2 },
                    },
                    .transcodingProfiles = {
                        { .container = "mp3", .audioCodec = "mp3", .protocol = "http", .maxAudioChannels = 2 },
                    },
                    .codecProfiles = { { .type = "AudioCodec", .name = "mp3", .limitations = {
                                                                                  { .name = Limitation::Type::AudioSamplerate, .comparison = Limitation::ComparisonOperator::LessThanEqual, .values = { "48000" }, .required = true },
                                                                              } } },
                },
                .source = {
                    .container = core::media::Container::MPEG,
                    .codec = core::media::Codec::MP3,
                    .duration = std::chrono::seconds{ 60 },
                    .bitrate = 192'000,
                    .channelCount = 2,
                    .sampleRate = 96'000,
                    .bitsPerSample = std::nullopt,
                },

                .expected = { detail::TranscodeResult{ .reasons = { detail::TranscodeReason::AudioSampleRateNotSupported }, .targetStreamInfo = { .protocol = "http", .container = "mp3", .codec = "mp3", .audioChannels = std::nullopt, .audioBitrate = 192'000, .audioProfile = "", .audioSamplerate = 48'000, .audioBitdepth = std::nullopt } } },
            },

            // Needs transcode due to max nb channels not handle by profile
            {
                .clientInfo = {
                    .name = "TestClient",
                    .platform = "TestPlatform",
                    .maxAudioBitrate = 320'000,
                    .maxTranscodingAudioBitrate = 320'000,
                    .directPlayProfiles = { { .containers = { "mp3" }, .audioCodecs = { "mp3" }, .protocols = { "http" }, .maxAudioChannels = 2 } },
                    .transcodingProfiles = { { .container = "mp3", .audioCodec = "mp3", .protocol = "http", .maxAudioChannels = 2 } },
                    .codecProfiles = { { .type = "AudioCodec", .name = "mp3", .limitations = {} } },
                },
                .source = {
                    .container = core::media::Container::MPEG,
                    .codec = core::media::Codec::MP3,
                    .duration = std::chrono::seconds{ 60 },
                    .bitrate = 192'000,
                    .channelCount = 5,
                    .sampleRate = 48'000,
                    .bitsPerSample = std::nullopt,
                },

                .expected = { detail::TranscodeResult{ .reasons = { detail::TranscodeReason::AudioChannelsNotSupported }, .targetStreamInfo = { .protocol = "http", .container = "mp3", .codec = "mp3", .audioChannels = 2, .audioBitrate = 192'000, .audioProfile = "", .audioSamplerate = std::nullopt, .audioBitdepth = std::nullopt } } },
            },

            // Needs transcode due to max nb channels not handle by codec. TODO take channel reduction into account for bitrate
            {
                .clientInfo = {
                    .name = "TestClient",
                    .platform = "TestPlatform",
                    .maxAudioBitrate = 320'000,
                    .maxTranscodingAudioBitrate = 320'000,
                    .directPlayProfiles = {
                        { .containers = { "mp3" }, .audioCodecs = { "mp3" }, .protocols = { "http" }, .maxAudioChannels = std::nullopt },
                    },
                    .transcodingProfiles = {
                        { .container = "mp3", .audioCodec = "mp3", .protocol = "http", .maxAudioChannels = std::nullopt },
                    },
                    .codecProfiles = { { .type = "AudioCodec", .name = "mp3", .limitations = { { .name = Limitation::Type::AudioChannels, .comparison = Limitation::ComparisonOperator::LessThanEqual, .values = { "2" }, .required = true } } } },
                },
                .source = {
                    .container = core::media::Container::MPEG,
                    .codec = core::media::Codec::MP3,
                    .duration = std::chrono::seconds{ 60 },
                    .bitrate = 192'000,
                    .channelCount = 5,
                    .sampleRate = 48'000,
                    .bitsPerSample = std::nullopt,
                },

                .expected = { detail::TranscodeResult{ .reasons = { detail::TranscodeReason::AudioChannelsNotSupported }, .targetStreamInfo = { .protocol = "http", .container = "mp3", .codec = "mp3", .audioChannels = 2, .audioBitrate = 192'000, .audioProfile = "", .audioSamplerate = std::nullopt, .audioBitdepth = std::nullopt } } },
            },

            // needs transcode because codec not handled
            {
                .clientInfo = {
                    .name = "TestClient",
                    .platform = "TestPlatform",
                    .maxAudioBitrate = 320'000,
                    .maxTranscodingAudioBitrate = 320'000,
                    .directPlayProfiles = {
                        { .containers = { "mp3" }, .audioCodecs = { "mp3" }, .protocols = { "http" }, .maxAudioChannels = std::nullopt },
                    },
                    .transcodingProfiles = {
                        { .container = "mp3", .audioCodec = "mp3", .protocol = "http", .maxAudioChannels = std::nullopt },
                    },
                    .codecProfiles = { { .type = "AudioCodec", .name = "mp3", .limitations = { { .name = Limitation::Type::AudioChannels, .comparison = Limitation::ComparisonOperator::LessThanEqual, .values = { "2" }, .required = true } } } },
                },
                .source = {
                    .container = core::media::Container::Ogg,
                    .codec = core::media::Codec::Opus,
                    .duration = std::chrono::seconds{ 60 },
                    .bitrate = 128'000,
                    .channelCount = 2,
                    .sampleRate = 48'000,
                    .bitsPerSample = std::nullopt,
                },

                .expected = { detail::TranscodeResult{ .reasons = { detail::TranscodeReason::ContainerNotSupported }, .targetStreamInfo = { .protocol = "http", .container = "mp3", .codec = "mp3", .audioChannels = std::nullopt, .audioBitrate = 128'000, .audioProfile = "", .audioSamplerate = std::nullopt, .audioBitdepth = std::nullopt } } },
            },

            // needs transcode because codec not handled (lossless source => using max bitrate)
            {
                .clientInfo = {
                    .name = "TestClient",
                    .platform = "TestPlatform",
                    .maxAudioBitrate = 1'000'000,
                    .maxTranscodingAudioBitrate = 320'000,
                    .directPlayProfiles = {
                        { .containers = { "mp3" }, .audioCodecs = { "mp3" }, .protocols = { "http" }, .maxAudioChannels = std::nullopt },
                    },
                    .transcodingProfiles = {
                        { .container = "mp3", .audioCodec = "mp3", .protocol = "http", .maxAudioChannels = std::nullopt },
                    },
                    .codecProfiles = { { .type = "AudioCodec", .name = "mp3", .limitations = {} } },
                },
                .source = {
                    .container = core::media::Container::FLAC,
                    .codec = core::media::Codec::FLAC,
                    .duration = std::chrono::seconds{ 60 },
                    .bitrate = 750'000,
                    .channelCount = 2,
                    .sampleRate = 48'000,
                    .bitsPerSample = 16,
                },

                .expected = { detail::TranscodeResult{ .reasons = { detail::TranscodeReason::ContainerNotSupported }, .targetStreamInfo = { .protocol = "http", .container = "mp3", .codec = "mp3", .audioChannels = std::nullopt, .audioBitrate = 320000, .audioProfile = "", .audioSamplerate = std::nullopt, .audioBitdepth = std::nullopt } } },
            },

            // needs transcode because codec not handled (lossless source => using a default good bitrate)
            {
                .clientInfo = { .name = "TestClient", .platform = "TestPlatform", .maxAudioBitrate = std::nullopt, .maxTranscodingAudioBitrate = std::nullopt, .directPlayProfiles = { {
                                                                                                                                                                   { .containers = { "mp3" }, .audioCodecs = { "mp3" }, .protocols = { "http" }, .maxAudioChannels = std::nullopt },
                                                                                                                                                               } },
                                .transcodingProfiles = {
                                    { .container = "mp3", .audioCodec = "mp3", .protocol = "http", .maxAudioChannels = std::nullopt },
                                },
                                .codecProfiles = {} },
                .source = {
                    .container = core::media::Container::FLAC,
                    .codec = core::media::Codec::FLAC,
                    .duration = std::chrono::seconds{ 60 },
                    .bitrate = 750'000,
                    .channelCount = 2,
                    .sampleRate = 48'000,
                    .bitsPerSample = 16,
                },

                .expected = { detail::TranscodeResult{ .reasons = { detail::TranscodeReason::ContainerNotSupported }, .targetStreamInfo = { .protocol = "http", .container = "mp3", .codec = "mp3", .audioChannels = std::nullopt, .audioBitrate = 256000, .audioProfile = "", .audioSamplerate = std::nullopt, .audioBitdepth = std::nullopt } } },
            },

            // check protocol * and codec * are properly handled
            {
                .clientInfo = {
                    .name = "TestClient",
                    .platform = "TestPlatform",
                    .maxAudioBitrate = 1'000'000,
                    .maxTranscodingAudioBitrate = 320'000,
                    .directPlayProfiles = {
                        { .containers = { "mp4", "flac", "mp3" }, .audioCodecs = {}, .protocols = {}, .maxAudioChannels = std::nullopt },
                    },
                    .transcodingProfiles = {},
                    .codecProfiles = {},
                },
                .source = {
                    .container = core::media::Container::MPEG,
                    .codec = core::media::Codec::MP3,
                    .duration = std::chrono::seconds{ 60 },
                    .bitrate = 128'000,
                    .channelCount = 2,
                    .sampleRate = 48'000,
                    .bitsPerSample = std::nullopt,
                },

                .expected = { detail::DirectPlayResult{} },
            },

            // check container * is properly handled
            {
                .clientInfo = {
                    .name = "TestClient",
                    .platform = "TestPlatform",
                    .maxAudioBitrate = 1'000'000,
                    .maxTranscodingAudioBitrate = 320'000,
                    .directPlayProfiles = {
                        { .containers = {}, .audioCodecs = { "mp3" }, .protocols = {}, .maxAudioChannels = std::nullopt },
                    },
                    .transcodingProfiles = {},
                    .codecProfiles = {},
                },
                .source = {
                    .container = core::media::Container::MPEG,
                    .codec = core::media::Codec::MP3,
                    .duration = std::chrono::seconds{ 60 },
                    .bitrate = 128'000,
                    .channelCount = 2,
                    .sampleRate = 48'000,
                    .bitsPerSample = std::nullopt,
                },

                .expected = { detail::DirectPlayResult{} },
            },

            // want flac but bitrate too high
            {
                .clientInfo = {
                    .name = "LocalDevice",
                    .platform = "Android",
                    .maxAudioBitrate = 320'000,
                    .maxTranscodingAudioBitrate = 320'000,
                    .directPlayProfiles = {
                        { .containers = { "flac" }, .audioCodecs = { "flac" }, .protocols = {}, .maxAudioChannels = 32 },
                    },
                    .transcodingProfiles = { { .container = "ogg", .audioCodec = "opus", .protocol = "http", .maxAudioChannels = std::nullopt }, { .container = "mp3", .audioCodec = "mp3", .protocol = "http", .maxAudioChannels = 2 } },
                    .codecProfiles = {},
                },
                .source = {
                    .container = core::media::Container::FLAC,
                    .codec = core::media::Codec::FLAC,
                    .duration = std::chrono::seconds{ 60 },
                    .bitrate = 1'000'000,
                    .channelCount = 2,
                    .sampleRate = 48'000,
                    .bitsPerSample = 16,
                },

                .expected = { detail::TranscodeResult{ .reasons = { detail::TranscodeReason::AudioBitrateNotSupported }, .targetStreamInfo = { .protocol = "http", .container = "ogg", .codec = "opus", .audioChannels = std::nullopt, .audioBitrate = 320'000, .audioProfile = "", .audioSamplerate = std::nullopt, .audioBitdepth = std::nullopt } } },
            },

            // want flac but source sample rate is too high
            {
                .clientInfo = {
                    .name = "SONOS",
                    .platform = "UPnP",
                    .maxAudioBitrate = 1'000'000,
                    .maxTranscodingAudioBitrate = 1'000'000,
                    .directPlayProfiles = {
                        { .containers = { "flac" }, .audioCodecs = {}, .protocols = {}, .maxAudioChannels = std::nullopt },
                        { .containers = { "mp3" }, .audioCodecs = { "mp3" }, .protocols = {}, .maxAudioChannels = std::nullopt },
                        { .containers = { "m4a", "mp4" }, .audioCodecs = { "aac" }, .protocols = {}, .maxAudioChannels = std::nullopt },
                    },
                    .transcodingProfiles = {
                        { .container = "flac", .audioCodec = "flac", .protocol = "http", .maxAudioChannels = 6 },
                        { .container = "aac", .audioCodec = "aac", .protocol = "http", .maxAudioChannels = 6 },
                        { .container = "mp3", .audioCodec = "mp3", .protocol = "http", .maxAudioChannels = 2 },
                    },
                    .codecProfiles = {
                        { .type = "AudioCodec", .name = "flac", .limitations = { { .name = Limitation::Type::AudioSamplerate, .comparison = Limitation::ComparisonOperator::LessThanEqual, .values = { "48000" }, .required = true } } },
                        { .type = "AudioCodec", .name = "vorbis", .limitations = { { .name = Limitation::Type::AudioSamplerate, .comparison = Limitation::ComparisonOperator::LessThanEqual, .values = { "48000" }, .required = true } } },
                        { .type = "AudioCodec", .name = "opus", .limitations = { { .name = Limitation::Type::AudioSamplerate, .comparison = Limitation::ComparisonOperator::LessThanEqual, .values = { "48000" }, .required = true } } },
                    },
                },
                .source = {
                    .container = core::media::Container::FLAC,
                    .codec = core::media::Codec::FLAC,
                    .duration = std::chrono::seconds{ 60 },
                    .bitrate = 950'000,
                    .channelCount = 2,
                    .sampleRate = 96'000,
                    .bitsPerSample = 24,
                },

                .expected = { detail::TranscodeResult{ .reasons = { detail::TranscodeReason::AudioSampleRateNotSupported, detail::TranscodeReason::ContainerNotSupported, detail::TranscodeReason::ContainerNotSupported }, .targetStreamInfo = { .protocol = "http", .container = "flac", .codec = "flac", .audioChannels = std::nullopt, .audioBitrate = std::nullopt, .audioProfile = "", .audioSamplerate = 48'000, .audioBitdepth = std::nullopt } } },
            },

            // want flac but source sample rate is too high, no max bitrate
            {
                .clientInfo = {
                    .name = "SONOS",
                    .platform = "UPnP",
                    .maxAudioBitrate = std::nullopt,
                    .maxTranscodingAudioBitrate = std::nullopt,
                    .directPlayProfiles = {
                        { .containers = { "opus", "ogg", "oga", "aac", "webma", "webm", "wav", "flac", "mka" }, .audioCodecs = {}, .protocols = {}, .maxAudioChannels = std::nullopt },
                        { .containers = { "mp3" }, .audioCodecs = { "mp3" }, .protocols = {}, .maxAudioChannels = std::nullopt },
                        { .containers = { "m4a", "mp4" }, .audioCodecs = { "aac" }, .protocols = {}, .maxAudioChannels = std::nullopt },
                    },
                    .transcodingProfiles = {
                        { .container = "flac", .audioCodec = "flac", .protocol = "http", .maxAudioChannels = 6 },
                        { .container = "mp4", .audioCodec = "aac", .protocol = "http", .maxAudioChannels = 6 },
                        { .container = "aac", .audioCodec = "aac", .protocol = "http", .maxAudioChannels = 6 },
                        { .container = "mp3", .audioCodec = "mp3", .protocol = "http", .maxAudioChannels = 2 },
                    },
                    .codecProfiles = {
                        { .type = "AudioCodec", .name = "flac", .limitations = { { .name = Limitation::Type::AudioSamplerate, .comparison = Limitation::ComparisonOperator::LessThanEqual, .values = { "48000" }, .required = true } } },
                        { .type = "AudioCodec", .name = "vorbis", .limitations = { { .name = Limitation::Type::AudioSamplerate, .comparison = Limitation::ComparisonOperator::LessThanEqual, .values = { "48000" }, .required = true } } },
                        { .type = "AudioCodec", .name = "opus", .limitations = { { .name = Limitation::Type::AudioSamplerate, .comparison = Limitation::ComparisonOperator::LessThanEqual, .values = { "48000" }, .required = true } } },
                    },
                },
                .source = {
                    .container = core::media::Container::FLAC,
                    .codec = core::media::Codec::FLAC,
                    .duration = std::chrono::seconds{ 60 },
                    .bitrate = 950'000,
                    .channelCount = 2,
                    .sampleRate = 96'000,
                    .bitsPerSample = 24,
                },

                .expected = { detail::TranscodeResult{ .reasons = { detail::TranscodeReason::AudioSampleRateNotSupported, detail::TranscodeReason::ContainerNotSupported, detail::TranscodeReason::ContainerNotSupported }, .targetStreamInfo = { .protocol = "http", .container = "flac", .codec = "flac", .audioChannels = std::nullopt, .audioBitrate = std::nullopt, .audioProfile = "", .audioSamplerate = 48'000, .audioBitdepth = std::nullopt } } },
            },

            // wants a lossy codec not handled -> transcode to lossy
            {
                .clientInfo = {
                    .name = "SONOS",
                    .platform = "UPnP",
                    .maxAudioBitrate = 1'000'000,
                    .maxTranscodingAudioBitrate = 1'000'000,
                    .directPlayProfiles = {
                        { .containers = { "flac" }, .audioCodecs = {}, .protocols = {}, .maxAudioChannels = std::nullopt },
                        { .containers = { "mp3" }, .audioCodecs = { "mp3" }, .protocols = {}, .maxAudioChannels = std::nullopt },
                        { .containers = { "m4a", "mp4" }, .audioCodecs = { "aac" }, .protocols = {}, .maxAudioChannels = std::nullopt },
                    },
                    .transcodingProfiles = {
                        { .container = "flac", .audioCodec = "flac", .protocol = "http", .maxAudioChannels = 6 },
                        { .container = "aac", .audioCodec = "aac", .protocol = "http", .maxAudioChannels = 6 },
                        { .container = "mp3", .audioCodec = "mp3", .protocol = "http", .maxAudioChannels = 2 },
                    },
                    .codecProfiles = {
                        { .type = "AudioCodec", .name = "flac", .limitations = { { .name = Limitation::Type::AudioSamplerate, .comparison = Limitation::ComparisonOperator::LessThanEqual, .values = { "48000" }, .required = true } } },
                        { .type = "AudioCodec", .name = "vorbis", .limitations = { { .name = Limitation::Type::AudioSamplerate, .comparison = Limitation::ComparisonOperator::LessThanEqual, .values = { "48000" }, .required = true } } },
                        { .type = "AudioCodec", .name = "opus", .limitations = { { .name = Limitation::Type::AudioSamplerate, .comparison = Limitation::ComparisonOperator::LessThanEqual, .values = { "48000" }, .required = true } } },
                    },
                },
                .source = {
                    .container = core::media::Container::Ogg,
                    .codec = core::media::Codec::Vorbis,
                    .duration = std::chrono::seconds{ 60 },
                    .bitrate = 128'000,
                    .channelCount = 2,
                    .sampleRate = 48'000,
                    .bitsPerSample = 16,
                },

                .expected = { detail::TranscodeResult{ .reasons = { detail::TranscodeReason::ContainerNotSupported, detail::TranscodeReason::ContainerNotSupported, detail::TranscodeReason::ContainerNotSupported }, .targetStreamInfo = { .protocol = "http", .container = "mp3", .codec = "mp3", .audioChannels = std::nullopt, .audioBitrate = 128000, .audioProfile = "", .audioSamplerate = std::nullopt, .audioBitdepth = std::nullopt } } },
            },

            // wants a lossless codec not handled -> transcode to lossless
            {
                .clientInfo = {
                    .name = "SONOS",
                    .platform = "UPnP",
                    .maxAudioBitrate = 1'000'000,
                    .maxTranscodingAudioBitrate = 1'000'000,
                    .directPlayProfiles = {
                        { .containers = { "flac" }, .audioCodecs = {}, .protocols = {}, .maxAudioChannels = std::nullopt },
                        { .containers = { "mp3" }, .audioCodecs = { "mp3" }, .protocols = {}, .maxAudioChannels = std::nullopt },
                        { .containers = { "m4a", "mp4" }, .audioCodecs = { "aac" }, .protocols = {}, .maxAudioChannels = std::nullopt },
                    },
                    .transcodingProfiles = {
                        { .container = "flac", .audioCodec = "flac", .protocol = "http", .maxAudioChannels = 6 },
                        { .container = "aac", .audioCodec = "aac", .protocol = "http", .maxAudioChannels = 6 },
                        { .container = "mp3", .audioCodec = "mp3", .protocol = "http", .maxAudioChannels = 2 },
                    },
                    .codecProfiles = {
                        { .type = "AudioCodec", .name = "flac", .limitations = { { .name = Limitation::Type::AudioSamplerate, .comparison = Limitation::ComparisonOperator::LessThanEqual, .values = { "48000" }, .required = true } } },
                        { .type = "AudioCodec", .name = "vorbis", .limitations = { { .name = Limitation::Type::AudioSamplerate, .comparison = Limitation::ComparisonOperator::LessThanEqual, .values = { "48000" }, .required = true } } },
                        { .type = "AudioCodec", .name = "opus", .limitations = { { .name = Limitation::Type::AudioSamplerate, .comparison = Limitation::ComparisonOperator::LessThanEqual, .values = { "48000" }, .required = true } } },
                    },
                },
                .source = {
                    .container = core::media::Container::DSF,
                    .codec = core::media::Codec::DSD,
                    .duration = std::chrono::seconds{ 60 },
                    .bitrate = 950'000,
                    .channelCount = 2,
                    .sampleRate = 96'000,
                    .bitsPerSample = 24,
                },

                .expected = { detail::TranscodeResult{ .reasons = { detail::TranscodeReason::ContainerNotSupported, detail::TranscodeReason::ContainerNotSupported, detail::TranscodeReason::ContainerNotSupported }, .targetStreamInfo = { .protocol = "http", .container = "flac", .codec = "flac", .audioChannels = std::nullopt, .audioBitrate = std::nullopt, .audioProfile = "", .audioSamplerate = 48'000, .audioBitdepth = std::nullopt } } },
            },

            // no protocol specified
            {
                .clientInfo = {
                    .name = "TestClient",
                    .platform = "TestPlatform",
                    .maxAudioBitrate = 512'000,
                    .maxTranscodingAudioBitrate = 96'000,
                    .directPlayProfiles = {
                        { .containers = { "mp3" }, .audioCodecs = { "mp3" }, .protocols = {}, .maxAudioChannels = 2 },
                    },
                    .transcodingProfiles = {
                        { .container = "mp3", .audioCodec = "mp3", .protocol = { "http" }, .maxAudioChannels = 2 },
                    },
                    .codecProfiles = { { .type = "AudioCodec", .name = "mp3", .limitations = {
                                                                                  { .name = Limitation::Type::AudioBitrate, .comparison = Limitation::ComparisonOperator::LessThanEqual, .values = { "96000" }, .required = true },
                                                                              } } },
                },
                .source = {
                    .container = core::media::Container::MPEG,
                    .codec = core::media::Codec::MP3,
                    .duration = std::chrono::seconds{ 60 },
                    .bitrate = 128'000,
                    .channelCount = 2,
                    .sampleRate = 44'100,
                    .bitsPerSample = std::nullopt,
                },

                .expected = { detail::TranscodeResult{ .reasons = { detail::TranscodeReason::AudioBitrateNotSupported }, .targetStreamInfo = { .protocol = "http", .container = "mp3", .codec = "mp3", .audioChannels = std::nullopt, .audioBitrate = 96000, .audioProfile = "", .audioSamplerate = std::nullopt, .audioBitdepth = std::nullopt } } },
            },
        };

        processTests(testCases);
    }
} // namespace lms::api::subsonic
