import Foundation

@main
struct StreamResolverTests {
    static func main() async {
        testClassifierPrefersObservedDirectFile()
        testResolverSelectsUnsupportedDirectURLAndHeaders()
        testResolverRejectsHLSOnlyResponse()
        testRedactorHandlesPercentEncodedPath()
        testPlaybackTimeFormatting()
        testSubtitleCandidateParsing()
        testOpenSubtitlesV3CandidateParsing()
        testOpenSubtitlesNestedAttributesFilesParsing()
        testOpenSubtitlesManifestIDsAreNotResolvedAsSubtitles()
        testOpenSubtitlesArtworkAndAddonEndpointsAreIgnored()
        testStremioSubtitleDownloadURLParsing()
        testOpenSubtitlesV3DownloadResponseResolution()
        testOpenSubtitlesNestedDownloadResponseResolution()
        await testSubtitleResolverCachesStremioDownloadBody()
        await testSubtitleResolverCachesPlainStremioDownloadBody()
        await testSubtitleResolverDownloadJSONReturningLink()
        await testSubtitleResolverRedirectToDirectSubtitle()
        await testSubtitleResolverRejectsNonSubtitleAPIResponse()
        testSubtitleCandidateDeduplicationPreservesLabels()
        testSubtitleCandidateDeduplicationUpgradesLabels()
        testSubtitleDisplayNameNormalization()
        testSubtitleDisplayNameUsesPreservedNamesForGenericVLCTracks()
        testSubtitleOptionMappingIncludesNone()
        testExternalSubtitleParserHandlesCRLFSRT()
        testExternalSubtitleCueLookupBoundaries()
        testExternalSubtitleParserCleansMultilineCueText()
        testExternalSubtitleParserHandlesSouthParkFirstCueTiming()
        testContentRangeParsing()
        testSparseRangeStoreMergesOverlaps()
        testSparseRangeStoreHitPartialHitAndMiss()
        testSparseRangeStoreEvictsOutsideWindow()
        testSparseRangeStoreTrimsOverlappingWindow()
        testSparseRangeStoreEvictsByBudgetWhilePreservingUsefulRanges()
        testRangeCacheSessionCapsResponseRange()
        testRangeCachePrefetchPrioritizesSeekOffset()
        testRangeCacheSeekPrimingIncludesObservedVLCStart()
        testRangeCachePrefetchUsesGlobalChunkBoundaries()
        testRangeCacheForegroundMissFetchesAlignedChunks()
        await testRangeCacheForegroundMissReprioritizesPrefetch()
        await testRangeCacheHitFollowsActualPostSeekReadArea()
        await testRangeProbeAllowsRangeCacheForMKVWhenServerSupportsRanges()
        await testRangeProbeAppliesRequestTimeout()
        await testRangeProbeFallsBackWhenServerIgnoresRange()
        await testRangeFetcherPreservesHeaders()
        print("StreamResolverTests passed")
    }

    private static func testClassifierPrefersObservedDirectFile() {
        let body: [String: Any] = [
            "url": "https://cdn.example.test/movie.mkv?token=secret",
            "resolverUrl": "https://addon.debridio.com/play/example"
        ]
        let candidate = StreamCandidate(messageBody: body)!
        let request = StreamClassifier.playbackRequest(from: candidate, userAgent: "DreamioTest/1")!

        assertEqual(request.playbackURL.absoluteString, "https://cdn.example.test/movie.mkv?token=secret")
        assertEqual(request.headers["Referer"], "https://web.stremio.com/")
        assertEqual(request.headers["User-Agent"], "DreamioTest/1")
    }

    private static func testResolverSelectsUnsupportedDirectURLAndHeaders() {
        let payload: [String: Any] = [
            "streams": [
                [
                    "url": "https://cdn.example.test/trailer.mp4"
                ],
                [
                    "externalUrl": "https://cdn.example.test/movie.mkv?signature=secret",
                    "behaviorHints": [
                        "proxyHeaders": [
                            "request": [
                                "Referer": "https://resolver.example.test/",
                                "User-Agent": "ResolverAgent/1"
                            ]
                        ]
                    ]
                ]
            ]
        ]

        let stream = StremioStreamResolver.bestPlayableStream(
            in: payload,
            fallbackHeaders: ["Referer": "https://web.stremio.com/"]
        )!

        assertEqual(stream.playbackURL.absoluteString, "https://cdn.example.test/movie.mkv?signature=secret")
        assertEqual(stream.headers["Referer"], "https://resolver.example.test/")
        assertEqual(stream.headers["User-Agent"], "ResolverAgent/1")
    }

    private static func testResolverRejectsHLSOnlyResponse() {
        let payload: [String: Any] = [
            "streams": [
                ["url": "https://cdn.example.test/live.m3u8"]
            ]
        ]

        let stream = StremioStreamResolver.bestPlayableStream(
            in: payload,
            fallbackHeaders: ["Referer": "https://web.stremio.com/"]
        )

        assert(stream == nil, "Expected HLS-only resolver response to stay out of native playback")
    }

    private static func testRedactorHandlesPercentEncodedPath() {
        let original = "https://cdn.example.test/video/abcdefghijklmnopqrstuvwxyz012345/%E2%9C%93.mp4?token=secret#fragment"
        let redacted = URLRedactor.redactedURLString(original)

        assertEqual(redacted, "https://cdn.example.test/video/%5Bredacted%5D/%E2%9C%93.mp4")
    }

