package protocol

import (
	"encoding/json"
	"fmt"
	"strings"
	"time"
)

const CurrentProtocolVersion = 1

// MessageType identifies a versioned agent/control-plane message.
type MessageType string

const (
	// Agent → Control Plane
	TypeAgentHello              MessageType = "agent.hello"
	TypeAgentHeartbeat          MessageType = "agent.heartbeat"
	TypeAgentInventory          MessageType = "agent.inventory"
	TypeServerState             MessageType = "server.state"
	TypeServerMetrics           MessageType = "server.metrics"
	TypeServerLog               MessageType = "server.log"
	TypeServerOperationProgress MessageType = "server.operation.progress"
	TypeServerOperationComplete MessageType = "server.operation.completed"
	TypeServerOperationFailed   MessageType = "server.operation.failed"

	// Control Plane → Agent
	TypeServerProvision       MessageType = "server.provision"
	TypeServerInstall         MessageType = "server.install"
	TypeServerStart           MessageType = "server.start"
	TypeServerStop            MessageType = "server.stop"
	TypeServerKill            MessageType = "server.kill"
	TypeServerDelete          MessageType = "server.delete"
	TypeServerInspect         MessageType = "server.inspect"
	TypeServerCommand         MessageType = "server.command"
	TypeServerBackupPrepare   MessageType = "server.backup.prepare"
	TypeServerBackupRelease   MessageType = "server.backup.release"
	TypeServerFilesList       MessageType = "server.files.list"
	TypeServerFilesRead       MessageType = "server.files.read"
	TypeServerFilesWrite      MessageType = "server.files.write"
	TypeServerFilesUploadDone MessageType = "server.files.upload.complete"
	TypeServerWorldValidate   MessageType = "server.world.validate"
	TypeServerMetricsSub      MessageType = "server.metrics.subscribe"
	TypeServerLogsSubscribe   MessageType = "server.logs.subscribe"
)

var agentToControlTypes = map[MessageType]struct{}{
	TypeAgentHello:              {},
	TypeAgentHeartbeat:          {},
	TypeAgentInventory:          {},
	TypeServerState:             {},
	TypeServerMetrics:           {},
	TypeServerLog:               {},
	TypeServerOperationProgress: {},
	TypeServerOperationComplete: {},
	TypeServerOperationFailed:   {},
}

var controlToAgentTypes = map[MessageType]struct{}{
	TypeServerProvision:       {},
	TypeServerInstall:         {},
	TypeServerStart:           {},
	TypeServerStop:            {},
	TypeServerKill:            {},
	TypeServerDelete:          {},
	TypeServerInspect:         {},
	TypeServerCommand:         {},
	TypeServerBackupPrepare:   {},
	TypeServerBackupRelease:   {},
	TypeServerFilesList:       {},
	TypeServerFilesRead:       {},
	TypeServerFilesWrite:      {},
	TypeServerFilesUploadDone: {},
	TypeServerWorldValidate:   {},
	TypeServerMetricsSub:      {},
	TypeServerLogsSubscribe:   {},
}

// Envelope is the common wrapper for all protocol messages.
type Envelope struct {
	ProtocolVersion int             `json:"protocolVersion"`
	MessageID       string          `json:"messageId"`
	CorrelationID   string          `json:"correlationId,omitempty"`
	Type            MessageType     `json:"type"`
	Timestamp       time.Time       `json:"timestamp"`
	Payload         json.RawMessage `json:"payload"`
}

// OperationMeta is shared metadata for lifecycle operations.
type OperationMeta struct {
	ServerID       string     `json:"serverId"`
	Generation     int64      `json:"generation"`
	Deadline       *time.Time `json:"deadline,omitempty"`
	IdempotencyKey string     `json:"idempotencyKey,omitempty"`
}

// AgentHelloPayload is sent when the agent connects to the control plane.
type AgentHelloPayload struct {
	NodeID         string `json:"nodeId"`
	AgentVersion   string `json:"agentVersion"`
	ProtocolVersion int   `json:"protocolVersion"`
	Hostname       string `json:"hostname,omitempty"`
	Capabilities   []string `json:"capabilities,omitempty"`
}

