package state

import (
	"encoding/json"
	"fmt"
	"log"
	"os"
	"strconv"
	"sync"
	"time"
)

/* --------------------------------------------------------------------------
   CONFIG STRUCT
----------------------------------------------------------------------------*/

type Config struct {
	VMID int `json:"vmid"`

	// Container identity
	ContainerIP   string `json:"container_ip,omitempty"`
	ContainerType string `json:"container_type,omitempty"`

	// Dev runtime (only for dev containers)
	Runtime         string `json:"runtime,omitempty"`
	InternalProfile string `json:"internal_profile,omitempty"`
	Version         string `json:"version"`

	// OPTIONAL addons (role-agnostic)
	Addons           []string `json:"addons,omitempty"`
	EnableCodeServer bool     `json:"enable_code_server,omitempty"`

	Game         string `json:"game"`
	Variant      string `json:"variant"`
	World        string `json:"world"`
	Ports        []int  `json:"ports"`
	ArtifactPath string `json:"artifact_path"`
	JavaPath     string `json:"java_path"`
	MemoryMB     int    `json:"memory_mb"`

	// Steam + admin credentials
	SteamUser string `json:"steam_user,omitempty"`
	SteamPass string `json:"steam_pass,omitempty"`
	SteamAuth string `json:"steam_auth,omitempty"`

	AdminUser string `json:"admin_user,omitempty"`
	AdminPass string `json:"admin_pass,omitempty"`
}

/* --------------------------------------------------------------------------
   AGENT STATE ENUM
----------------------------------------------------------------------------*/

type AgentState string

const (
	StateIdle       AgentState = "idle"
	StateInstalling AgentState = "installing"
	StateStarting   AgentState = "starting"
	StateRunning    AgentState = "running"
	StateStopping   AgentState = "stopping"
	StateCrashed    AgentState = "crashed"
	StateError      AgentState = "error"
)

/* --------------------------------------------------------------------------
   GLOBAL STATE STRUCT
----------------------------------------------------------------------------*/

type agentStatus struct {
	mu              sync.Mutex
	state           AgentState
	lastChange      time.Time
	installStep     string
	lastError       error
	crashCount      int
	lastCrash       time.Time
	lastCrashInfo   *CrashInfo
	intentionalStop bool
	ready           bool
	readySource     string
	readyError      string
	lastReadyAt     time.Time
	operation       OperationInfo
}

type CrashInfo struct {
	Time           time.Time `json:"time"`
	ExitCode       int       `json:"exitCode"`
	Signal         int       `json:"signal"`
	UptimeSeconds  int64     `json:"uptimeSeconds"`
	Classification string    `json:"classification"`
	LogTail        []string  `json:"logTail"`
}

type OperationInfo struct {
	InProgress  bool      `json:"inProgress"`
	Type        string    `json:"type,omitempty"`
	Maintenance bool      `json:"maintenance"`
	StartedAt   time.Time `json:"startedAt,omitempty"`
	Message     string    `json:"message,omitempty"`
}

var global = &agentStatus{
	state:      StateIdle,
	lastChange: time.Now(),
}

func stateLogf(format string, args ...any) {
	if cfg, err := LoadConfig(); err == nil && cfg != nil {
		log.Printf("[state] vmid=%d "+format, append([]any{cfg.VMID}, args...)...)
		return
	}
	log.Printf("[state] "+format, args...)
}

/* --------------------------------------------------------------------------
   STATE GETTERS
----------------------------------------------------------------------------*/

func GetState() AgentState {
	global.mu.Lock()
	defer global.mu.Unlock()
	return global.state
}

func GetInstallStep() string {
	global.mu.Lock()
	defer global.mu.Unlock()
	return global.installStep
}

func GetError() error {
	global.mu.Lock()
	defer global.mu.Unlock()
	return global.lastError
}

func GetCrashCount() int {
	global.mu.Lock()
	defer global.mu.Unlock()
	return global.crashCount
}

func ResetCrashCount() {
	global.mu.Lock()
	defer global.mu.Unlock()
	global.crashCount = 0
}

func GetLastChange() time.Time {
	global.mu.Lock()
	defer global.mu.Unlock()
	return global.lastChange
}

func GetReady() bool {
	global.mu.Lock()
	defer global.mu.Unlock()
	return global.ready
}

func GetReadySource() string {
	global.mu.Lock()
	defer global.mu.Unlock()
	return global.readySource
}

func GetReadyError() string {
	global.mu.Lock()
	defer global.mu.Unlock()
	return global.readyError
}

func GetLastReadyAt() time.Time {
	global.mu.Lock()
	defer global.mu.Unlock()
	return global.lastReadyAt
}