    private static func testPlaybackTimeFormatting() {
        assertEqual(PlaybackTimeFormatter.label(for: 0), "0:00")
        assertEqual(PlaybackTimeFormatter.label(for: 65), "1:05")
        assertEqual(PlaybackTimeFormatter.label(for: 3_725), "1:02:05")
    }

    private static func testSubtitleCandidateParsing() {
        let payload: [String: Any] = [
            "subtitles": [
                [
                    "lang": "eng",
                    "url": "https://opensubtitles.example.test/download/subtitle.srt?token=secret"
                ],
                [
                    "language": "Spanish",
                    "file": "https://cdn.example.test/movie.es.vtt"
                ],
                "https://cdn.example.test/ignored.txt"
            ],
            "nested": [
                "body": "metadata https://cdn.example.test/movie.fr.ass?download=1"
            ]
        ]

        let candidates = SubtitleCandidateParser.candidates(in: payload)

        assertEqual(candidates.count, 3)
        assertEqual(candidates[0].language, "eng")
        assertEqual(candidates[1].label, "Spanish")
        assertEqual(candidates[2].url.absoluteString, "https://cdn.example.test/movie.fr.ass?download=1")
    }

    private static func testOpenSubtitlesV3CandidateParsing() {
        let payload: [String: Any] = [
            "subtitles": [
                [
                    "language": "English",
                    "download": "https://api.opensubtitles.com/api/v1/download/subtitle-file",
                    "nested": [
                        [
                            "file": "https://dl.opensubtitles.org/en/subtitle.vtt?download=1"
                        ]
                    ]
                ],
                [
                    "lang": "spa",
                    "url": "https://opensubtitles.example.test/download/episode.srt"
                ]
            ],
            "body": "alternate https://cdn.example.test/from-string.ass?source=opensubtitles",
            "ignored": [
                "https://cdn.example.test/poster.jpg",
                ["file": "https://cdn.example.test/video.mkv"]
            ]
        ]

        let candidates = SubtitleCandidateParser.candidates(in: payload)

        assertEqual(candidates.count, 4)
        assertEqual(candidates[0].label, "English")
        assertEqual(candidates[0].language, "English")
        assertEqual(candidates[1].url.absoluteString, "https://dl.opensubtitles.org/en/subtitle.vtt?download=1")
        assertEqual(candidates[1].label, "English")
        assertEqual(candidates[1].language, "English")
        assertEqual(candidates[2].label, "spa")
        assertEqual(candidates[2].language, "spa")
        assertEqual(candidates[3].url.absoluteString, "https://cdn.example.test/from-string.ass?source=opensubtitles")
    }

    private static func testOpenSubtitlesNestedAttributesFilesParsing() {
        let payload: [String: Any] = [
            "data": [
                [
                    "attributes": [
                        "language": "English",
                        "file_name": "episode.en.srt",
                        "files": [
                            [
                                "file_id": 12345,
                                "file_name": "nested.en.srt"
                            ],
                            [
                                "link": "https://dl.opensubtitles.org/en/download/nested.vtt?token=secret",
                                "language": "eng"
                            ]
                        ]
                    ]
                ]
            ]
        ]

        let candidates = SubtitleCandidateParser.candidates(in: payload)

        assertEqual(candidates.count, 2)
        assertEqual(candidates[0].url.absoluteString, "https://api.opensubtitles.com/api/v1/download/12345")
        assertEqual(candidates[0].label, "nested.en.srt")
        assertEqual(candidates[0].language, "English")
        assertEqual(candidates[1].url.absoluteString, "https://dl.opensubtitles.org/en/download/nested.vtt?token=secret")
        assertEqual(candidates[1].label, "eng")
        assertEqual(candidates[1].language, "eng")
    }

    private static func testOpenSubtitlesManifestIDsAreNotResolvedAsSubtitles() {
        let payload: [String: Any] = [
            "subtitles": [
                [
                    "url": "https://opensubtitles-v3.strem.io/manifest.json_14",
                    "file_id": 98765,
                    "lang": "eng"
                ],
                [
                    "url": "https://opensubtitles-v3.strem.io/manifest.json_15",
                    "lang": "spa"
                ],
                "https://opensubtitles-v3.strem.io/manifest.json_16"
            ]
        ]

        let candidates = SubtitleCandidateParser.candidates(in: payload)

        assertEqual(candidates.count, 1)
        assertEqual(candidates[0].url.absoluteString, "https://api.opensubtitles.com/api/v1/download/98765")
        assertEqual(candidates[0].language, "eng")
    }

    private static func testOpenSubtitlesArtworkAndAddonEndpointsAreIgnored() {
        let payload: [String: Any] = [
            "subtitles": [
                [
                    "label": "External Subtitle",
                    "url": "http://www.strem.io/images/addons/opensubtitles-logo.png"
                ],
                [
                    "label": "External Subtitle",
                    "url": "https://opensubtitles.strem.io/stremio/v1"
                ],
                [
                    "label": "English",
                    "url": "https://opensubtitles.example.test/subtitles/movie.en.srt"
                ]
            ],
            "body": "metadata https://www.strem.io/images/addons/opensubtitles-logo.png"
        ]

        let candidates = SubtitleCandidateParser.candidates(in: payload)

        assertEqual(candidates.count, 1)
        assertEqual(candidates[0].url.absoluteString, "https://opensubtitles.example.test/subtitles/movie.en.srt")
        assertEqual(candidates[0].label, "English")
    }

