//go:build windows

package hcn

import (
	"errors"
	"fmt"

	"github.com/sirupsen/logrus"
	"golang.org/x/sys/windows"

	"github.com/Microsoft/hcsshim/internal/hcs"
	"github.com/Microsoft/hcsshim/internal/hcserror"
	"github.com/Microsoft/hcsshim/internal/interop"
)

var (
	errInvalidNetworkID      = errors.New("invalid network ID")
	errInvalidEndpointID     = errors.New("invalid endpoint ID")
	errInvalidNamespaceID    = errors.New("invalid namespace ID")
	errInvalidLoadBalancerID = errors.New("invalid load balancer ID")
	errInvalidRouteID        = errors.New("invalid route ID")
)

func checkForErrors(methodName string, hr error, resultBuffer *uint16) error {
	errorFound := false

	if hr != nil {
		errorFound = true
	}

	result := ""
	if resultBuffer != nil {
		result = interop.ConvertAndFreeCoTaskMemString(resultBuffer)
		if result != "" {
			errorFound = true
		}
	}

	if errorFound {
		returnError := new(hr, methodName, result)
		logrus.Debugf(returnError.Error()) // HCN errors logged for debugging.
		return returnError
	}

	return nil
}

type ErrorCode uint32

// For common errors, define the error as it is in windows, so we can quickly determine it later
const (
	ERROR_NOT_FOUND                     = ErrorCode(windows.ERROR_NOT_FOUND)
	HCN_E_PORT_ALREADY_EXISTS ErrorCode = ErrorCode(windows.HCN_E_PORT_ALREADY_EXISTS)
	HCN_E_NOTIMPL             ErrorCode = ErrorCode(windows.E_NOTIMPL)
)

type HcnError struct {
	*hcserror.HcsError
	code ErrorCode
}

func (e *HcnError) Error() string {
	return e.HcsError.Error()
}

func CheckErrorWithCode(err error, code ErrorCode) bool {
	var hcnError *HcnError
	if errors.As(err, &hcnError) {
		return hcnError.code == code
	}
	return false
}

func IsElementNotFoundError(err error) bool {
	return CheckErrorWithCode(err, ERROR_NOT_FOUND)
}

func IsPortAlreadyExistsError(err error) bool {
	return CheckErrorWithCode(err, HCN_E_PORT_ALREADY_EXISTS)
}

func IsNotImplemented(err error) bool {
	return CheckErrorWithCode(err, HCN_E_NOTIMPL)
}

func new(hr error, title string, rest string) error {
	err := &HcnError{}
	hcsError := hcserror.New(hr, title, rest)
	err.HcsError = hcsError.(*hcserror.HcsError) //nolint:errorlint
	err.code = ErrorCode(hcserror.Win32FromError(hr))
	return err
}

//
// Note that the below errors are not errors returned by hcn itself
// we wish to separate them as they are shim usage error
//

// NetworkNotFoundError results from a failed search for a network by Id or Name
type NetworkNotFoundError struct {
	NetworkName string
	NetworkID   string
}

var _ error = NetworkNotFoundError{}

func (e NetworkNotFoundError) Error() string {
	if e.NetworkName != "" {
		return fmt.Sprintf("Network name %q not found", e.NetworkName)
	}
	return fmt.Sprintf("Network ID %q not found", e.NetworkID)
}

// EndpointNotFoundError results from a failed search for an endpoint by Id or Name
type EndpointNotFoundError struct {
	EndpointName string
	EndpointID   string
}

var _ error = EndpointNotFoundError{}

func (e EndpointNotFoundError) Error() string {
	if e.EndpointName != "" {
		return fmt.Sprintf("Endpoint name %q not found", e.EndpointName)
	}
	return fmt.Sprintf("Endpoint ID %q not found", e.EndpointID)
}

// NamespaceNotFoundError results from a failed search for a namsepace by Id
type NamespaceNotFoundError struct {
	NamespaceID string
}

var _ error = NamespaceNotFoundError{}

func (e NamespaceNotFoundError) Error() string {
	return fmt.Sprintf("Namespace ID %q not found", e.NamespaceID)
}

// LoadBalancerNotFoundError results from a failed search for a loadbalancer by Id
type LoadBalancerNotFoundError struct {
	LoadBalancerId string
}

var _ error = LoadBalancerNotFoundError{}

func (e LoadBalancerNotFoundError) Error() string {
	return fmt.Sprintf("LoadBalancer %q not found", e.LoadBalancerId)
}

// RouteNotFoundError results from a failed search for a route by Id
type RouteNotFoundError struct {
	RouteId string
}

var _ error = RouteNotFoundError{}

func (e RouteNotFoundError) Error() string {
	return fmt.Sprintf("SDN Route %q not found", e.RouteId)
}

// IsNotFoundError returns a boolean indicating whether the error was caused by
// a resource not being found.
func IsNotFoundError(err error) bool {
	// Calling [errors.As] in a loop over `[]error{NetworkNotFoundError{}, ...}` will not work,
	// since the loop variable will be an interface type (ie, `error`) and `errors.As(error, *error)` will
	// always succeed.
	// Unless golang adds loops over (or arrays of) types, we need to manually call `errors.As` for
	// each potential error type.
	//
	// Also, for T = NetworkNotFoundError and co, the error implementation is for T, not *T
	if e := (NetworkNotFoundError{}); errors.As(err, &e) {
		return true
	}
	if e := (EndpointNotFoundError{}); errors.As(err, &e) {
		return true
	}
	if e := (NamespaceNotFoundError{}); errors.As(err, &e) {
		return true
	}
	if e := (LoadBalancerNotFoundError{}); errors.As(err, &e) {
		return true
	}
	if e := (RouteNotFoundError{}); errors.As(err, &e) {
		return true
	}
	if e := (&hcserror.HcsError{}); errors.As(err, &e) {
		return errors.Is(e.Err, hcs.ErrElementNotFound)
	}

	return false
}
