package mxgateway

import (
	"fmt"

	pb "gitea.dohertylan.com/dohertj2/mxaccessgw/clients/go/internal/generated"
)

// GatewayError wraps transport-level gRPC failures.
type GatewayError struct {
	// Op names the operation that failed (for example "dial" or "invoke").
	Op string
	// Err is the underlying gRPC or transport error.
	Err error
}

// Error returns the formatted gateway error message.
func (e *GatewayError) Error() string {
	if e == nil {
		return ""
	}
	if e.Op == "" {
		return fmt.Sprintf("mxgateway: %v", e.Err)
	}
	return fmt.Sprintf("mxgateway: %s failed: %v", e.Op, e.Err)
}

// Unwrap returns the wrapped transport error.
func (e *GatewayError) Unwrap() error {
	if e == nil {
		return nil
	}
	return e.Err
}

// CommandError reports a non-OK gateway protocol status and keeps the raw
// command reply when one exists.
type CommandError struct {
	// Op names the gateway operation that produced the non-OK status.
	Op string
	// Status carries the gateway-reported protocol status.
	Status *ProtocolStatus
	// Reply is the raw command reply, when one was returned alongside the status.
	Reply *MxCommandReply
}

// Error returns the formatted command error message.
func (e *CommandError) Error() string {
	if e == nil {
		return ""
	}
	status := e.Status
	if status == nil {
		return fmt.Sprintf("mxgateway: %s failed with missing protocol status", e.Op)
	}
	if status.GetMessage() == "" {
		return fmt.Sprintf("mxgateway: %s failed with protocol status %s", e.Op, status.GetCode())
	}
	return fmt.Sprintf("mxgateway: %s failed with protocol status %s: %s", e.Op, status.GetCode(), status.GetMessage())
}

// MxAccessError reports HRESULT or MXSTATUS_PROXY failures returned by MXAccess.
type MxAccessError struct {
	// Command is the wrapped CommandError when the protocol status carried one.
	Command *CommandError
	// Reply is the raw MXAccess command reply that surfaced the failure.
	Reply *MxCommandReply
}

// Error returns the formatted MXAccess error message.
func (e *MxAccessError) Error() string {
	if e == nil {
		return ""
	}
	if e.Command != nil && e.Command.Status != nil && e.Command.Status.GetMessage() != "" {
		return e.Command.Error()
	}
	if e.Reply != nil && e.Reply.GetDiagnosticMessage() != "" {
		return fmt.Sprintf("mxgateway: MXAccess command %s failed: %s", e.Reply.GetKind(), e.Reply.GetDiagnosticMessage())
	}
	if e.Reply != nil && e.Reply.Hresult != nil {
		return fmt.Sprintf("mxgateway: MXAccess command %s failed with HRESULT 0x%08X", e.Reply.GetKind(), uint32(e.Reply.GetHresult()))
	}
	return "mxgateway: MXAccess command failed"
}

// Unwrap returns the wrapped CommandError, when one is present.
func (e *MxAccessError) Unwrap() error {
	if e == nil {
		return nil
	}
	return e.Command
}

// EnsureProtocolSuccess returns a typed CommandError when status is non-OK.
func EnsureProtocolSuccess(op string, status *ProtocolStatus, reply *MxCommandReply) error {
	if status == nil || status.GetCode() == pb.ProtocolStatusCode_PROTOCOL_STATUS_CODE_OK {
		return nil
	}

	commandError := &CommandError{
		Op:     op,
		Status: status,
		Reply:  reply,
	}
	if status.GetCode() == pb.ProtocolStatusCode_PROTOCOL_STATUS_CODE_MXACCESS_FAILURE {
		return &MxAccessError{
			Command: commandError,
			Reply:   reply,
		}
	}
	return commandError
}

// EnsureMxAccessSuccess returns a typed MxAccessError for failing HRESULTs or
// MXSTATUS_PROXY entries.
func EnsureMxAccessSuccess(op string, reply *MxCommandReply) error {
	if reply == nil {
		return nil
	}
	if reply.Hresult != nil && reply.GetHresult() != 0 {
		return &MxAccessError{Reply: reply}
	}
	for _, status := range reply.GetStatuses() {
		if !StatusSucceeded(status) {
			return &MxAccessError{Reply: reply}
		}
	}
	return nil
}