    private static func testStremioSubtitleDownloadURLParsing() {
        let payload: [String: Any] = [
            "subtitles": [
                [
                    "label": "English",
                    "lang": "eng",
                    "url": "https://subs5.strem.io/en/download/subencoding-stremio-utf8/src-api/file/1952341941"
                ],
                [
                    "label": "Not a subtitle",
                    "url": "https://www.strem.io/images/addons/opensubtitles-logo.png"
                ]
            ]
        ]

        let candidates = SubtitleCandidateParser.candidates(in: payload)

        assertEqual(candidates.count, 1)
        assertEqual(candidates[0].url.absoluteString, "https://subs5.strem.io/en/download/subencoding-stremio-utf8/src-api/file/1952341941")
        assertEqual(candidates[0].label, "English")
        assertEqual(candidates[0].language, "eng")
        assert(!SubtitleResolver.isDirectSubtitleFile(candidates[0].url), "Expected Stremio subtitle downloads to be resolved before VLC attachment")
    }

    private static func testContentRangeParsing() {
        let parsed = HTTPContentRange.parse("bytes 10-19/100")

        assertEqual(parsed?.range.start, 10)
        assertEqual(parsed?.range.end, 19)
        assertEqual(parsed?.totalLength, 100)
        assert(HTTPContentRange.parse("items 10-19/100") == nil, "Expected non-byte content range to be rejected")
        assert(HTTPContentRange.parse("bytes 20-10/100") == nil, "Expected invalid content range to be rejected")
    }

    private static func testSparseRangeStoreMergesOverlaps() {
        let store = SparseHTTPByteRangeStore()

        store.insert(data: Data([0, 1, 2, 3]), at: 0)
        store.insert(data: Data([3, 4, 5]), at: 3)

        assertEqual(store.cachedRanges, [HTTPByteRange(start: 0, end: 5)])
        assertEqual(Array(store.data(for: HTTPByteRange(start: 0, end: 5)) ?? Data()), [0, 1, 2, 3, 4, 5])
    }

    private static func testSparseRangeStoreHitPartialHitAndMiss() {
        let store = SparseHTTPByteRangeStore()

        store.insert(data: Data([10, 11, 12, 13]), at: 10)

        assertEqual(Array(store.data(for: HTTPByteRange(start: 10, end: 13)) ?? Data()), [10, 11, 12, 13])
        assert(store.data(for: HTTPByteRange(start: 11, end: 14)) == nil, "Expected partial hit to miss")
        assert(store.data(for: HTTPByteRange(start: 20, end: 21)) == nil, "Expected uncached range to miss")
    }

    private static func testSparseRangeStoreEvictsOutsideWindow() {
        let store = SparseHTTPByteRangeStore()

        store.insert(data: Data([0, 1, 2]), at: 0)
        store.insert(data: Data([10, 11, 12]), at: 10)
        store.evict(keeping: HTTPByteRange(start: 9, end: 12))

        assertEqual(store.cachedRanges, [HTTPByteRange(start: 10, end: 12)])
    }

    private static func testSparseRangeStoreTrimsOverlappingWindow() {
        let store = SparseHTTPByteRangeStore()

        store.insert(data: Data([0, 1, 2, 3, 4, 5]), at: 0)
        store.evict(keeping: HTTPByteRange(start: 2, end: 4))

        assertEqual(store.cachedRanges, [HTTPByteRange(start: 2, end: 4)])
        assertEqual(Array(store.data(for: HTTPByteRange(start: 2, end: 4)) ?? Data()), [2, 3, 4])
        assert(store.data(for: HTTPByteRange(start: 0, end: 5)) == nil, "Expected trimmed bytes outside the window to be evicted")
    }

    private static func testSparseRangeStoreEvictsByBudgetWhilePreservingUsefulRanges() {
        let store = SparseHTTPByteRangeStore()

        store.insert(data: Data(repeating: 1, count: 4), at: 0)
        store.insert(data: Data(repeating: 2, count: 4), at: 100)
        store.insert(data: Data(repeating: 3, count: 4), at: 200)

        let evicted = store.evict(
            toByteBudget: 8,
            preserving: [
                HTTPByteRange(start: 0, end: 3),
                HTTPByteRange(start: 190, end: 210)
            ]
        )

        assertEqual(evicted, [HTTPByteRange(start: 100, end: 103)])
        assertEqual(store.cachedRanges, [
            HTTPByteRange(start: 0, end: 3),
            HTTPByteRange(start: 200, end: 203)
        ])
    }

    private static func testRangeCacheSessionCapsResponseRange() {
        let session = ProgressiveHTTPRangeCacheSession(
            fetcher: HTTPRangeRemoteFetcher(url: URL(string: "https://example.test/video.mkv")!, headers: [:]),
            contentLength: 711_080_522,
            durationProvider: { 0 }
        )

        let responseRange = session.responseRange(for: HTTPByteRange(start: 0, end: 711_080_521))

        assertEqual(responseRange, HTTPByteRange(start: 0, end: 1_048_575))
    }

