package main

// Phase 6: ELF Sandbox Loader — Load telosc-compiled BPF programs
//
// Parses bpf_sandbox.o generated by telosc (telos-lang/telosc/src/codegen/bpf.rs)
// and extracts verified LSM hook programs + policy maps for hot-swap loading.
//
// Expected ELF sections from telosc:
//   - maps:                telos_net_allow, telos_file_allow, telos_event_ringbuf
//   - lsm/socket_connect:  telos_check_connect
//   - lsm/file_open:       telos_file_open
//   - xdp/:                telos_xdp_bridge (optional)
//   - .data:               __telos_aarm_mac_key (SipHash signing key)

import (
	"debug/elf"
	"fmt"
	"log"
	"os"
	"strings"

	ciliumebpf "github.com/cilium/ebpf"
)

// SandboxManifest describes the contents of a telosc-compiled BPF ELF.
type SandboxManifest struct {
	ELFPath      string
	LSMHooks     []string // e.g., ["lsm/socket_connect", "lsm/file_open"]
	XDPPrograms  []string // e.g., ["xdp/telos_xdp_bridge"]
	Maps         []string // e.g., ["telos_net_allow", "telos_file_allow"]
	HasAARMKey   bool     // Whether __telos_aarm_mac_key exists
	AARMKeyValue uint64   // The MAC key value (if present)
}

// InspectSandboxELF parses a telosc-compiled BPF ELF and returns a manifest
// of its contents without loading anything into the kernel.
func InspectSandboxELF(elfPath string) (*SandboxManifest, error) {
	f, err := elf.Open(elfPath)
	if err != nil {
		return nil, fmt.Errorf("failed to open ELF: %w", err)
	}
	defer f.Close()

	manifest := &SandboxManifest{
		ELFPath: elfPath,
	}

	for _, section := range f.Sections {
		name := section.Name
		switch {
		case strings.HasPrefix(name, "lsm/"):
			manifest.LSMHooks = append(manifest.LSMHooks, name)
		case strings.HasPrefix(name, "xdp/"):
			manifest.XDPPrograms = append(manifest.XDPPrograms, name)
		case name == "maps":
			// Map definitions section — parse individual maps from symbols
		}
	}

	// Check symbols for map names and AARM key
	syms, err := f.Symbols()
	if err == nil {
		for _, sym := range syms {
			switch {
			case sym.Name == "__telos_aarm_mac_key":
				manifest.HasAARMKey = true
				// Read the key value from .data section
				if section := f.Section(".data"); section != nil {
					data, err := section.Data()
					if err == nil && len(data) >= 8 {
						// Little-endian u64
						manifest.AARMKeyValue = uint64(data[0]) |
							uint64(data[1])<<8 |
							uint64(data[2])<<16 |
							uint64(data[3])<<24 |
							uint64(data[4])<<32 |
							uint64(data[5])<<40 |
							uint64(data[6])<<48 |
							uint64(data[7])<<56
					}
				}
			case strings.HasPrefix(sym.Name, "telos_"):
				// Check if it's a map (in "maps" section)
				if sym.Section != elf.SHN_UNDEF {
					secIdx := int(sym.Section)
					if secIdx < len(f.Sections) && f.Sections[secIdx].Name == "maps" {
						manifest.Maps = append(manifest.Maps, sym.Name)
					}
				}
			}
		}
	}

	return manifest, nil
}

// LoadSandboxSpec uses cilium/ebpf to parse the BPF ELF into a
// CollectionSpec that can be loaded into the kernel.
func LoadSandboxSpec(elfPath string) (*ciliumebpf.CollectionSpec, error) {
	if _, err := os.Stat(elfPath); os.IsNotExist(err) {
		return nil, fmt.Errorf("sandbox ELF not found: %s", elfPath)
	}

	spec, err := ciliumebpf.LoadCollectionSpec(elfPath)
	if err != nil {
		return nil, fmt.Errorf("failed to parse BPF ELF: %w", err)
	}

	// Validate expected programs exist
	expectedProgs := []string{"telos_check_connect", "telos_file_open"}
	for _, name := range expectedProgs {
		if _, ok := spec.Programs[name]; !ok {
			log.Printf("[ELF-LOADER] WARNING: expected program '%s' not found in %s", name, elfPath)
		}
	}

	// Validate expected maps exist
	expectedMaps := []string{"telos_net_allow", "telos_file_allow"}
	for _, name := range expectedMaps {
		if _, ok := spec.Maps[name]; !ok {
			log.Printf("[ELF-LOADER] WARNING: expected map '%s' not found in %s", name, elfPath)
		}
	}

	log.Printf("[ELF-LOADER] Parsed sandbox ELF: %d programs, %d maps",
		len(spec.Programs), len(spec.Maps))

	return spec, nil
}

// cmdLoadSandbox handles the LOAD_SANDBOX IPC command.
func (d *TelosDaemon) cmdLoadSandbox(data map[string]interface{}) IPCResponse {
	elfPath, ok := data["elf_path"].(string)
	if !ok || elfPath == "" {
		return IPCResponse{Success: false, Error: "Missing or invalid 'elf_path'"}
	}

	// 1. Inspect the ELF for manifest info
	manifest, err := InspectSandboxELF(elfPath)
	if err != nil {
		return IPCResponse{Success: false, Error: fmt.Sprintf("ELF inspection failed: %v", err)}
	}

	log.Printf("[ELF-LOADER] Sandbox manifest: hooks=%v maps=%v aarm_key=%v",
		manifest.LSMHooks, manifest.Maps, manifest.HasAARMKey)

	// 2. Parse with cilium/ebpf
	spec, err := LoadSandboxSpec(elfPath)
	if err != nil {
		return IPCResponse{Success: false, Error: fmt.Sprintf("ELF load failed: %v", err)}
	}

	// 3. Return manifest info (actual kernel loading is a separate step
	// that requires detaching current hooks first — dangerous in production)
	result := map[string]interface{}{
		"elf_path":     elfPath,
		"lsm_hooks":   manifest.LSMHooks,
		"xdp_programs": manifest.XDPPrograms,
		"maps":         manifest.Maps,
		"has_aarm_key": manifest.HasAARMKey,
		"program_count": len(spec.Programs),
		"map_count":     len(spec.Maps),
		"status":       "parsed_and_verified",
	}

	log.Printf("[ELF-LOADER] Sandbox ELF verified successfully: %s", elfPath)
	return IPCResponse{Success: true, Data: result}
}
