//go:build windows

package divert

import (
	"errors"
	"fmt"

	idivert "github.com/imgk/divert-go"
)

// Handle wraps a WinDivert handle.
type Handle struct {
	h *idivert.Handle
}

// Open opens a WinDivert handle at NETWORK layer for outbound capture.
// The filter expression is the standard WinDivert syntax (see
// internal/divert/filter.go for our builder).
//
// Returns ErrAccessDenied when the calling process is not elevated.
// Returns ErrDriverFailedPriorUnload when an outdated WinDivert
// (e.g. v1.x from zapret) is already loaded.
func Open(filter string) (*Handle, error) {
	h, err := idivert.Open(filter, idivert.LayerNetwork, 0, 0)
	if err != nil {
		return nil, mapWinDivertErr(err)
	}
	return &Handle{h: h}, nil
}

// Close closes the handle. Safe to call multiple times.
func (h *Handle) Close() error {
	if h == nil || h.h == nil {
		return nil
	}
	err := h.h.Close()
	h.h = nil
	return err
}

// Recv blocks until a packet arrives that matches the filter, or until
// the handle is closed (Close from another goroutine returns
// ErrShutdown to the recv'er). buf must be sized for a full Ethernet
// MTU (~1600 bytes is fine).
//
// Returns the captured packet length, the WinDivertAddress (containing
// direction, interface index, etc), and any error.
func (h *Handle) Recv(buf []byte) (int, *idivert.Address, error) {
	if h == nil || h.h == nil {
		return 0, nil, errors.New("handle closed")
	}
	addr := new(idivert.Address)
	n, err := h.h.Recv(buf, addr)
	if err != nil {
		return 0, nil, mapWinDivertErr(err)
	}
	return int(n), addr, nil
}

// Send reinjects a packet. The address typically comes from a previous
// Recv call (so the kernel knows whether it's outbound or inbound, which
// interface, etc).
func (h *Handle) Send(buf []byte, addr *idivert.Address) (int, error) {
	if h == nil || h.h == nil {
		return 0, errors.New("handle closed")
	}
	n, err := h.h.Send(buf, addr)
	if err != nil {
		return 0, mapWinDivertErr(err)
	}
	return int(n), nil
}

// Sentinel errors mapped from raw Windows errors so the engine layer
// can pattern-match without importing windows package.
var (
	ErrAccessDenied            = errors.New("WinDivert: access denied (need admin)")
	ErrDriverFailedPriorUnload = errors.New("WinDivert: outdated driver from another tool is loaded; reboot or stop the other tool first")
	ErrInvalidHandle           = errors.New("WinDivert: handle invalidated (driver crashed?)")
	ErrShutdown                = errors.New("WinDivert: shutdown")
)

func mapWinDivertErr(err error) error {
	if err == nil {
		return nil
	}
	msg := err.Error()
	switch {
	case contains(msg, "access is denied"), contains(msg, "ACCESS_DENIED"):
		return ErrAccessDenied
	case contains(msg, "FAILED_PRIOR_UNLOAD"), contains(msg, "prior unload"):
		return ErrDriverFailedPriorUnload
	case contains(msg, "INVALID_HANDLE"):
		return ErrInvalidHandle
	case contains(msg, "SHUTDOWN"):
		return ErrShutdown
	}
	return fmt.Errorf("WinDivert: %w", err)
}

func contains(s, sub string) bool {
	// case-insensitive
	if len(sub) == 0 {
		return true
	}
	if len(s) < len(sub) {
		return false
	}
	for i := 0; i+len(sub) <= len(s); i++ {
		match := true
		for j := 0; j < len(sub); j++ {
			a, b := s[i+j], sub[j]
			if a >= 'A' && a <= 'Z' {
				a += 32
			}
			if b >= 'A' && b <= 'Z' {
				b += 32
			}
			if a != b {
				match = false
				break
			}
		}
		if match {
			return true
		}
	}
	return false
}