    private static func testRangeCachePrefetchPrioritizesSeekOffset() {
        let session = ProgressiveHTTPRangeCacheSession(
            fetcher: HTTPRangeRemoteFetcher(url: URL(string: "https://example.test/video.mkv")!, headers: [:]),
            contentLength: 20_000_000,
            durationProvider: { 0 }
        )

        let chunks = session.prefetchChunks(
            in: HTTPByteRange(start: 0, end: 4_194_303),
            preferredOffset: 2_200_000
        )

        assertEqual(chunks.prefix(2).map { $0 }, [
            HTTPByteRange(start: 2_097_152, end: 3_145_727),
            HTTPByteRange(start: 3_145_728, end: 4_194_303)
        ])
        assertEqual(chunks.suffix(2).map { $0 }, [
            HTTPByteRange(start: 0, end: 1_048_575),
            HTTPByteRange(start: 1_048_576, end: 2_097_151)
        ])
    }

    private static func testRangeCacheSeekPrimingIncludesObservedVLCStart() {
        let session = ProgressiveHTTPRangeCacheSession(
            fetcher: HTTPRangeRemoteFetcher(url: URL(string: "https://example.test/video.mkv")!, headers: [:]),
            contentLength: 711_080_522,
            durationProvider: { 0 }
        )

        let estimatedOffset: Int64 = 213_615_760
        let firstVLCRequest = HTTPByteRange(start: 212_942_432, end: 213_991_007)
        let window = session.seekPrimeWindow(aroundByteOffset: estimatedOffset)
        let chunks = session.prefetchChunks(
            in: window,
            preferredOffset: estimatedOffset,
            startsAtWindowStart: true
        )

        let chunkContainingVLCStart = chunks.firstIndex { $0.contains(firstVLCRequest.start) }
        let chunkContainingEstimatedOffset = chunks.firstIndex { $0.contains(estimatedOffset) }

        assert(chunkContainingVLCStart != nil, "Expected seek priming to include VLC's first request start")
        assert(chunkContainingEstimatedOffset != nil, "Expected seek priming to include the estimated offset")
        assert(
            chunkContainingVLCStart! <= chunkContainingEstimatedOffset!,
            "Expected bytes behind the seek target to be primed before ahead chunks"
        )
        assertEqual(chunks[chunkContainingVLCStart!], HTTPByteRange(start: 212_860_928, end: 213_909_503))
    }

    private static func testRangeCachePrefetchUsesGlobalChunkBoundaries() {
        let session = ProgressiveHTTPRangeCacheSession(
            fetcher: HTTPRangeRemoteFetcher(url: URL(string: "https://example.test/video.mkv")!, headers: [:]),
            contentLength: 711_080_522,
            durationProvider: { 0 }
        )

        let chunks = session.prefetchChunks(
            in: HTTPByteRange(start: 213_278_260, end: 216_000_000),
            preferredOffset: 213_615_760
        )

        assert(chunks.allSatisfy { $0.start % 1_048_576 == 0 }, "Expected prefetch chunk starts to use stable global 1 MB boundaries: \(chunks)")
        assertEqual(chunks[0], HTTPByteRange(start: 212_860_928, end: 213_909_503))
    }

    private static func testRangeCacheForegroundMissFetchesAlignedChunks() {
        let session = ProgressiveHTTPRangeCacheSession(
            fetcher: HTTPRangeRemoteFetcher(url: URL(string: "https://example.test/video.mkv")!, headers: [:]),
            contentLength: 711_080_522,
            durationProvider: { 0 }
        )

        let chunks = session.alignedChunks(covering: HTTPByteRange(start: 48_234_649, end: 49_185_907))

        assertEqual(chunks, [HTTPByteRange(start: 48_234_496, end: 49_283_071)])
    }

    private static func testRangeCacheForegroundMissReprioritizesPrefetch() async {
        let queue = DispatchQueue(label: "dreamio.range-cache-test")
        var requestedRanges: [String] = []
        MockURLProtocol.handler = { request in
            let range = request.value(forHTTPHeaderField: "Range") ?? ""
            queue.sync {
                requestedRanges.append(range)
            }
            let byteRange = byteRange(fromHeader: range, contentLength: 80_000_000)
            let response = HTTPURLResponse(
                url: request.url!,
                statusCode: 206,
                httpVersion: nil,
                headerFields: ["Content-Range": "bytes \(byteRange.start)-\(byteRange.end)/80000000"]
            )!
            return (Data(repeating: 1, count: Int(byteRange.length)), response)
        }

        let session = ProgressiveHTTPRangeCacheSession(
            fetcher: HTTPRangeRemoteFetcher(
                url: URL(string: "https://cdn.example.test/movie.mp4")!,
                headers: [:],
                session: mockSession()
            ),
            contentLength: 80_000_000,
            durationProvider: { 100 }
        )
        defer {
            session.cancelPrefetch()
        }

        session.prefetch(aroundByteOffset: 28_242_716)
        _ = try? await session.data(for: HTTPByteRange(start: 51_818_977, end: 52_867_552))
        try? await Task.sleep(nanoseconds: 50_000_000)

        let ranges = queue.sync { requestedRanges }
        assert(ranges.contains("bytes=51380224-52428799"), "Expected foreground VLC miss to fetch aligned cache chunks")
        assert(ranges.contains { range in
            range.hasPrefix("bytes=52428800-")
        }, "Expected prefetch to restart on a global chunk boundary near VLC's foreground range, got \(ranges)")
        session.cancelPrefetch()
        MockURLProtocol.handler = nil
        try? await Task.sleep(nanoseconds: 50_000_000)
    }

