//go:build windows

package divert

import (
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"os"
	"path/filepath"
	"runtime"
	"strings"
)

// DriverPaths records where the WinDivert binaries landed after install.
type DriverPaths struct {
	SysPath string // e.g. C:\ProgramData\Drover\windivert\WinDivert64.sys
	DllPath string
}

// InstallDriver extracts the embedded WinDivert.sys + WinDivert.dll
// into %PROGRAMDATA%\Drover\windivert\ and SHA256-verifies them.
//
// On second and subsequent runs, if the existing files already match
// the embedded SHAs, the function is a no-op and just returns paths.
//
// Errors:
//   - ARM64 architecture (WinDivert doesn't support it)
//   - %PROGRAMDATA% not set or not writable
//   - SHA256 mismatch after write (driver corrupted on disk)
func InstallDriver() (*DriverPaths, error) {
	if runtime.GOARCH == "arm64" {
		return nil, fmt.Errorf("Drover requires x86-64 Windows; ARM64 is not supported (WinDivert does not ship an ARM64 driver)")
	}
	pd := os.Getenv("ProgramData")
	if pd == "" {
		return nil, fmt.Errorf("ProgramData environment variable is not set")
	}
	dst := filepath.Join(pd, "Drover", "windivert")
	return installDriverInto(dst)
}

func installDriverInto(dst string) (*DriverPaths, error) {
	if runtime.GOARCH == "arm64" {
		return nil, fmt.Errorf("Drover requires x86-64 Windows; ARM64 is not supported")
	}
	if err := os.MkdirAll(dst, 0755); err != nil {
		return nil, fmt.Errorf("create %s: %w", dst, err)
	}
	sysPath := filepath.Join(dst, "WinDivert64.sys")
	dllPath := filepath.Join(dst, "WinDivert.dll")

	if err := writeIfDifferent(sysPath, winDivertSys, WinDivertSysSHA256); err != nil {
		return nil, fmt.Errorf("install WinDivert64.sys: %w", err)
	}
	if err := writeIfDifferent(dllPath, winDivertDll, WinDivertDllSHA256); err != nil {
		return nil, fmt.Errorf("install WinDivert.dll: %w", err)
	}
	return &DriverPaths{SysPath: sysPath, DllPath: dllPath}, nil
}

// writeIfDifferent compares the existing file's SHA256 to the expected
// hash; if it matches, no-op. Otherwise overwrite atomically and verify
// the resulting on-disk SHA matches expected.
func writeIfDifferent(path string, content []byte, expectedSHA string) error {
	if existing, err := os.ReadFile(path); err == nil {
		if strings.EqualFold(sha256Hex(existing), expectedSHA) {
			return nil // already up to date
		}
	}
	tmp := path + ".new"
	if err := os.WriteFile(tmp, content, 0644); err != nil {
		return err
	}
	if err := os.Rename(tmp, path); err != nil {
		_ = os.Remove(tmp)
		return err
	}
	// Verify after write — guards against AV-on-write tampering.
	got, err := os.ReadFile(path)
	if err != nil {
		return err
	}
	if !strings.EqualFold(sha256Hex(got), expectedSHA) {
		return fmt.Errorf("SHA256 mismatch after write at %s; antivirus may have tampered with the file. Add %%PROGRAMDATA%%\\Drover\\ to your AV exclusions and restart Drover", path)
	}
	return nil
}

func sha256Hex(b []byte) string {
	h := sha256.Sum256(b)
	return hex.EncodeToString(h[:])
}
