package parser

import (
	"encoding/binary"
	"fmt"
)

// H2Magic est la préface HTTP/2 client (RFC 7540 §3.5), exportée pour usage
// par le routeur Magic Bytes (package dispatcher) et les consommateurs RingBuffer.
const H2Magic = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"

// h2MagicPrefaceLen est la longueur du préambule HTTP/2 client.
const h2MagicPrefaceLen = 24

// h2MagicPreface est le préambule ("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n") envoyé
// par tout client HTTP/2 avant la première frame SETTINGS.
var h2MagicPreface = []byte(H2Magic)

// Identifiants de types de frames HTTP/2 (RFC 7540, §11.2).
const (
	h2FrameData         = 0
	h2FrameHeaders      = 1
	h2FramePriority     = 2
	h2FrameRSTStream    = 3
	h2FrameSettings     = 4
	h2FramePushPromise  = 5
	h2FramePing         = 6
	h2FrameGoAway       = 7
	h2FrameWindowUpdate = 8
	h2FrameContinuation = 9
)

// Identifiants des paramètres SETTINGS (RFC 7540, §11.3).
const (
	h2SettingHeaderTableSize      = 1
	h2SettingEnablePush           = 2
	h2SettingMaxConcurrentStreams = 3
	h2SettingInitialWindowSize    = 4
	h2SettingMaxFrameSize         = 5
	h2SettingMaxHeaderListSize    = 6
)

// h2FrameHeader représente l'en-tête fixe de 9 octets d'une frame HTTP/2.
type h2FrameHeader struct {
	Length   uint32 // longueur du payload (3 octets)
	Type     uint8  // type de frame
	Flags    uint8  // flags
	StreamID uint32 // identifiant de stream (masque 0x7FFFFFFF)
}

// parseH2FrameHeader décode l'en-tête de 9 octets d'une frame HTTP/2.
func parseH2FrameHeader(data []byte) (h2FrameHeader, error) {
	if len(data) < 9 {
		return h2FrameHeader{}, fmt.Errorf("données insuffisantes pour l'en-tête frame HTTP/2: %d octets", len(data))
	}
	// Longueur sur 3 octets big-endian
	length := uint32(data[0])<<16 | uint32(data[1])<<8 | uint32(data[2])
	return h2FrameHeader{
		Length:   length,
		Type:     data[3],
		Flags:    data[4],
		StreamID: binary.BigEndian.Uint32(data[5:9]) & 0x7FFFFFFF,
	}, nil
}

// DetectH2Preface vérifie si le buffer commence par le préambule HTTP/2.
func DetectH2Preface(data []byte) bool {
	if len(data) < h2MagicPrefaceLen {
		return false
	}
	for i := 0; i < h2MagicPrefaceLen; i++ {
		if data[i] != h2MagicPreface[i] {
			return false
		}
	}
	return true
}

// H2MagicPrefaceLen retourne la longueur du préambule HTTP/2.
func H2MagicPrefaceLen() int {
	return h2MagicPrefaceLen
}

// HTTP2Settings contient les paramètres SETTINGS et WINDOW_UPDATE du client HTTP/2.
type HTTP2Settings struct {
	HeaderTableSize        int32    // SETTINGS_HEADER_TABLE_SIZE (-1 si absent)
	EnablePush             int32    // SETTINGS_ENABLE_PUSH
	MaxConcurrentStreams   int32    // SETTINGS_MAX_CONCURRENT_STREAMS
	InitialWindowSize      int32    // SETTINGS_INITIAL_WINDOW_SIZE
	MaxFrameSize           int32    // SETTINGS_MAX_FRAME_SIZE
	MaxHeaderListSize      int32    // SETTINGS_MAX_HEADER_LIST_SIZE
	UnknownSettings        int32    // paramètre 0x7 (JA4H2)
	WindowUpdateIncrement  uint32   // valeur WINDOW_UPDATE sur stream 0
	PseudoHeaderOrder      []string // ordre des pseudo-headers [:method, :authority, ...]
}

