package sshpush

import (
	"context"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"math/rand"
	"os"
	"strings"
)

// WriteFileIdempotent writes content to peer:path atomically (write-to-tmp
// + mv, REQ-074) with mode, but only if the remote file's SHA-256 differs
// from the local content's SHA-256 (C-18 content-addressed idempotency —
// the SSH-push equivalent of the v0.8 X-Orca-Idempotency-Key).
//
// Returns written=true if the file was written, written=false if the
// content already matched (skip). The default per-SCP timeout is
// SCPTimeout (I-B-001).
//
// Atomicity: the content is written to a temp file in the same directory
// as the target, then `mv`'d into place. The temp file is mode-appended
// (e.g. `/etc/orca/foo.conf.orca-tmp-<rand>`) so the rename is atomic on
// POSIX filesystems.
func (t *Transport) WriteFileIdempotent(ctx context.Context, peer string, path string, content []byte, mode os.FileMode) (bool, error) {
	localHash := sha256Hex(content)
	remoteHash, err := t.remoteSHA256(ctx, peer, path)
	if err == nil && remoteHash != "" && strings.EqualFold(remoteHash, localHash) {
		return false, nil
	}
	if err := t.writeFile(ctx, peer, path, content, mode); err != nil {
		return false, err
	}
	return true, nil
}

// writeFile writes content to peer:path atomically (write-to-tmp + mv).
// It writes the content via a single SSH exec (cat heredoc + chmod + mv),
// keeping the transfer in one round-trip. The temp file lives next to the
// target so the rename is atomic.
func (t *Transport) writeFile(ctx context.Context, peer string, path string, content []byte, mode os.FileMode) error {
	dir, base := splitDir(path)
	tmpName := fmt.Sprintf(".orca-tmp-%s", randomToken(8))
	tmpPath := base + "/" + tmpName
	if dir == "" {
		tmpPath = tmpName
	}
	// Build the remote command: mkdir -p <dir> && cat > <tmp> <<'EOF'
	// ... EOF && chmod <mode> <tmp> && mv <tmp> <path>. The heredoc
	// delimiter is per-write random and verified absent from content
	// to prevent command injection via crafted file content.
	eof := "ORCA_PUSH_EOF_" + randomToken(16)
	for strings.Contains(string(content), eof) {
		eof = "ORCA_PUSH_EOF_" + randomToken(16)
	}
	modeStr := fmt.Sprintf("%04o", uint32(mode.Perm()))
	cmd := fmt.Sprintf(
		"mkdir -p %s && cat > %s <<'%s'\n%s\n%s\nchmod %s %s && mv -f %s %s",
		shellQuote(base),
		shellQuote(tmpPath),
		eof,
		string(content),
		eof,
		modeStr,
		shellQuote(tmpPath),
		shellQuote(tmpPath),
		shellQuote(path),
	)
	execCtx, cancel := context.WithTimeout(ctx, SCPTimeout)
	defer cancel()
	if _, err := t.execWithRetry(execCtx, peer, cmd, true); err != nil {
		return fmt.Errorf("sshpush: write %s: %w", path, err)
	}
	return nil
}

// sha256Hex returns the lowercase hex SHA-256 digest of b.
func sha256Hex(b []byte) string {
	sum := sha256.Sum256(b)
	return hex.EncodeToString(sum[:])
}

// splitDir returns the directory and the directory itself (for mkdir).
// For "/etc/orca/foo.conf" it returns ("/etc/orca", "/etc/orca"). For
// "foo.conf" it returns ("", ".").
func splitDir(path string) (dir, base string) {
	idx := strings.LastIndex(path, "/")
	if idx < 0 {
		return "", "."
	}
	return path[:idx], path[:idx]
}

// randomToken returns a random hex token of the given byte length. Used
// for temp-file naming to avoid collisions under parallel fan-out.
func randomToken(n int) string {
	b := make([]byte, n)
	for i := range b {
		b[i] = byte(rand.Intn(256))
	}
	return hex.EncodeToString(b)
}