    private static func testRangeCacheHitFollowsActualPostSeekReadArea() async {
        let queue = DispatchQueue(label: "dreamio.range-cache-hit-follow-test")
        var requestedRanges: [String] = []
        MockURLProtocol.handler = { request in
            let range = request.value(forHTTPHeaderField: "Range") ?? ""
            queue.sync {
                requestedRanges.append(range)
            }
            let byteRange = byteRange(fromHeader: range, contentLength: 80_000_000)
            let response = HTTPURLResponse(
                url: request.url!,
                statusCode: 206,
                httpVersion: nil,
                headerFields: ["Content-Range": "bytes \(byteRange.start)-\(byteRange.end)/80000000"]
            )!
            return (Data(repeating: 1, count: Int(byteRange.length)), response)
        }

        let session = ProgressiveHTTPRangeCacheSession(
            fetcher: HTTPRangeRemoteFetcher(
                url: URL(string: "https://cdn.example.test/movie.mp4")!,
                headers: [:],
                session: mockSession()
            ),
            contentLength: 80_000_000,
            durationProvider: { 100 }
        )
        defer {
            session.cancelPrefetch()
        }

        session.store.insert(data: Data(repeating: 7, count: 1_048_576), at: 27_165_812)
        session.prefetchForSeek(aroundByteOffset: 15_936_567)
        _ = try? await session.data(for: HTTPByteRange(start: 27_165_812, end: 28_214_387))
        try? await Task.sleep(nanoseconds: 100_000_000)

        let ranges = queue.sync { requestedRanges }
        assert(ranges.contains { range in
            range.hasPrefix("bytes=27262976-")
        }, "Expected a cache hit far from the seek estimate to restart prefetch near VLC's real read area, got \(ranges)")
        MockURLProtocol.handler = nil
        try? await Task.sleep(nanoseconds: 50_000_000)
    }

    private static func testRangeProbeAllowsRangeCacheForMKVWhenServerSupportsRanges() async {
        var requestCount = 0
        MockURLProtocol.handler = { request in
            requestCount += 1
            assertEqual(request.httpMethod, "HEAD")
            let response = HTTPURLResponse(
                url: request.url!,
                statusCode: 200,
                httpVersion: nil,
                headerFields: [
                    "Accept-Ranges": "bytes",
                    "Content-Length": "20"
                ]
            )!
            return (Data(), response)
        }

        let fetcher = HTTPRangeRemoteFetcher(
            url: URL(string: "https://cdn.example.test/show.mkv?token=secret")!,
            headers: [:],
            session: mockSession()
        )
        let probe = await fetcher.probe()

        assertEqual(probe.isCacheable, true)
        assertEqual(probe.contentLength, 20)
        assertEqual(probe.fallbackReason, nil)
        assertEqual(requestCount, 1)
        MockURLProtocol.handler = nil
    }

    private static func testRangeProbeAppliesRequestTimeout() async {
        MockURLProtocol.handler = { request in
            assertEqual(request.timeoutInterval, 1.5)
            let response = HTTPURLResponse(
                url: request.url!,
                statusCode: 200,
                httpVersion: nil,
                headerFields: [
                    "Accept-Ranges": "bytes",
                    "Content-Length": "20"
                ]
            )!
            return (Data(), response)
        }

        let fetcher = HTTPRangeRemoteFetcher(
            url: URL(string: "https://cdn.example.test/show.mkv")!,
            headers: [:],
            session: mockSession()
        )
        let probe = await fetcher.probe(timeoutInterval: 1.5)

        assertEqual(probe.isCacheable, true)
        MockURLProtocol.handler = nil
    }

    private static func byteRange(fromHeader header: String, contentLength: Int64) -> HTTPByteRange {
        let value = header.replacingOccurrences(of: "bytes=", with: "")
        let pieces = value.split(separator: "-", maxSplits: 1).map(String.init)
        guard pieces.count == 2,
              let start = Int64(pieces[0]) else {
            return HTTPByteRange(start: 0, end: 0)
        }
        let end = pieces[1].isEmpty ? contentLength - 1 : (Int64(pieces[1]) ?? contentLength - 1)
        return HTTPByteRange(start: start, end: min(end, contentLength - 1))
    }

    private static func testRangeProbeFallsBackWhenServerIgnoresRange() async {
        MockURLProtocol.handler = { request in
            if request.httpMethod == "HEAD" {
                let response = HTTPURLResponse(
                    url: request.url!,
                    statusCode: 200,
                    httpVersion: nil,
                    headerFields: ["Content-Length": "4"]
                )!
                return (Data(), response)
            }
            assertEqual(request.value(forHTTPHeaderField: "Range"), "bytes=0-0")
            let response = HTTPURLResponse(
                url: request.url!,
                statusCode: 200,
                httpVersion: nil,
                headerFields: ["Content-Length": "4"]
            )!
            return (Data([1, 2, 3, 4]), response)
        }

        let fetcher = HTTPRangeRemoteFetcher(
            url: URL(string: "https://cdn.example.test/movie.mp4")!,
            headers: [:],
            session: mockSession()
        )
        let probe = await fetcher.probe()

        assertEqual(probe.isCacheable, false)
        assertEqual(probe.fallbackReason, "range-probe-status-200")
    }

