# frozen_string_literal: true
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#

require 'spec_helper'

describe 'UUID Validation' do
  protocols = [
    ['BinaryProtocol', Thrift::BinaryProtocol],
  ]

  if defined?(Thrift::BinaryProtocolAccelerated)
    protocols << ['BinaryProtocolAccelerated', Thrift::BinaryProtocolAccelerated]
  end

  protocols << ['CompactProtocol', Thrift::CompactProtocol]
  protocols << ['JsonProtocol', Thrift::JsonProtocol]

  protocols.each do |protocol_name, protocol_class|
    describe protocol_name do
      before(:each) do
        @trans = Thrift::MemoryBufferTransport.new
        @prot = protocol_class.new(@trans)
      end

      context 'valid UUIDs' do
        it 'should accept lowercase UUIDs' do
          uuid = '550e8400-e29b-41d4-a716-446655440000'
          expect { @prot.write_uuid(uuid) }.not_to raise_error
          result = @prot.read_uuid
          expect(result).to eq(uuid)
        end

        it 'should accept uppercase UUIDs' do
          uuid = '550E8400-E29B-41D4-A716-446655440000'
          expect { @prot.write_uuid(uuid) }.not_to raise_error
          result = @prot.read_uuid
          # Result should be lowercase
          expect(result).to eq('550e8400-e29b-41d4-a716-446655440000')
        end

        it 'should accept mixed case UUIDs' do
          uuid = '550e8400-E29B-41d4-A716-446655440000'
          expect { @prot.write_uuid(uuid) }.not_to raise_error
          result = @prot.read_uuid
          expect(result).to eq('550e8400-e29b-41d4-a716-446655440000')
        end

        it 'should accept all zeros' do
          uuid = '00000000-0000-0000-0000-000000000000'
          expect { @prot.write_uuid(uuid) }.not_to raise_error
          result = @prot.read_uuid
          expect(result).to eq(uuid)
        end

        it 'should accept all fs' do
          uuid = 'ffffffff-ffff-ffff-ffff-ffffffffffff'
          expect { @prot.write_uuid(uuid) }.not_to raise_error
          result = @prot.read_uuid
          expect(result).to eq(uuid)
        end
      end

      context 'invalid UUIDs' do
        def expect_invalid_uuid(value, message)
          expect { @prot.write_uuid(value) }.to raise_error(Thrift::ProtocolException) do |error|
            expect(error.type).to eq(Thrift::ProtocolException::INVALID_DATA)
            expect(error.message).to eq(message)
          end
        end

        it 'should reject nil' do
          expect_invalid_uuid(nil, 'UUID must be a string')
        end

        it 'should reject non-string' do
          expect_invalid_uuid(12345, 'UUID must be a string')
        end

        it 'should reject wrong length' do
          expect_invalid_uuid('550e8400-e29b-41d4-a716', 'Invalid UUID format')
        end

        it 'should reject missing hyphens' do
          expect_invalid_uuid('550e8400e29b41d4a716446655440000', 'Invalid UUID format')
        end

        it 'should reject hyphens in wrong positions' do
          expect_invalid_uuid('550e840-0e29b-41d4-a716-446655440000', 'Invalid UUID format')
        end

        it 'should reject invalid hex characters (g)' do
          expect_invalid_uuid('550e8400-e29b-41d4-a716-44665544000g', 'Invalid UUID format')
        end

        it 'should reject invalid hex characters (z)' do
          expect_invalid_uuid('z50e8400-e29b-41d4-a716-446655440000', 'Invalid UUID format')
        end

        it 'should reject invalid hex characters (space)' do
          expect_invalid_uuid('550e8400-e29b-41d4-a716-44665544000 ', 'Invalid UUID format')
        end

        it 'should reject empty string' do
          expect_invalid_uuid('', 'Invalid UUID format')
        end

        it 'should reject UUID with extra characters' do
          expect_invalid_uuid('550e8400-e29b-41d4-a716-446655440000x', 'Invalid UUID format')
        end

        it 'should reject trailing hyphen' do
          expect_invalid_uuid('550e8400-e29b-41d4-a716-44665544000-', 'Invalid UUID format')
        end

        it 'should reject hyphen inside hex pair' do
          expect_invalid_uuid('550e8400-e29b-41d4-a716-4466-5544000', 'Invalid UUID format')
        end
      end

      context 'malformed binary data on read' do
        it 'should raise error on truncated data' do
          @trans = Thrift::MemoryBufferTransport.new
          @prot = protocol_class.new(@trans)

          # Write only 10 bytes instead of 16
          if protocol_class == Thrift::JsonProtocol
            @trans.write('"00000000-0000-0000-0000"')
          else
            @trans.write("\x00" * 10)
          end

          expect { @prot.read_uuid }.to raise_error(EOFError)
        end

        it 'should raise error on 15 bytes (one byte short)' do
          @trans = Thrift::MemoryBufferTransport.new
          @prot = protocol_class.new(@trans)

          if protocol_class == Thrift::JsonProtocol
            @trans.write('"00000000-0000-0000-0000-000000000"')
          else
            @trans.write("\x00" * 15)
          end

          expect { @prot.read_uuid }.to raise_error(EOFError)
        end

        it 'should raise error on empty buffer' do
          @trans = Thrift::MemoryBufferTransport.new
          @prot = protocol_class.new(@trans)

          expect { @prot.read_uuid }.to raise_error(EOFError)
        end
      end

      context 'multiple UUIDs in sequence' do
        it 'should handle 10 UUIDs in sequence' do
          uuids = 10.times.map { |i| sprintf('%08x-0000-0000-0000-000000000000', i) }

          @trans = Thrift::MemoryBufferTransport.new
          @prot = protocol_class.new(@trans)

          uuids.each { |uuid| @prot.write_uuid(uuid) }

          results = 10.times.map { @prot.read_uuid }
          expect(results).to eq(uuids)
        end

        it 'should handle UUIDs interleaved with other types' do
          @trans = Thrift::MemoryBufferTransport.new
          @prot = protocol_class.new(@trans)

          @prot.write_message_begin('testMessage', Thrift::MessageTypes::CALL, 0)
          @prot.write_i32(42)
          @prot.write_uuid('550e8400-e29b-41d4-a716-446655440000')
          @prot.write_string('test')
          @prot.write_uuid('6ba7b810-9dad-11d1-80b4-00c04fd430c8')
          @prot.write_i64(123456789)
          @prot.write_message_end

          @prot.read_message_begin
          expect(@prot.read_i32).to eq(42)
          expect(@prot.read_uuid).to eq('550e8400-e29b-41d4-a716-446655440000')
          expect(@prot.read_string).to eq('test')
          expect(@prot.read_uuid).to eq('6ba7b810-9dad-11d1-80b4-00c04fd430c8')
          expect(@prot.read_i64).to eq(123456789)
          @prot.read_message_end
        end

        it 'should handle UUIDs in struct fields context' do
          @trans = Thrift::MemoryBufferTransport.new
          @prot = protocol_class.new(@trans)

          # Simulate struct field headers
          @prot.write_struct_begin('test')
          @prot.write_field_begin('uuid1', Thrift::Types::UUID, 1)
          @prot.write_uuid('550e8400-e29b-41d4-a716-446655440000')
          @prot.write_field_end
          @prot.write_field_begin('uuid2', Thrift::Types::UUID, 2)
          @prot.write_uuid('6ba7b810-9dad-11d1-80b4-00c04fd430c8')
          @prot.write_field_end
          @prot.write_field_stop
          @prot.write_struct_end

          @prot.read_struct_begin
          name, type, id = @prot.read_field_begin
          expect(type).to eq(Thrift::Types::UUID)
          expect(@prot.read_uuid).to eq('550e8400-e29b-41d4-a716-446655440000')
          @prot.read_field_end

          name, type, id = @prot.read_field_begin
          expect(type).to eq(Thrift::Types::UUID)
          expect(@prot.read_uuid).to eq('6ba7b810-9dad-11d1-80b4-00c04fd430c8')
          @prot.read_field_end

          name, type, id = @prot.read_field_begin
          expect(type).to eq(Thrift::Types::STOP)
        end
      end
    end
  end
end
