content: {-# LANGUAGE OverloadedStrings #-}
  
  module OpenTelemetry.Propagator.W3CMultiHeaderSpec (spec) where
  
  import qualified Data.ByteString.Char8 as C8
  import Data.List (intercalate)
  import qualified Data.Text as T
  import OpenTelemetry.Propagator.W3CTraceContext
  import OpenTelemetry.Trace.TraceState (Key (..), Value (..), empty, fromList, toList)
  import Test.Hspec
  
  
  spec :: Spec
  spec = describe "W3C TraceContext Multi-Header Support" $ do
    describe "encodeTraceStateMultiple" $ do
      it "returns single header for small tracestate" $ do
        let ts = fromList [(Key "vendor1", Value "value1"), (Key "vendor2", Value "value2")]
            result = encodeTraceStateMultiple 512 ts
        result `shouldBe` ["vendor1=value1,vendor2=value2"]
  
      it "splits into multiple headers when size limit exceeded" $ do
        let mediumValue = T.replicate 50 "x" -- Use smaller values that won't be filtered
            ts =
              fromList
                [ (Key "vendor1", Value mediumValue)
                , (Key "vendor2", Value mediumValue)
                , (Key "vendor3", Value mediumValue)
                , (Key "vendor4", Value mediumValue)
                ]
            result = encodeTraceStateMultiple 150 ts -- Small limit to force splitting
        length result `shouldSatisfy` (> 1)
        -- Each header should be under the size limit
        all (\h -> C8.length h <= 150) result `shouldBe` True
  
      it "removes entries larger than 128 characters" $ do
        let longValue = T.replicate 200 "x" -- This entry will be > 128 chars total
            shortValue = "short"
            ts =
              fromList
                [ (Key "long", Value longValue)
                , (Key "vendor1", Value shortValue)
                , (Key "vendor2", Value shortValue)
                ]
            result = encodeTraceStateMultiple 512 ts
            combinedResult = C8.intercalate "," result
        -- The long entry should be filtered out
        combinedResult `shouldNotSatisfy` C8.isInfixOf "long="
        combinedResult `shouldSatisfy` C8.isInfixOf "vendor1=short"
        combinedResult `shouldSatisfy` C8.isInfixOf "vendor2=short"
  
      it "respects 32 entry limit before splitting" $ do
        let pairs = [(Key (T.pack $ "key" ++ show i), Value (T.pack $ "val" ++ show i)) | i <- [1 .. 40]]
            ts = fromList pairs
            result = encodeTraceStateMultiple 512 ts
            combinedResult = C8.intercalate "," result
            entryCount = length $ filter (== '=') $ C8.unpack combinedResult
        entryCount `shouldBe` 32
  
      it "returns empty list for empty tracestate" $ do
        let result = encodeTraceStateMultiple 512 empty
        result `shouldBe` []
  
      it "handles single very large header correctly" $ do
        let pairs = [(Key (T.pack $ "k" ++ show i), Value (T.pack $ "v" ++ show i)) | i <- [1 .. 20]]
            ts = fromList pairs
            result = encodeTraceStateMultiple 50 ts -- Very small limit
        length result `shouldSatisfy` (> 1)
        -- Should not exceed the limit
        all (\h -> C8.length h <= 50) result `shouldBe` True
  
    describe "decodeTraceStateMultiple" $ do
      it "combines single header correctly" $ do
        let headers = ["vendor1=value1,vendor2=value2"]
            result = decodeTraceStateMultiple headers
            pairs = toList result
        pairs `shouldContain` [(Key "vendor1", Value "value1")]
        pairs `shouldContain` [(Key "vendor2", Value "value2")]
  
      it "combines multiple headers in order" $ do
        let headers = ["vendor1=value1,vendor2=value2", "vendor3=value3,vendor4=value4"]
            result = decodeTraceStateMultiple headers
            pairs = toList result
        length pairs `shouldBe` 4
        pairs `shouldContain` [(Key "vendor1", Value "value1")]
        pairs `shouldContain` [(Key "vendor2", Value "value2")]
        pairs `shouldContain` [(Key "vendor3", Value "value3")]
        pairs `shouldContain` [(Key "vendor4", Value "value4")]
  
      it "handles empty headers gracefully" $ do
        let headers = ["vendor1=value1", "", "vendor2=value2"]
            result = decodeTraceStateMultiple headers
            pairs = toList result
        pairs `shouldContain` [(Key "vendor1", Value "value1")]
        pairs `shouldContain` [(Key "vendor2", Value "value2")]
  
      it "returns empty tracestate for invalid headers" $ do
        let headers = ["invalid$key=value", "another=bad,value"]
            result = decodeTraceStateMultiple headers
        result `shouldBe` empty
  
      it "returns empty tracestate for empty header list" $ do
        let result = decodeTraceStateMultiple []
        result `shouldBe` empty
  
      it "handles whitespace-only headers" $ do
        let headers = ["vendor1=value1", "   ", "vendor2=value2"]
            result = decodeTraceStateMultiple headers
            pairs = toList result
        pairs `shouldContain` [(Key "vendor1", Value "value1")]
        pairs `shouldContain` [(Key "vendor2", Value "value2")]
  
    describe "round-trip multi-header property" $ do
      it "round-trips through multiple headers" $ do
        let originalPairs =
              [ (Key "vendor1", Value "value1")
              , (Key "vendor2", Value "value2")
              , (Key "vendor3", Value "value3")
              ]
            ts = fromList originalPairs
            encoded = encodeTraceStateMultiple 512 ts
            decoded = decodeTraceStateMultiple encoded
            decodedPairs = toList decoded
        decodedPairs `shouldBe` originalPairs
  
      it "round-trips with size-constrained splitting" $ do
        let originalPairs =
              [ (Key "vendor1", Value "val1")
              , (Key "vendor2", Value "val2")
              , (Key "vendor3", Value "val3")
              , (Key "vendor4", Value "val4")
              ]
            ts = fromList originalPairs
            encoded = encodeTraceStateMultiple 25 ts -- Force splitting
            decoded = decodeTraceStateMultiple encoded
            decodedPairs = toList decoded
        -- Should contain all original entries (order may vary due to splitting)
        length decodedPairs `shouldBe` 4
        all (`elem` decodedPairs) originalPairs `shouldBe` True
  
    describe "RFC7230 compliance" $ do
      it "maintains header order when combining" $ do
        let headers = ["first=1", "second=2", "third=3"]
            result = decodeTraceStateMultiple headers
        -- The combined header should be processed in order
        result `shouldSatisfy` (/= empty)
  
      it "handles comma-separated values correctly" $ do
        let headers = ["a=1,b=2", "c=3,d=4"]
            result = decodeTraceStateMultiple headers
            pairs = toList result
        length pairs `shouldBe` 4
        pairs `shouldContain` [(Key "a", Value "1")]
        pairs `shouldContain` [(Key "b", Value "2")]
        pairs `shouldContain` [(Key "c", Value "3")]
        pairs `shouldContain` [(Key "d", Value "4")]
  
    describe "W3C specification compliance" $ do
      it "supports at least 512 character recommendation" $ do
        let longValuePairs = [(Key (T.pack $ "vendor" ++ show i), Value (T.pack $ replicate 30 'x')) | i <- [1 .. 10]]
            ts = fromList longValuePairs
            encoded = encodeTraceStateMultiple 512 ts
            totalSize = sum $ map C8.length encoded
        -- Should handle at least 512 characters worth of data
        totalSize `shouldSatisfy` (>= 300) -- Conservative check given filtering
      it "removes oversized entries as per spec" $ do
        let oversizedEntry = (Key "big", Value (T.replicate 200 "x"))
            normalEntry = (Key "small", Value "value")
            ts = fromList [oversizedEntry, normalEntry]
            encoded = encodeTraceStateMultiple 512 ts
            combined = C8.intercalate "," encoded
        -- Oversized entry should be filtered out
        combined `shouldNotSatisfy` C8.isInfixOf "big="
        combined `shouldSatisfy` C8.isInfixOf "small=value"
  