func GetLastCrash() *CrashInfo {
	global.mu.Lock()
	defer global.mu.Unlock()

	if global.lastCrashInfo == nil {
		return nil
	}

	copyInfo := *global.lastCrashInfo
	if global.lastCrashInfo.LogTail != nil {
		copyInfo.LogTail = append([]string(nil), global.lastCrashInfo.LogTail...)
	}
	return &copyInfo
}

func IsIntentionalStop() bool {
	global.mu.Lock()
	defer global.mu.Unlock()
	return global.intentionalStop
}

func GetOperation() OperationInfo {
	global.mu.Lock()
	defer global.mu.Unlock()
	return global.operation
}

/* --------------------------------------------------------------------------
   STATE SETTERS — unified with logging
----------------------------------------------------------------------------*/

func SetState(s AgentState) {
	global.mu.Lock()
	defer global.mu.Unlock()

	if global.state != s {
		stateLogf("%s -> %s", global.state, s)
		global.state = s
		global.lastChange = time.Now()
	}
}

func SetInstallStep(step string) {
	global.mu.Lock()
	defer global.mu.Unlock()
	global.installStep = step
}

func SetError(err error) {
	global.mu.Lock()
	defer global.mu.Unlock()
	global.lastError = err
}

func MarkIntentionalStop() {
	global.mu.Lock()
	defer global.mu.Unlock()
	global.intentionalStop = true
}

func ClearIntentionalStop() {
	global.mu.Lock()
	defer global.mu.Unlock()
	global.intentionalStop = false
}

func SetLastCrash(info *CrashInfo) {
	global.mu.Lock()
	defer global.mu.Unlock()

	if info == nil {
		global.lastCrashInfo = nil
		return
	}

	copyInfo := *info
	if info.LogTail != nil {
		copyInfo.LogTail = append([]string(nil), info.LogTail...)
	}
	global.lastCrashInfo = &copyInfo
}

func RecordCrash(err error) {
	global.mu.Lock()
	defer global.mu.Unlock()

	stateLogf("crash recorded err=%v", err)

	global.state = StateCrashed
	global.lastError = err
	global.crashCount++
	global.lastCrash = time.Now()
	global.intentionalStop = false
	global.ready = false
	global.readySource = ""
	global.readyError = fmt.Sprintf("%v", err)
	global.lastReadyAt = time.Time{}
}

func SetReadyState(ready bool, source, errText string) {
	global.mu.Lock()
	defer global.mu.Unlock()

	global.ready = ready
	global.readySource = source
	global.readyError = errText
	if ready {
		global.lastReadyAt = time.Now()
	} else {
		global.lastReadyAt = time.Time{}
	}
}

func TryStartOperation(opType string, maintenance bool, message string) (func(), bool, OperationInfo) {
	global.mu.Lock()
	defer global.mu.Unlock()

	if global.operation.InProgress {
		return nil, false, global.operation
	}

	global.operation = OperationInfo{
		InProgress:  true,
		Type:        opType,
		Maintenance: maintenance,
		StartedAt:   time.Now().UTC(),
		Message:     message,
	}

	end := func() {
		global.mu.Lock()
		defer global.mu.Unlock()
		if global.operation.Type == opType {
			global.operation = OperationInfo{}
		}
	}

	return end, true, global.operation
}

func SetOperationMessage(message string) {
	global.mu.Lock()
	defer global.mu.Unlock()
	if global.operation.InProgress {
		global.operation.Message = message
	}
}

/* --------------------------------------------------------------------------
   CONFIG SAVE / LOAD
----------------------------------------------------------------------------*/

const configPath = "/opt/zlh-agent/config/payload.json"

func SaveConfig(cfg *Config) error {
	if err := os.MkdirAll("/opt/zlh-agent/config", 0o755); err != nil {
		return err
	}

	b, err := json.MarshalIndent(cfg, "", "  ")
	if err != nil {
		return err
	}

	return os.WriteFile(configPath, b, 0o644)
}

func LoadConfig() (*Config, error) {
	b, err := os.ReadFile(configPath)
	if err != nil {
		return nil, err
	}

	var cfg Config
	if err := json.Unmarshal(b, &cfg); err != nil {
		return nil, err
	}

	if cfg.ContainerType == "" {
		vmidStr := strconv.Itoa(cfg.VMID)
		if len(vmidStr) > 0 {
			switch vmidStr[0] {
			case '6':
				cfg.ContainerType = "dev"
			case '5':
				cfg.ContainerType = "game"
			}
		}
		if cfg.ContainerType == "" && cfg.Runtime != "" {
			cfg.ContainerType = "dev"
		}
		if cfg.ContainerType == "" {
			cfg.ContainerType = "game"
		}
	}

	return &cfg, nil
}