    private static func testRangeFetcherPreservesHeaders() async {
        MockURLProtocol.handler = { request in
            assertEqual(request.value(forHTTPHeaderField: "User-Agent"), "DreamioTest/1")
            assertEqual(request.value(forHTTPHeaderField: "Referer"), "https://web.stremio.com/")
            assertEqual(request.value(forHTTPHeaderField: "Cookie"), "session=abc")
            assertEqual(request.value(forHTTPHeaderField: "Range"), "bytes=5-7")
            let response = HTTPURLResponse(
                url: request.url!,
                statusCode: 206,
                httpVersion: nil,
                headerFields: ["Content-Range": "bytes 5-7/20"]
            )!
            return (Data([5, 6, 7]), response)
        }

        let fetcher = HTTPRangeRemoteFetcher(
            url: URL(string: "https://cdn.example.test/movie.mp4")!,
            headers: [
                "User-Agent": "DreamioTest/1",
                "Referer": "https://web.stremio.com/",
                "Cookie": "session=abc"
            ],
            session: mockSession()
        )
        let data = try? await fetcher.fetch(range: HTTPByteRange(start: 5, end: 7))

        assertEqual(Array(data ?? Data()), [5, 6, 7])
    }

    private static func testOpenSubtitlesV3DownloadResponseResolution() {
        let payload = """
        {
          "link": "https://dl.opensubtitles.org/en/download/subtitle.srt?token=secret",
          "file_name": "episode.srt",
          "requests": 1
        }
        """.data(using: .utf8)!
        let original = SubtitleCandidate(
            url: URL(string: "https://api.opensubtitles.com/api/v1/download")!,
            label: "English",
            language: "eng"
        )

        let candidate = SubtitleResolver.bestPlayableCandidate(
            from: payload,
            responseURL: original.url,
            original: original
        )

        assertEqual(candidate?.url.absoluteString, "https://dl.opensubtitles.org/en/download/subtitle.srt?token=secret")
        assertEqual(candidate?.label, "English")
        assertEqual(candidate?.language, "eng")
    }

    private static func testOpenSubtitlesNestedDownloadResponseResolution() {
        let payload = """
        {
          "data": {
            "attributes": {
              "files": [
                {
                  "file_name": "ignored.txt",
                  "link": "https://cdn.example.test/ignored.txt"
                },
                {
                  "file_name": "episode.en.ass",
                  "download": {
                    "link": "https://dl.opensubtitles.org/en/download/episode.en.ass?token=secret"
                  }
                }
              ]
            }
          }
        }
        """.data(using: .utf8)!
        let original = SubtitleCandidate(
            url: URL(string: "https://api.opensubtitles.com/api/v1/download/987")!,
            label: "English SDH",
            language: "eng"
        )

        let candidate = SubtitleResolver.bestPlayableCandidate(
            from: payload,
            responseURL: original.url,
            original: original
        )

        assertEqual(candidate?.url.absoluteString, "https://dl.opensubtitles.org/en/download/episode.en.ass?token=secret")
        assertEqual(candidate?.label, "English SDH")
        assertEqual(candidate?.language, "eng")
    }

    private static func testSubtitleResolverCachesStremioDownloadBody() async {
        let sourceURL = "https://subs5.strem.io/en/download/subencoding-stremio-utf8/src-api/file/1952341941"
        let subtitleBody = """
        1
        00:00:01,000 --> 00:00:02,000
        Hello from Stremio

        """
        MockURLProtocol.handler = nil
        MockURLProtocol.handlers = [
            sourceURL: (
                200,
                URL(string: sourceURL)!,
                subtitleBody.data(using: .utf8)!
            )
        ]

        let cacheDirectory = FileManager.default.temporaryDirectory
            .appendingPathComponent("DreamioSubtitleResolverTests-\(UUID().uuidString)", isDirectory: true)
        defer {
            try? FileManager.default.removeItem(at: cacheDirectory)
        }

        let resolver = SubtitleResolver(session: mockSession(), cacheDirectory: cacheDirectory)
        let candidate = await resolver.resolve(SubtitleCandidate(
            url: URL(string: sourceURL)!,
            label: "English",
            language: "eng"
        ))

        assertEqual(candidate?.url.isFileURL, true)
        assertEqual(candidate?.url.pathExtension, "srt")
        assertEqual(candidate?.label, "English")
        assertEqual(candidate?.language, "eng")
        let cachedBody = try? String(contentsOf: candidate!.url, encoding: .utf8)
        assertEqual(cachedBody, subtitleBody)
    }

    private static func testSubtitleResolverCachesPlainStremioDownloadBody() async {
        let sourceURL = "https://subs5.strem.io/en/download/subencoding-stremio-utf8/src-api/file/1952341942"
        let subtitleBody = """
        00:01.000 --> 00:02.000
        Plain cue text without an index

        """
        MockURLProtocol.handler = nil
        MockURLProtocol.handlers = [
            sourceURL: (
                200,
                URL(string: sourceURL)!,
                subtitleBody.data(using: .utf8)!
            )
        ]

        let cacheDirectory = FileManager.default.temporaryDirectory
            .appendingPathComponent("DreamioSubtitleResolverTests-\(UUID().uuidString)", isDirectory: true)
        defer {
            try? FileManager.default.removeItem(at: cacheDirectory)
        }

        let resolver = SubtitleResolver(session: mockSession(), cacheDirectory: cacheDirectory)
        let candidate = await resolver.resolve(SubtitleCandidate(
            url: URL(string: sourceURL)!,
            label: "English",
            language: "eng"
        ))

        assertEqual(candidate?.url.isFileURL, true)
        assertEqual(candidate?.url.pathExtension, "srt")
        let cachedBody = try? String(contentsOf: candidate!.url, encoding: .utf8)
        assertEqual(cachedBody, subtitleBody)
    }