// ParseH2ClientPreface extrait les paramètres SETTINGS et le WINDOW_UPDATE
// depuis le flux HTTP/2 déchiffré du client.
// data doit commencer APRÈS le magic preface (offset 24).
func ParseH2ClientPreface(data []byte) (*HTTP2Settings, error) {
	settings := &HTTP2Settings{
		HeaderTableSize:      -1,
		EnablePush:           -1,
		MaxConcurrentStreams: -1,
		InitialWindowSize:    -1,
		MaxFrameSize:         -1,
		MaxHeaderListSize:    -1,
		UnknownSettings:      -1,
	}

	offset := 0
	// Parser au maximum 10 frames pour éviter une boucle infinie
	for frameIdx := 0; frameIdx < 10 && offset < len(data); frameIdx++ {
		if offset+9 > len(data) {
			break
		}

		hdr, err := parseH2FrameHeader(data[offset:])
		if err != nil {
			break
		}
		offset += 9

		payloadEnd := offset + int(hdr.Length)
		if payloadEnd > len(data) {
			break
		}
		payload := data[offset:payloadEnd]
		offset = payloadEnd

		switch hdr.Type {
		case h2FrameSettings:
			// Parser uniquement les SETTINGS du client (stream 0)
			if hdr.StreamID == 0 {
				pairs, err := parseH2SettingsFrame(payload)
				if err != nil {
					continue
				}
				for id, val := range pairs {
					switch id {
					case h2SettingHeaderTableSize:
						settings.HeaderTableSize = int32(val)
					case h2SettingEnablePush:
						settings.EnablePush = int32(val)
					case h2SettingMaxConcurrentStreams:
						settings.MaxConcurrentStreams = int32(val)
					case h2SettingInitialWindowSize:
						settings.InitialWindowSize = int32(val)
					case h2SettingMaxFrameSize:
						settings.MaxFrameSize = int32(val)
					case h2SettingMaxHeaderListSize:
						settings.MaxHeaderListSize = int32(val)
					case 7: // paramètre non standard (JA4H2)
						settings.UnknownSettings = int32(val)
					}
				}
			}

		case h2FrameWindowUpdate:
			// WINDOW_UPDATE sur stream 0 = flux de connexion
			if hdr.StreamID == 0 && len(payload) >= 4 {
				settings.WindowUpdateIncrement = binary.BigEndian.Uint32(payload[0:4]) & 0x7FFFFFFF
			}

		case h2FrameHeaders:
			// Extraire l'ordre des pseudo-headers depuis le premier bloc HEADERS
			if hdr.StreamID > 0 && len(settings.PseudoHeaderOrder) == 0 {
				settings.PseudoHeaderOrder = ParseH2PseudoHeaders(payload)
			}
		}
	}

	return settings, nil
}

// parseH2SettingsFrame extrait les paires (identifiant, valeur) d'une frame SETTINGS.
// Chaque paire fait 6 octets : identifiant(2) + valeur(4).
func parseH2SettingsFrame(payload []byte) (map[uint16]uint32, error) {
	if len(payload)%6 != 0 {
		return nil, fmt.Errorf("longueur de frame SETTINGS invalide: %d (doit être multiple de 6)", len(payload))
	}
	result := make(map[uint16]uint32)
	for i := 0; i+6 <= len(payload); i += 6 {
		id  := binary.BigEndian.Uint16(payload[i : i+2])
		val := binary.BigEndian.Uint32(payload[i+2 : i+6])
		result[id] = val
	}
	return result, nil
}

// ParseH2PseudoHeaders extrait l'ordre des pseudo-headers depuis un bloc HPACK.
//
// Implémentation simplifiée : détecte les pseudo-headers via les index HPACK statiques.
// Table statique HPACK (RFC 7541, Annexe A) — index pertinents :
//   1  :authority
//   2  :method = GET
//   3  :method = POST
//   4  :path = /
//   5  :path = /index.html
//   6  :scheme = http
//   7  :scheme = https
func ParseH2PseudoHeaders(headersBlock []byte) []string {
	// Index HPACK statique → pseudo-header
	hpackStaticPseudo := map[int]string{
		1: ":authority",
		2: ":method",
		3: ":method",
		4: ":path",
		5: ":path",
		6: ":scheme",
		7: ":scheme",
	}

	seen := make(map[string]bool)
	var order []string
	offset := 0

	for offset < len(headersBlock) {
		b := headersBlock[offset]

		// Représentation indexée (bit 7 = 1) : RFC 7541 §6.1
		if b&0x80 != 0 {
			idx := int(b & 0x7F)
			if name, ok := hpackStaticPseudo[idx]; ok {
				if !seen[name] {
					seen[name] = true
					order = append(order, name)
				}
			} else if idx == 0 {
				// Fin de la liste d'index ou encodage multi-octets
				offset++
				continue
			} else {
				// Index dynamique ou non-pseudo-header : arrêter le scan
				break
			}
			offset++
			continue
		}

		// Représentation littérale avec index incrémental (bits 7-6 = 01) : RFC 7541 §6.2.1
		if b&0xC0 == 0x40 {
			idx := int(b & 0x3F)
			if name, ok := hpackStaticPseudo[idx]; ok {
				if !seen[name] {
					seen[name] = true
					order = append(order, name)
				}
			}
			offset++
			// Sauter la valeur (longueur + contenu)
			if offset >= len(headersBlock) {
				break
			}
			valueLen := int(headersBlock[offset] & 0x7F) // ignorer le bit Huffman
			offset += 1 + valueLen
			continue
		}

		// Tout autre encodage : arrêter (ce n'est probablement plus un pseudo-header)
		break
	}

	return order
}
