// Licensed to 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. Apache Software Foundation (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.

package frame

import (
	"encoding/binary"
	"fmt"
)

// ValidateHeader is the fail-loud preflight a decoder calls before parsing any
// column data. It is the SOLE technical guard for the raw-wire hard-cutover
// model: a frame whose magic does not match the codec's Magic, or whose
// wire-version is not the codec's WireVersion, is rejected loudly here — never
// silently mis-decoded.
//
// It does NOT parse the columnar body; that is Decode's job. Returns the parsed
// Header, the number of bytes consumed from b (so the caller can slice
// b[bytesRead:] to reach the first column block), and a non-nil error wrapping
// one of ErrTruncated, ErrBadMagic, ErrBadVersion on any failure.
func (c Codec) ValidateHeader(b []byte) (Header, int, error) {
	if len(b) < MinHeaderLen {
		return Header{}, 0, fmt.Errorf("%w: have %d bytes, need at least %d", ErrTruncated, len(b), MinHeaderLen)
	}
	var h Header
	copy(h.Magic[:], b[:MagicLen])
	if h.Magic != c.Magic {
		return Header{}, 0, fmt.Errorf("%w: got %#x, want %#x", ErrBadMagic, h.Magic[:], c.Magic[:])
	}
	h.WireVersion = b[MagicLen]
	if h.WireVersion != c.WireVersion {
		return Header{}, 0, fmt.Errorf("%w: got %d, want %d", ErrBadVersion, h.WireVersion, c.WireVersion)
	}
	offset := MagicLen + 1
	nrows, nrowsLen := binary.Uvarint(b[offset:])
	if nrowsLen <= 0 {
		return Header{}, 0, fmt.Errorf("%w: malformed NumRows varint at offset %d", ErrTruncated, offset)
	}
	h.NumRows = nrows
	offset += nrowsLen
	ncols, ncolsLen := binary.Uvarint(b[offset:])
	if ncolsLen <= 0 {
		return Header{}, 0, fmt.Errorf("%w: malformed NumCols varint at offset %d", ErrTruncated, offset)
	}
	h.NumCols = ncols
	offset += ncolsLen
	// Bound NumRows/NumCols by the body length before any column is parsed. A
	// frame of length L can encode at most L rows (each row costs at least one
	// byte across its columns) and at most L-offset columns (each column block is
	// at least a role+type byte). Without this an adversarial/corrupt header with
	// a near-2^64 NumRows would (a) overflow the (NumRows+7)/8 validity-byte math
	// and (b) drive make([]bool, NumRows) into an OOM panic in readValidityBitmap.
	if h.NumRows > uint64(len(b)) {
		return Header{}, 0, fmt.Errorf("%w: NumRows=%d exceeds frame length %d", ErrTruncated, h.NumRows, len(b))
	}
	if h.NumCols > uint64(len(b)-offset) {
		return Header{}, 0, fmt.Errorf("%w: NumCols=%d exceeds remaining %d bytes", ErrTruncated, h.NumCols, len(b)-offset)
	}
	return h, offset, nil
}