    private static func testSubtitleResolverDownloadJSONReturningLink() async {
        MockURLProtocol.handler = nil
        MockURLProtocol.handlers = [
            "https://api.opensubtitles.com/api/v1/download/123": (
                200,
                URL(string: "https://api.opensubtitles.com/api/v1/download/123")!,
                #"{"link":"https://dl.opensubtitles.org/en/download/movie.srt?token=secret"}"#.data(using: .utf8)!
            )
        ]
        let resolver = SubtitleResolver(session: mockSession())
        let candidate = await resolver.resolve(SubtitleCandidate(
            url: URL(string: "https://api.opensubtitles.com/api/v1/download/123")!,
            label: "English",
            language: "eng"
        ))

        assertEqual(candidate?.url.absoluteString, "https://dl.opensubtitles.org/en/download/movie.srt?token=secret")
        assertEqual(candidate?.label, "English")
        assertEqual(candidate?.language, "eng")
    }

    private static func testSubtitleResolverRedirectToDirectSubtitle() async {
        MockURLProtocol.handler = nil
        MockURLProtocol.handlers = [
            "https://api.opensubtitles.com/api/v1/download/redirect": (
                200,
                URL(string: "https://dl.opensubtitles.org/en/redirected.vtt?download=1")!,
                Data()
            )
        ]
        let resolver = SubtitleResolver(session: mockSession())
        let candidate = await resolver.resolve(SubtitleCandidate(
            url: URL(string: "https://api.opensubtitles.com/api/v1/download/redirect")!,
            label: "English",
            language: "eng"
        ))

        assertEqual(candidate?.url.absoluteString, "https://dl.opensubtitles.org/en/redirected.vtt?download=1")
    }

    private static func testSubtitleResolverRejectsNonSubtitleAPIResponse() async {
        MockURLProtocol.handler = nil
        MockURLProtocol.handlers = [
            "https://api.opensubtitles.com/api/v1/download/not-found": (
                200,
                URL(string: "https://api.opensubtitles.com/api/v1/download/not-found")!,
                #"{"message":"not found"}"#.data(using: .utf8)!
            )
        ]
        let resolver = SubtitleResolver(session: mockSession())
        let candidate = await resolver.resolve(SubtitleCandidate(
            url: URL(string: "https://api.opensubtitles.com/api/v1/download/not-found")!,
            label: "English",
            language: "eng"
        ))

        assert(candidate == nil, "Expected non-subtitle API response to be rejected")
    }

    private static func testSubtitleCandidateDeduplicationPreservesLabels() {
        let payload: [String: Any] = [
            "subtitles": [
                [
                    "label": "English SDH",
                    "lang": "eng",
                    "url": "https://opensubtitles.example.test/download/duplicate.srt"
                ],
                [
                    "label": "Duplicate",
                    "language": "English",
                    "download": "https://opensubtitles.example.test/download/duplicate.srt"
                ],
                "https://opensubtitles.example.test/download/duplicate.srt"
            ]
        ]

        let candidates = SubtitleCandidateParser.candidates(in: payload)

        assertEqual(candidates.count, 1)
        assertEqual(candidates[0].label, "English SDH")
        assertEqual(candidates[0].language, "eng")
    }

    private static func testSubtitleCandidateDeduplicationUpgradesLabels() {
        let payload: [String: Any] = [
            "subtitles": [
                "https://opensubtitles.example.test/download/duplicate.srt",
                [
                    "label": "English SDH",
                    "lang": "eng",
                    "url": "https://opensubtitles.example.test/download/duplicate.srt"
                ]
            ]
        ]

        let candidates = SubtitleCandidateParser.candidates(in: payload)

        assertEqual(candidates.count, 1)
        assertEqual(candidates[0].label, "English SDH")
        assertEqual(candidates[0].language, "eng")
    }

    private static func testSubtitleOptionMappingIncludesNone() {
        let options = SubtitleOptionMapper.options(from: [
            SubtitleTrack(id: 2, name: "English"),
            SubtitleTrack(id: 5, name: "Spanish")
        ])

        assertEqual(options.map(\.name), ["None", "English", "Spanish"])
        assertEqual(options.first?.id, -1)
    }

    private static func testExternalSubtitleParserHandlesCRLFSRT() {
        let body = "1\r\n00:00:01,000 --> 00:00:02,500\r\nHello from CRLF\r\n\r\n"
        let cues = ExternalSubtitleTrackParser.parseCues(from: body)

        assertEqual(cues.count, 1)
        assertEqual(cues[0].start, 1)
        assertEqual(cues[0].end, 2.5)
        assertEqual(cues[0].text, "Hello from CRLF")
    }

