package updater

import (
	"bufio"
	"fmt"
	"strings"
)

// ParseSHA256Sums turns the body of a sha256sum-format file into a
// filename → hex-digest map. Format per line:
//
//	<64 hex chars><whitespace><filename>
//
// Anything else (blank lines, comments starting with '#') is
// tolerated. Returns an error only when a line that LOOKS like an
// entry is malformed — silent skipping of garbage would be the wrong
// failure mode for a security-relevant input.
//
// Used by `banger update` after downloading the SHA256SUMS file
// alongside the release tarball: look up the tarball's basename in
// the resulting map to get its expected hash.
func ParseSHA256Sums(body []byte) (map[string]string, error) {
	out := map[string]string{}
	scanner := bufio.NewScanner(strings.NewReader(string(body)))
	scanner.Buffer(make([]byte, 64*1024), 64*1024)
	lineNo := 0
	for scanner.Scan() {
		lineNo++
		line := strings.TrimSpace(scanner.Text())
		if line == "" || strings.HasPrefix(line, "#") {
			continue
		}
		// Tolerate the BSD-style `SHA256 (file) = hex` form too —
		// some signing pipelines emit it. The GNU-style is what
		// `sha256sum` defaults to.
		if rest, ok := strings.CutPrefix(line, "SHA256 ("); ok {
			closingParen := strings.Index(rest, ")")
			eq := strings.LastIndex(rest, "= ")
			if closingParen <= 0 || eq <= closingParen {
				return nil, fmt.Errorf("line %d: malformed BSD-style sum line", lineNo)
			}
			file := strings.TrimSpace(rest[:closingParen])
			digest := strings.TrimSpace(rest[eq+2:])
			if !looksLikeSHA256(digest) {
				return nil, fmt.Errorf("line %d: digest %q is not a 64-char hex sha256", lineNo, digest)
			}
			out[file] = strings.ToLower(digest)
			continue
		}
		fields := strings.Fields(line)
		if len(fields) < 2 {
			return nil, fmt.Errorf("line %d: expected `<digest>  <filename>`, got %q", lineNo, line)
		}
		digest := fields[0]
		// GNU format may prefix the filename with `*` for binary mode
		// (`<digest>  *file`) or a leading space; trim it.
		filename := strings.TrimSpace(strings.Join(fields[1:], " "))
		filename = strings.TrimPrefix(filename, "*")
		if !looksLikeSHA256(digest) {
			return nil, fmt.Errorf("line %d: digest %q is not a 64-char hex sha256", lineNo, digest)
		}
		out[filename] = strings.ToLower(digest)
	}
	if err := scanner.Err(); err != nil {
		return nil, err
	}
	if len(out) == 0 {
		return nil, fmt.Errorf("SHA256SUMS body contained no entries")
	}
	return out, nil
}

// looksLikeSHA256 returns true when s is exactly 64 hex characters.
// Doesn't check that those bytes are themselves a valid digest of
// anything — that's the cryptographic verifier's job, not the
// parser's.
func looksLikeSHA256(s string) bool {
	if len(s) != 64 {
		return false
	}
	for _, c := range s {
		switch {
		case c >= '0' && c <= '9':
		case c >= 'a' && c <= 'f':
		case c >= 'A' && c <= 'F':
		default:
			return false
		}
	}
	return true
}