// AgentHeartbeatPayload reports node health and resource usage.
type AgentHeartbeatPayload struct {
	NodeID           string  `json:"nodeId"`
	CPUUsagePercent  float64 `json:"cpuUsagePercent"`
	MemoryUsedBytes  int64   `json:"memoryUsedBytes"`
	MemoryTotalBytes int64   `json:"memoryTotalBytes"`
	DiskUsedBytes    int64   `json:"diskUsedBytes"`
	DiskTotalBytes   int64   `json:"diskTotalBytes"`
	RunningServers   int     `json:"runningServers"`
}

// ServerStartPayload instructs the agent to start a game server.
type ServerStartPayload struct {
	OperationMeta
	DesiredGeneration int64 `json:"desiredGeneration"`
}

// ServerProvisionPayload instructs the agent to create a Minecraft server container.
type ServerProvisionPayload struct {
	OperationMeta
	Edition          string `json:"edition"`
	SoftwareFamily   string `json:"softwareFamily"`
	MinecraftVersion string `json:"minecraftVersion"`
	HostPort         int    `json:"hostPort"`
	RamMb            int    `json:"ramMb"`
	DataPath         string `json:"dataPath"`
	EulaAccepted     bool   `json:"eulaAccepted"`
}

// ServerStopPayload instructs the agent to stop a running game server.
type ServerStopPayload struct {
	OperationMeta
}

// ServerOperationResultPayload reports the outcome of a lifecycle operation.
type ServerOperationResultPayload struct {
	OperationMeta
	ResultCode   string `json:"resultCode"`
	ErrorCode    string `json:"errorCode,omitempty"`
	ErrorMessage string `json:"errorMessage,omitempty"`
}

// ServerStatePayload reports the observed server state.
type ServerStatePayload struct {
	OperationMeta
	State        string `json:"state"`
	ErrorCode    string `json:"errorCode,omitempty"`
	ErrorMessage string `json:"errorMessage,omitempty"`
}

// Validate checks envelope fields and known message types.
func (e *Envelope) Validate() error {
	if e == nil {
		return fmt.Errorf("envelope is nil")
	}
	if e.ProtocolVersion < 1 {
		return fmt.Errorf("protocolVersion must be >= 1")
	}
	if e.ProtocolVersion > CurrentProtocolVersion {
		return fmt.Errorf("unsupported protocolVersion %d", e.ProtocolVersion)
	}
	if strings.TrimSpace(e.MessageID) == "" {
		return fmt.Errorf("messageId is required")
	}
	if strings.TrimSpace(string(e.Type)) == "" {
		return fmt.Errorf("type is required")
	}
	if e.Timestamp.IsZero() {
		return fmt.Errorf("timestamp is required")
	}
	if !e.Timestamp.UTC().Equal(e.Timestamp) {
		return fmt.Errorf("timestamp must be UTC")
	}
	if _, ok := agentToControlTypes[e.Type]; !ok {
		if _, ok := controlToAgentTypes[e.Type]; !ok {
			return fmt.Errorf("unknown message type: %s", e.Type)
		}
	}
	if len(e.Payload) == 0 {
		return fmt.Errorf("payload is required")
	}
	if !json.Valid(e.Payload) {
		return fmt.Errorf("payload must be valid JSON")
	}
	return nil
}

// IsAgentToControl returns true when the message flows from agent to control plane.
func (t MessageType) IsAgentToControl() bool {
	_, ok := agentToControlTypes[t]
	return ok
}

// IsControlToAgent returns true when the message flows from control plane to agent.
func (t MessageType) IsControlToAgent() bool {
	_, ok := controlToAgentTypes[t]
	return ok
}

// DecodeEnvelope parses and validates a JSON envelope.
func DecodeEnvelope(data []byte) (*Envelope, error) {
	var env Envelope
	if err := json.Unmarshal(data, &env); err != nil {
		return nil, fmt.Errorf("decode envelope: %w", err)
	}
	if err := env.Validate(); err != nil {
		return nil, err
	}
	return &env, nil
}

// NewEnvelope creates a validated outbound envelope.
func NewEnvelope(msgType MessageType, messageID string, payload any) (*Envelope, error) {
	raw, err := json.Marshal(payload)
	if err != nil {
		return nil, fmt.Errorf("marshal payload: %w", err)
	}
	env := &Envelope{
		ProtocolVersion: CurrentProtocolVersion,
		MessageID:       messageID,
		Type:            msgType,
		Timestamp:       time.Now().UTC(),
		Payload:         raw,
	}
	if err := env.Validate(); err != nil {
		return nil, err
	}
	return env, nil
}