    private static func testExternalSubtitleCueLookupBoundaries() {
        let track = ExternalSubtitleTrack(
            id: 1,
            name: "English",
            cues: [
                ExternalSubtitleCue(start: 7.101, end: 9.25, text: "First cue")
            ]
        )

        assert(track.cue(at: 7.100) == nil, "Expected time before first cue to hide overlay")
        assertEqual(track.cue(at: 7.101)?.text, "First cue")
        assertEqual(track.cue(at: 8.0)?.text, "First cue")
        assert(track.cue(at: 9.25) == nil, "Expected cue end boundary to hide overlay")
        assert(track.cue(at: 9.251) == nil, "Expected time after cue end to hide overlay")
    }

    private static func testExternalSubtitleParserCleansMultilineCueText() {
        let body = """
        1
        00:00:03,000 --> 00:00:05,000
        <i>Hello</i>
        {\\an8}there

        """
        let cues = ExternalSubtitleTrackParser.parseCues(from: body)

        assertEqual(cues.count, 1)
        assertEqual(cues[0].text, "Hello\nthere")
    }

    private static func testExternalSubtitleParserHandlesSouthParkFirstCueTiming() {
        let body = """
        1
        00:00:07,101 --> 00:00:09,103
        I'm going down to South Park

        """
        let cues = ExternalSubtitleTrackParser.parseCues(from: body)
        let track = ExternalSubtitleTrack(id: 1, name: "English", cues: cues)

        assertEqual(cues.count, 1)
        assertEqual(cues[0].start, 7.101)
        assert(track.cue(at: 7.100) == nil, "Expected no text before the South Park-style first cue")
        assertEqual(track.cue(at: 7.101)?.text, "I'm going down to South Park")
    }

    private static func testSubtitleDisplayNameNormalization() {
        assertEqual(
            SubtitleDisplayName.displayName(for: SubtitleCandidate(
                url: URL(string: "https://opensubtitles.example.test/download/subtitle.srt")!,
                label: "Track 1",
                language: "eng"
            )),
            "English"
        )
        assertEqual(
            SubtitleDisplayName.displayName(for: SubtitleCandidate(
                url: URL(string: "https://opensubtitles.example.test/download/subtitle.srt")!,
                label: "Track 2",
                language: "Spanish"
            )),
            "Spanish"
        )
        assertEqual(
            SubtitleDisplayName.displayName(for: SubtitleCandidate(
                url: URL(string: "https://opensubtitles.example.test/download/subtitle.srt")!,
                label: "English SDH",
                language: "eng"
            )),
            "English SDH"
        )
        assertEqual(
            SubtitleDisplayName.displayName(for: SubtitleCandidate(
                url: URL(string: "https://cdn.example.test/subtitles/movie.es.srt")!,
                label: "External Subtitle",
                language: nil
            )),
            "movie.es"
        )
        assertEqual(
            SubtitleDisplayName.displayName(for: SubtitleCandidate(
                url: URL(string: "https://opensubtitles.example.test/download/subtitle.srt")!,
                label: "Track 3",
                language: "nld"
            )),
            "Dutch"
        )
        assertEqual(
            SubtitleDisplayName.displayName(for: SubtitleCandidate(
                url: URL(string: "https://opensubtitles.example.test/download/subtitle.srt")!,
                label: "Track 4",
                language: "dan"
            )),
            "Danish"
        )
    }

    private static func testSubtitleDisplayNameUsesPreservedNamesForGenericVLCTracks() {
        let options = SubtitleOptionMapper.options(from: [
            SubtitleTrack(
                id: 3,
                name: SubtitleDisplayName.name(forVLCTrackName: "Track 1", preservedName: "English")
            ),
            SubtitleTrack(
                id: 4,
                name: SubtitleDisplayName.name(forVLCTrackName: "Commentary", preservedName: "Spanish")
            )
        ])

        assertEqual(options.map(\.name), ["None", "English", "Commentary"])
    }

    private static func assertEqual<T: Equatable>(_ actual: T?, _ expected: T, file: StaticString = #file, line: UInt = #line) {
        assert(actual == expected, "Expected \(String(describing: expected)), got \(String(describing: actual))", file: file, line: line)
    }

    private static func mockSession() -> URLSession {
        let configuration = URLSessionConfiguration.ephemeral
        configuration.protocolClasses = [MockURLProtocol.self]
        return URLSession(configuration: configuration)
    }
}

private final class MockURLProtocol: URLProtocol {
    static var handler: ((URLRequest) throws -> (Data, HTTPURLResponse))?
    static var handlers: [String: (status: Int, url: URL, data: Data)] = [:]

    override class func canInit(with request: URLRequest) -> Bool {
        true
    }

    override class func canonicalRequest(for request: URLRequest) -> URLRequest {
        request
    }

    override func startLoading() {
        if let handler = Self.handler {
            do {
                let (data, response) = try handler(request)
                client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
                client?.urlProtocol(self, didLoad: data)
                client?.urlProtocolDidFinishLoading(self)
            } catch {
                client?.urlProtocol(self, didFailWithError: error)
            }
            return
        }

        guard let url = request.url,
              let handler = Self.handlers[url.absoluteString],
              let response = HTTPURLResponse(
                url: handler.url,
                statusCode: handler.status,
                httpVersion: "HTTP/1.1",
                headerFields: nil
              )
        else {
            client?.urlProtocol(self, didFailWithError: URLError(.badURL))
            return
        }

        client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
        client?.urlProtocol(self, didLoad: handler.data)
        client?.urlProtocolDidFinishLoading(self)
    }

    override func stopLoading() {}
}
