package server

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"

	"vocat/internal/device"
	"vocat/internal/i18n"
	"vocat/internal/modem"
	"vocat/internal/store"
	"vocat/internal/vowifi"
	vowifiruntime "vocat/internal/vowifi/runtime"
)

// DeviceController is the narrow hardware boundary used by the HTTP layer.
// device.Manager implements it; tests can provide a transcript-backed fake.
type DeviceController interface {
	Discover(context.Context) ([]device.Device, error)
	List() []device.Device
	Get(string) (device.Device, error)
	Refresh(context.Context, string) (device.Snapshot, error)
	ExecuteAT(context.Context, string, string) (modem.Response, error)
	Reboot(context.Context, string) error
	USSD(context.Context, string, string) (device.USSDResult, error)
	ContinueUSSD(context.Context, string, string) (device.USSDResult, error)
	CancelUSSD(context.Context, string) error
	SetFlight(context.Context, string, bool) (device.FlightResult, error)
	SetNetwork(context.Context, string, device.NetworkRequest) (device.NetworkResult, error)
	USBNetMode(context.Context, string) (device.USBNetMode, error)
	SetUSBNetMode(context.Context, string, int) (device.USBNetMode, error)
	SetUSBNetModeByPort(context.Context, string, int) (device.USBNetMode, error)
	OperatorSelection(context.Context, string) (device.OperatorSelection, error)
	SetOperatorSelection(context.Context, string, bool, string, *int) (device.OperatorSelection, error)
	ScanOperators(context.Context, string) (device.OperatorScanResult, error)
	SendSMS(context.Context, string, string, string) (device.SMSSendResult, error)
	ListSMS(context.Context, string) ([]device.SMSMessage, error)
	ReadSMS(context.Context, string, int) (device.SMSMessage, error)
	DeleteSMS(context.Context, string, int) error
	ESIMInventory(context.Context, string) ([]device.EsimInventoryEntry, error)
	ESIMListProfiles(context.Context, string) (device.EsimInfo, error)
	ESIMSwitchProfile(context.Context, string, string, string) error
	ESIMDisableProfile(context.Context, string, string, string) error
	ESIMRenameProfile(context.Context, string, string, string, string) error
	ESIMDownloadProfile(context.Context, string, device.EsimDownloadParams, func(device.EsimProgress)) (*device.EsimDownloadResult, error)
	ESIMDeleteProfile(context.Context, string, string, string) (*device.EsimDeleteResult, error)
	ESIMChipInfo(context.Context, string) (*device.EsimChipInfo, error)
}

type deviceConfigPayload struct {
	ID                 string `json:"id"`
	Name               string `json:"name"`
	Interface          string `json:"interface"`
	ControlDevice      string `json:"control_device"`
	ATPort             string `json:"at_port"`
	USBPath            string `json:"usb_path"`
	AudioDevice        string `json:"audio_device"`
	ModemIMEI          string `json:"modem_imei"`
	APN                string `json:"apn"`
	ProxyPort          int    `json:"proxy_port"`
	BaudRate           int    `json:"baud_rate"`
	DataBits           int    `json:"data_bits"`
	StopBits           int    `json:"stop_bits"`
	Parity             string `json:"parity"`
	DeviceBackend      string `json:"device_backend"`
	ESIMTransport      string `json:"esim_transport"`
	QMIUseProxy        bool   `json:"qmi_use_proxy"`
	QMIProxyPath       string `json:"qmi_proxy_path"`
	QMIProxyExecutable string `json:"qmi_proxy_executable"`
	NetworkEnabled     bool   `json:"network_enabled"`
	SMSEnabled         bool   `json:"sms_enabled"`
	VoWiFiEnabled      bool   `json:"vowifi_enabled"`
}

func (payload deviceConfigPayload) toStoreDevice() store.Device {
	name := strings.TrimSpace(payload.Name)
	if name == "" {
		name = payload.ID
	}
	return store.Device{
		ID:                 strings.TrimSpace(payload.ID),
		Name:               name,
		Interface:          strings.TrimSpace(payload.Interface),
		ControlDevice:      strings.TrimSpace(payload.ControlDevice),
		ATPort:             strings.TrimSpace(payload.ATPort),
		USBPath:            strings.TrimSpace(payload.USBPath),
		AudioDevice:        strings.TrimSpace(payload.AudioDevice),
		ModemIMEI:          strings.TrimSpace(payload.ModemIMEI),
		APN:                strings.TrimSpace(payload.APN),
		ProxyPort:          payload.ProxyPort,
		BaudRate:           payload.BaudRate,
		DataBits:           payload.DataBits,
		StopBits:           payload.StopBits,
		Parity:             payload.Parity,
		DeviceBackend:      payload.DeviceBackend,
		ESIMTransport:      payload.ESIMTransport,
		QMIUseProxy:        payload.QMIUseProxy,
		QMIProxyPath:       strings.TrimSpace(payload.QMIProxyPath),
		QMIProxyExecutable: strings.TrimSpace(payload.QMIProxyExecutable),
		NetworkEnabled:     payload.NetworkEnabled,
		SMSEnabled:         payload.SMSEnabled,
		VoWiFiEnabled:      payload.VoWiFiEnabled,
	}
}

func validDeviceID(value string) bool {
	value = strings.TrimSpace(value)
	if len(value) < 1 || len(value) > 64 {
		return false
	}
	for index, character := range value {
		if character >= 'a' && character <= 'z' ||
			character >= 'A' && character <= 'Z' ||
			character >= '0' && character <= '9' ||
			(index > 0 && (character == '.' || character == '_' || character == '-')) {
			continue
		}
		return false
	}
	return true
}

func (s *Server) routeDeviceAPI(w http.ResponseWriter, r *http.Request) bool {
	cleanPath := strings.Trim(strings.TrimPrefix(r.URL.Path, "/api"), "/")
	switch cleanPath {
	case "dashboard/devices":
		if !requireMethod(w, r, http.MethodGet) {
			return true
		}
		writeJSON(w, http.StatusOK, map[string]any{"data": s.dashboardDevices()})
		return true
	case "devices":
		return s.handleDevices(w, r)
	case "devices/discovered":
		return s.handleDiscoveredDevices(w, r)
	case "devices/actions/rescan":
		return s.handleDeviceRescan(w, r)
	case "device-mgmt/discovered/fix-usbnet":
		return s.handleFixUSBNet(w, r)
	}

	segments := splitAPIPath(cleanPath)
	if len(segments) >= 2 && segments[0] == "devices" {
		id := segments[1]
		if id == "" {
			writeError(w, http.StatusBadRequest, "invalid_device", "device ID is empty")
			return true
		}
		return s.handleDevicePath(w, r, id, segments[2:])
	}
	return false
}

func splitAPIPath(value string) []string {
	raw := strings.Split(value, "/")
	result := make([]string, 0, len(raw))
	for _, segment := range raw {
		decoded, err := url.PathUnescape(segment)
		if err != nil {
			decoded = segment
		}
		result = append(result, decoded)
	}
	return result
}

// maxDeviceLimit 是设备数量的软上限：达到上限后禁止再添加新设备。
const maxDeviceLimit = 5

func (s *Server) handleDevices(w http.ResponseWriter, r *http.Request) bool {
	switch r.Method {
	case http.MethodGet:
		writeJSON(w, http.StatusOK, map[string]any{
			"data": map[string]any{
				"device_limit": maxDeviceLimit,
				"devices":      s.deviceSummaries(),
			},
		})
	case http.MethodPost:
		if s.devices == nil {
			writeError(w, http.StatusServiceUnavailable, "device_manager_unavailable", "device manager is unavailable")
			return true
		}
		var request struct {
			Config json.RawMessage `json:"config"`
		}
		if err := s.decodeJSON(w, r, &request); err != nil {
			writeError(w, http.StatusBadRequest, "invalid_request", err.Error())
			return true
		}
		var payload deviceConfigPayload
		if err := json.Unmarshal(request.Config, &payload); err != nil {
			writeError(w, http.StatusBadRequest, "invalid_device_config", "device config must be a valid JSON object")
			return true
		}
		if !validDeviceID(payload.ID) {
			writeError(w, http.StatusBadRequest, "invalid_device_id", "device ID must use 1-64 letters, digits, dots, underscores, or hyphens")
			return true
		}
		if _, err := s.store.Device(r.Context(), payload.ID); err == nil {
			writeError(w, http.StatusConflict, "device_exists", "a device with this ID already exists")
			return true
		} else if !errors.Is(err, store.ErrNotFound) {
			s.writeStoreError(w, err)
			return true
		}
		configured, err := s.store.ListDevices(r.Context())
		if err != nil {
			s.writeStoreError(w, err)
			return true
		}
		if len(configured) >= maxDeviceLimit {
			writeError(w, http.StatusConflict, "device_limit_reached", i18n.Tf("设备数量已达上限，最多只能添加 %d 台设备", maxDeviceLimit))
			return true
		}
		devices, err := s.devices.Discover(r.Context())
		if err != nil {
			s.writeDeviceError(w, err)
			return true
		}
		selected := findDiscoveredDevice(devices, payload)
		if selected == nil {
			writeError(w, http.StatusNotFound, "device_not_found", "the selected Linux modem was not discovered")
			return true
		}
		config := payload.toStoreDevice()
		fillConfigFromPhysical(&config, *selected)
		if err := s.store.UpsertDevice(r.Context(), config); err != nil {
			s.writeStoreError(w, err)
			return true
		}
		writeJSON(w, http.StatusCreated, map[string]any{
			"data": map[string]any{
				"status":          "created",
				"id":              config.ID,
				"discovery_key":   selected.ID,
				"physical_device": s.configuredDeviceSummary(config, selected),
			},
		})
	default:
		w.Header().Set("Allow", "GET, POST")
		writeError(w, http.StatusMethodNotAllowed, "method_not_allowed", "method not allowed")
	}
	return true
}

func findDiscoveredDevice(devices []device.Device, config deviceConfigPayload) *device.Device {
	for index := range devices {
		candidate := devices[index].Candidate
		if config.ATPort != "" &&
			(candidate.ATPort.Path == config.ATPort || candidate.ATPort.OpenPath() == config.ATPort) {
			return &devices[index]
		}
		if config.ControlDevice != "" && candidate.QMIControl == config.ControlDevice {
			return &devices[index]
		}
		if config.USBPath != "" && candidate.USBPath == config.USBPath {
			return &devices[index]
		}
	}
	return nil
}

func (s *Server) handleDiscoveredDevices(w http.ResponseWriter, r *http.Request) bool {
	if !requireMethod(w, r, http.MethodGet) {
		return true
	}
	if s.devices == nil {
		writeJSON(w, http.StatusOK, map[string]any{"data": map[string]any{"devices": []any{}}})
		return true
	}
	devices := s.devices.List()
	configured, err := s.store.ListDevices(r.Context())
	if err != nil {
		s.writeStoreError(w, err)
		return true
	}
	result := make([]map[string]any, 0, len(devices))
	for _, entry := range devices {
		candidate := entry.Candidate
		atPorts := make([]string, 0, len(candidate.Ports))
		for _, port := range candidate.Ports {
			if port.Role != modem.PortRoleDiagnostic {
				atPorts = append(atPorts, port.OpenPath())
			}
		}
		controlPath := candidate.QMIControl
		if controlPath == "" {
			controlPath = candidate.ATPort.OpenPath()
		}
		configuredID := ""
		for _, config := range configured {
			if physicalMatchesConfig(entry, config) {
				configuredID = config.ID
				break
			}
		}
		result = append(result, map[string]any{
			"discovery_key":   entry.ID,
			"control_path":    controlPath,
			"net_interface":   candidate.NetworkInterface,
			"usb_path":        candidate.USBPath,
			"vendor_id":       parseHexID(candidate.VendorID),
			"product_id":      parseHexID(candidate.ProductID),
			"driver_name":     "",
			"at_ports":        atPorts,
			"at_port":         candidate.ATPort.OpenPath(),
			"imei":            snapshotString(entry.Snapshot, func(snapshot *device.Snapshot) string { return snapshot.IMEI }),
			"mode":            backendMode(candidate),
			"network_capable": candidate.NetworkInterface != "" || candidate.QMIControl != "",
			"configured":      configuredID != "",
			"configured_id":   configuredID,
			"degraded":        !candidate.HasATPort(),
		})
	}
	writeJSON(w, http.StatusOK, map[string]any{"data": map[string]any{"devices": result}})
	return true
}

func parseHexID(value string) int64 {
	value = strings.TrimPrefix(strings.ToLower(strings.TrimSpace(value)), "0x")
	number, _ := strconv.ParseInt(value, 16, 64)
	return number
}

func (s *Server) handleDeviceRescan(w http.ResponseWriter, r *http.Request) bool {
	if !requireMethod(w, r, http.MethodPost) {
		return true
	}
	if s.devices == nil {
		writeError(w, http.StatusServiceUnavailable, "device_manager_unavailable", "device manager is unavailable")
		return true
	}
	devices, err := s.devices.Discover(r.Context())
	if err != nil {
		s.writeDeviceError(w, err)
		return true
	}
	writeJSON(w, http.StatusOK, map[string]any{
		"data": map[string]any{"status": "ok", "devices": len(devices)},
	})
	return true
}

func (s *Server) handleDevicePath(
	w http.ResponseWriter,
	r *http.Request,
	id string,
	tail []string,
) bool {
	config, err := s.store.Device(r.Context(), id)
	if err != nil {
		s.writeStoreError(w, err)
		return true
	}
	if len(tail) == 0 {
		switch r.Method {
		case http.MethodDelete:
			if err := s.store.DeleteDevice(r.Context(), id); err != nil {
				s.writeStoreError(w, err)
				return true
			}
			writeJSON(w, http.StatusOK, map[string]any{
				"data": map[string]any{"deleted": true, "physical_device_untouched": true},
			})
		case http.MethodPut:
			var request struct {
				Config json.RawMessage `json:"config"`
			}
			if err := s.decodeJSON(w, r, &request); err != nil {
				writeError(w, http.StatusBadRequest, "invalid_request", err.Error())
				return true
			}
			var payload deviceConfigPayload
			if err := json.Unmarshal(request.Config, &payload); err != nil {
				writeError(w, http.StatusBadRequest, "invalid_device_config", "device config must be a valid JSON object")
				return true
			}
			if payload.ID != "" && payload.ID != id {
				writeError(w, http.StatusConflict, "immutable_device_id", "device ID cannot be changed")
				return true
			}
			next := payload.toStoreDevice()
			next.ID = id
			next.CreatedAt = config.CreatedAt
			if next.Name == id && strings.TrimSpace(payload.Name) == "" {
				next.Name = config.Name
			}
			if err := s.store.UpsertDevice(r.Context(), next); err != nil {
				s.writeStoreError(w, err)
				return true
			}
			writeJSON(w, http.StatusOK, map[string]any{
				"data": map[string]any{"status": "saved", "config": storedDeviceConfig(next)},
			})
		default:
			w.Header().Set("Allow", "DELETE, PUT")
			writeError(w, http.StatusMethodNotAllowed, "method_not_allowed", "method not allowed")
		}
		return true
	}

	entry, physicalID, physicalPresent := s.physicalForConfig(config)
	if len(tail) > 0 && tail[0] == "esim" {
		return s.handleESIM(w, r, tail[1:], physicalID, physicalPresent)
	}
	switch strings.Join(tail, "/") {
	case "overview":
		if !requireMethod(w, r, http.MethodGet) {
			return true
		}
		writeJSON(w, http.StatusOK, map[string]any{
			"data": map[string]any{"devices": []any{s.configuredDeviceOverview(config, entry, physicalPresent)}},
		})
	case "overview/stream":
		return s.handleOverviewStream(w, r, config, entry, physicalPresent)
	case "status":
		if !requireMethod(w, r, http.MethodGet) {
			return true
		}
		writeJSON(w, http.StatusOK, map[string]any{"data": s.configuredDeviceStatus(config, entry, physicalPresent)})
	case "config":
		if !requireMethod(w, r, http.MethodGet) {
			return true
		}
		writeJSON(w, http.StatusOK, map[string]any{
			"data": map[string]any{"config": storedDeviceConfig(config)},
		})
	case "actions/refresh":
		if !requireMethod(w, r, http.MethodPost) {
			return true
		}
		if !s.requirePhysicalDevice(w, physicalPresent) {
			return true
		}
		snapshot, err := s.devices.Refresh(r.Context(), physicalID)
		if err != nil {
			s.writeDeviceError(w, err)
			return true
		}
		writeJSON(w, http.StatusOK, map[string]any{"data": snapshot})
	case "actions/at":
		if !s.requirePhysicalDevice(w, physicalPresent) {
			return true
		}
		return s.handleAT(w, r, physicalID)
	case "actions/ussd":
		if !s.requirePhysicalDevice(w, physicalPresent) {
			return true
		}
		return s.handleUSSD(w, r, physicalID)
	case "actions/ussd/continue":
		return s.handleUSSDContinue(w, r)
	case "actions/ussd/cancel":
		return s.handleUSSDCancel(w, r)
	case "actions/reboot":
		if !requireMethod(w, r, http.MethodPost) {
			return true
		}
		if !s.requirePhysicalDevice(w, physicalPresent) {
			return true
		}
		if err := s.devices.Reboot(r.Context(), physicalID); err != nil {
			s.writeDeviceError(w, err)
			return true
		}
		writeJSON(w, http.StatusAccepted, map[string]any{"data": map[string]any{"status": "rebooting"}})
	case "flight-mode":
		if !s.requirePhysicalDevice(w, physicalPresent) {
			return true
		}
		return s.handleFlightMode(w, r, physicalID)
	case "usbnet-mode":
		if !s.requirePhysicalDevice(w, physicalPresent) {
			return true
		}
		return s.handleUSBNetMode(w, r, physicalID)
	case "operator_selection":
		if !s.requirePhysicalDevice(w, physicalPresent) {
			return true
		}
		return s.handleOperatorSelection(w, r, physicalID)
	case "operator_selection/scan":
		if !s.requirePhysicalDevice(w, physicalPresent) {
			return true
		}
		return s.handleOperatorScan(w, r, physicalID)
	case "operator_selection/scan/stream":
		if !s.requirePhysicalDevice(w, physicalPresent) {
			return true
		}
		return s.handleOperatorScanStream(w, r, physicalID)
	case "vowifi":
		return s.handleVoWiFiEnabled(w, r, config, physicalPresent)
	case "vowifi/actions/reconnect":
		return s.handleVoWiFiReconnect(w, r, config, physicalPresent)
	case "vowifi/e911/websheet":
		return s.handleE911Websheet(w, r, config)
	default:
		return false
	}
	return true
}

func (s *Server) handleUSBNetMode(w http.ResponseWriter, r *http.Request, physicalID string) bool {
	switch r.Method {
	case http.MethodGet:
		result, err := s.devices.USBNetMode(r.Context(), physicalID)
		if err != nil {
			s.writeDeviceError(w, err)
			return true
		}
		writeJSON(w, http.StatusOK, map[string]any{"data": result})
	case http.MethodPatch:
		var request struct {
			Mode int `json:"mode"`
		}
		if err := s.decodeJSON(w, r, &request); err != nil {
			writeError(w, http.StatusBadRequest, "invalid_request", err.Error())
			return true
		}
		result, err := s.devices.SetUSBNetMode(r.Context(), physicalID, request.Mode)
		if err != nil {
			s.writeDeviceError(w, err)
			return true
		}
		writeJSON(w, http.StatusOK, map[string]any{
			"data": map[string]any{
				"mode": result.Mode, "name": result.Name,
				"reboot_required": true,
			},
		})
	default:
		w.Header().Set("Allow", "GET, PATCH")
		writeError(w, http.StatusMethodNotAllowed, "method_not_allowed", "method not allowed")
	}
	return true
}

// operatorSelectionWire is the current-selection shape the SPA reads.
func operatorSelectionWire(sel device.OperatorSelection) map[string]any {
	mode := "automatic"
	if sel.Mode != 0 {
		mode = "manual"
	}
	return map[string]any{
		"mode":              mode,
		"plmn":              sel.Operator,
		"access_technology": sel.AccessTechnology,
	}
}

// accessTechnologyValue maps a RAT name (as surfaced to the UI by the scan) back
// to its numeric AT+COPS access technology. Returns nil for an unknown name.
func accessTechnologyValue(name string) *int {
	var value int
	switch strings.ToUpper(strings.TrimSpace(name)) {
	case "GSM":
		value = 0
	case "UTRAN", "UMTS", "WCDMA":
		value = 2
	case "EDGE":
		value = 3
	case "HSDPA":
		value = 4
	case "HSUPA":
		value = 5
	case "HSPA":
		value = 6
	case "LTE":
		value = 7
	case "NR5G", "NR":
		value = 9
	default:
		return nil
	}
	return &value
}

func (s *Server) handleOperatorSelection(w http.ResponseWriter, r *http.Request, physicalID string) bool {
	switch r.Method {
	case http.MethodGet:
		result, err := s.devices.OperatorSelection(r.Context(), physicalID)
		if err != nil {
			s.writeDeviceError(w, err)
			return true
		}
		writeJSON(w, http.StatusOK, map[string]any{"data": operatorSelectionWire(result)})
	case http.MethodPost, http.MethodPut, http.MethodPatch:
		// The SPA posts {mode, plmn, includes_pcs_digit, rat}; the legacy shape is
		// {automatic, plmn, access_technology}. Accept both. includes_pcs_digit is
		// accepted for contract compatibility but not currently applied to the command.
		var request struct {
			Mode             string `json:"mode"`
			Automatic        *bool  `json:"automatic"`
			PLMN             string `json:"plmn"`
			AccessTechnology *int   `json:"access_technology"`
			Rat              string `json:"rat"`
			IncludesPcsDigit bool   `json:"includes_pcs_digit"`
		}
		if err := s.decodeJSON(w, r, &request); err != nil {
			writeError(w, http.StatusBadRequest, "invalid_request", err.Error())
			return true
		}
		automatic := false
		switch strings.ToLower(strings.TrimSpace(request.Mode)) {
		case "manual":
			automatic = false
		case "automatic":
			automatic = true
		default:
			if request.Automatic != nil {
				automatic = *request.Automatic
			}
		}
		accessTechnology := request.AccessTechnology
		if rat := strings.TrimSpace(request.Rat); rat != "" {
			accessTechnology = accessTechnologyValue(rat)
		}
		// Manual PLMN selection can take tens of seconds while the modem
		// searches for and registers on the requested network. The server's
		// WriteTimeout would otherwise cut the response off, so clear the
		// connection's write deadline for this request like the SSE paths do.
		controller := http.NewResponseController(w)
		_ = controller.SetWriteDeadline(time.Time{})
		result, err := s.devices.SetOperatorSelection(r.Context(), physicalID, automatic, request.PLMN, accessTechnology)
		if err != nil {
			s.writeDeviceError(w, err)
			return true
		}
		writeJSON(w, http.StatusOK, map[string]any{"data": operatorSelectionWire(result)})
	default:
		w.Header().Set("Allow", "GET, POST, PUT, PATCH")
		writeError(w, http.StatusMethodNotAllowed, "method_not_allowed", "method not allowed")
	}
	return true
}

func (s *Server) handleVoWiFiEnabled(
	w http.ResponseWriter,
	r *http.Request,
	config store.Device,
	physicalPresent bool,
) bool {
	if !requireMethod(w, r, http.MethodPatch) {
		return true
	}
	var request struct {
		Enabled bool `json:"enabled"`
	}
	if err := s.decodeJSON(w, r, &request); err != nil {
		writeError(w, http.StatusBadRequest, "invalid_request", err.Error())
		return true
	}
	if s.vowifi == nil {
		writeError(w, http.StatusServiceUnavailable, "vowifi_provider_unavailable", "the VoWiFi runtime is unavailable")
		return true
	}
	if request.Enabled && !physicalPresent {
		writeError(w, http.StatusServiceUnavailable, "physical_device_missing", "the configured modem is not present on this Linux host")
		return true
	}
	if request.Enabled {
		entry, _, _ := s.physicalForConfig(config)
		imsi := snapshotString(entry.Snapshot, func(snapshot *device.Snapshot) string { return snapshot.IMSI })
		if reason := device.RegionBlockReason(imsi); reason != "" {
			writeError(w, http.StatusForbidden, "region_blocked", reason)
			return true
		}
	}

	previous := config.VoWiFiEnabled
	config.VoWiFiEnabled = request.Enabled
	if err := s.store.UpsertDevice(r.Context(), config); err != nil {
		s.writeStoreError(w, err)
		return true
	}
	state, err := s.vowifi.RequestEnabled(config.ID, request.Enabled)
	if err != nil {
		// Repeating the same desired state while its asynchronous transaction is
		// already running is idempotent. Treat it as accepted instead of making a
		// harmless double-click (or a stale browser refresh) surface vowifi_busy.
		if errors.Is(err, vowifiruntime.ErrOperationInProgress) &&
			(state.Enabled == request.Enabled || previous == request.Enabled) {
			writeJSON(w, http.StatusAccepted, map[string]any{
				"data": map[string]any{
					"accepted": true,
					"enabled":  request.Enabled,
					"status":   "in_progress",
					"runtime":  state,
				},
			})
			return true
		}
		config.VoWiFiEnabled = previous
		if restoreErr := s.store.UpsertDevice(r.Context(), config); restoreErr != nil {
			s.logger.Error(
				"restore VoWiFi policy after rejected runtime operation",
				"device_id", config.ID,
				"error", restoreErr,
			)
		}
		s.writeVoWiFiError(w, err)
		return true
	}
	writeJSON(w, http.StatusAccepted, map[string]any{
		"data": map[string]any{
			"accepted": true,
			"enabled":  request.Enabled,
			"status":   map[bool]string{true: "starting", false: "stopping"}[request.Enabled],
			"runtime":  state,
		},
	})
	return true
}

func (s *Server) handleVoWiFiReconnect(
	w http.ResponseWriter,
	r *http.Request,
	config store.Device,
	physicalPresent bool,
) bool {
	if !requireMethod(w, r, http.MethodPost) {
		return true
	}
	if s.vowifi == nil {
		writeError(w, http.StatusServiceUnavailable, "vowifi_provider_unavailable", "the VoWiFi runtime is unavailable")
		return true
	}
	if !config.VoWiFiEnabled {
		writeError(w, http.StatusConflict, "vowifi_disabled", "enable VoWiFi before requesting a reconnect")
		return true
	}
	if !physicalPresent {
		writeError(w, http.StatusServiceUnavailable, "physical_device_missing", "the configured modem is not present on this Linux host")
		return true
	}
	state, err := s.vowifi.RequestReconnect(config.ID)
	if err != nil {
		s.writeVoWiFiError(w, err)
		return true
	}
	writeJSON(w, http.StatusAccepted, map[string]any{
		"data": map[string]any{
			"accepted": true,
			"status":   "reconnecting",
			"runtime":  state,
		},
	})
	return true
}

func (s *Server) writeVoWiFiError(w http.ResponseWriter, err error) {
	switch {
	case errors.Is(err, vowifiruntime.ErrNotRegistered):
		writeError(w, http.StatusServiceUnavailable, "vowifi_device_unavailable", "the configured device has no VoWiFi runtime")
	case errors.Is(err, vowifiruntime.ErrOperationInProgress):
		writeError(w, http.StatusConflict, "vowifi_busy", "another VoWiFi operation is still in progress")
	case errors.Is(err, vowifiruntime.ErrClosed):
		writeError(w, http.StatusServiceUnavailable, "vowifi_runtime_stopped", "the VoWiFi runtime is stopping")
	case errors.Is(err, vowifi.ErrNotRunning):
		writeError(w, http.StatusConflict, "vowifi_not_running", "VoWiFi is not running")
	default:
		s.logger.Warn("VoWiFi action rejected", "error", err)
		writeError(w, http.StatusBadGateway, "vowifi_error", err.Error())
	}
}

// maxActionTimeoutMs bounds a client-supplied per-request timeout so a terminal
// action cannot hold the device operation lock indefinitely.
const maxActionTimeoutMs = 120000

// actionRequestContext applies the frontend's optional per-request timeout
// (timeout_ms) to the request context. Manager.withTimeout preserves an
// existing deadline, so setting one here makes the requested timeout take
// effect; a non-positive or absent value leaves the manager's defaults in place.
func actionRequestContext(parent context.Context, timeoutMs int) (context.Context, context.CancelFunc) {
	if parent == nil {
		parent = context.Background()
	}
	if timeoutMs <= 0 {
		return context.WithCancel(parent)
	}
	if timeoutMs > maxActionTimeoutMs {
		timeoutMs = maxActionTimeoutMs
	}
	return context.WithTimeout(parent, time.Duration(timeoutMs)*time.Millisecond)
}

func (s *Server) handleAT(w http.ResponseWriter, r *http.Request, id string) bool {
	if !requireMethod(w, r, http.MethodPost) {
		return true
	}
	var request struct {
		Command   string `json:"cmd"`
		TimeoutMs int    `json:"timeout_ms"`
	}
	if err := s.decodeJSON(w, r, &request); err != nil {
		writeError(w, http.StatusBadRequest, "invalid_request", err.Error())
		return true
	}
	command := strings.TrimSpace(request.Command)
	if err := validateATCommand(command); err != nil {
		writeError(w, http.StatusBadRequest, "unsafe_at_command", err.Error())
		return true
	}
	ctx, cancel := actionRequestContext(r.Context(), request.TimeoutMs)
	defer cancel()
	response, err := s.devices.ExecuteAT(ctx, id, command)
	if err != nil {
		s.writeDeviceError(w, err)
		return true
	}
	text := response.Text()
	if response.Final != "" {
		if text != "" {
			text += "\n"
		}
		text += response.Final
	}
	writeJSON(w, http.StatusOK, map[string]any{
		"data": map[string]any{
			"response":    text,
			"final":       response.Final,
			"duration_ms": response.Duration.Milliseconds(),
			"urcs":        response.URCs,
		},
	})
	return true
}

func validateATCommand(command string) error {
	upper := strings.ToUpper(command)
	if len(command) < 2 || len(command) > 512 || !strings.HasPrefix(upper, "AT") {
		return errors.New("AT command must start with AT and contain at most 512 characters")
	}
	if strings.ContainsAny(command, "\r\n\x00") {
		return errors.New("AT command must contain exactly one line")
	}
	canonical := strings.NewReplacer(" ", "", "\t", "").Replace(upper)
	for _, blocked := range []string{
		`+QCFG="USBNET"`,
		`+QCFG="USBCFG"`,
		"+QPOWD",
		"+CFUN=",
		"+CGATT=",
		"+CGACT=",
		"+CGDATA",
		"+QNETDEVCTL=",
		"+QIACT",
		"+QIDEACT",
		"+CMGS",
		"+CMSS",
		"+CMGC",
		"+QCMGS",
		"+CUSD=",
		"D",
		"A",
		"H",
	} {
		for _, segment := range strings.Split(canonical[2:], ";") {
			if strings.HasPrefix(segment, blocked) {
				return fmt.Errorf("AT%s is reserved for a guarded device action", blocked)
			}
		}
	}
	return nil
}

func (s *Server) handleUSSD(w http.ResponseWriter, r *http.Request, id string) bool {
	if !requireMethod(w, r, http.MethodPost) {
		return true
	}
	var request struct {
		Command   string `json:"command"`
		TimeoutMs int    `json:"timeout_ms"`
	}
	if err := s.decodeJSON(w, r, &request); err != nil {
		writeError(w, http.StatusBadRequest, "invalid_request", err.Error())
		return true
	}
	ctx, cancel := actionRequestContext(r.Context(), request.TimeoutMs)
	defer cancel()
	result, err := s.devices.USSD(ctx, id, request.Command)
	if err != nil {
		s.writeDeviceError(w, err)
		return true
	}
	writeJSON(w, http.StatusOK, map[string]any{
		"data": map[string]any{
			"result": result.Text,
			"raw":    result.Raw,
			"dcs":    result.DCS,
		},
	})
	return true
}

func (s *Server) handleFlightMode(w http.ResponseWriter, r *http.Request, id string) bool {
	if !requireMethod(w, r, http.MethodPatch) {
		return true
	}
	var request struct {
		Enabled bool `json:"enabled"`
	}
	if err := s.decodeJSON(w, r, &request); err != nil {
		writeError(w, http.StatusBadRequest, "invalid_request", err.Error())
		return true
	}
	result, err := s.devices.SetFlight(r.Context(), id, request.Enabled)
	if err != nil {
		s.writeDeviceError(w, err)
		return true
	}
	writeJSON(w, http.StatusOK, map[string]any{"data": result})
	return true
}

func (s *Server) requirePhysicalDevice(w http.ResponseWriter, present bool) bool {
	if s.devices == nil {
		writeError(w, http.StatusServiceUnavailable, "device_manager_unavailable", "device manager is unavailable")
		return false
	}
	if !present {
		writeError(w, http.StatusServiceUnavailable, "physical_device_missing", "the configured modem is not present on this Linux host")
		return false
	}
	return true
}

func (s *Server) writeDeviceError(w http.ResponseWriter, err error) {
	switch {
	case errors.Is(err, device.ErrNotFound):
		writeError(w, http.StatusNotFound, "device_not_found", "device was not found or is no longer present")
	case errors.Is(err, device.ErrNotStarted):
		writeError(w, http.StatusServiceUnavailable, "device_manager_not_started", "device manager is not started")
	case errors.Is(err, device.ErrNoATPort):
		writeError(w, http.StatusServiceUnavailable, "at_port_unavailable", "device has no usable AT port")
	case errors.Is(err, device.ErrDataBackendUnavailable):
		writeError(w, http.StatusNotImplemented, "data_backend_unavailable", err.Error())
	case errors.Is(err, device.ErrEUICCChannelStuck):
		writeError(w, http.StatusServiceUnavailable, "euicc_channel_stuck", err.Error())
	case errors.Is(err, device.ErrESIMDeleteProfileNotFound):
		writeError(w, http.StatusNotFound, "esim_profile_not_found", "The profile is no longer present on this eUICC. Refresh the profile list and try again.")
	case errors.Is(err, device.ErrESIMDeleteProfileNotDisabled):
		writeError(w, http.StatusConflict, "esim_profile_enabled", "The active profile cannot be deleted. Enable another profile first, then delete this disabled profile.")
	case errors.Is(err, device.ErrESIMDeleteDisallowedByPolicy):
		writeError(w, http.StatusConflict, "esim_delete_disallowed_by_policy", "This profile's policy does not allow it to be deleted.")
	case errors.Is(err, device.ErrESIMNicknameTooLong):
		writeError(w, http.StatusBadRequest, "esim_nickname_too_long", "Profile nickname must not exceed 64 characters.")
	case errors.Is(err, device.ErrESIMNicknameProfileNotFound):
		writeError(w, http.StatusNotFound, "esim_profile_not_found", "The profile is no longer present on this eUICC. Refresh the profile list and try again.")
	case errors.Is(err, device.ErrESIMDisableProfileNotFound):
		writeError(w, http.StatusNotFound, "esim_profile_not_found", "The profile is no longer present on this eUICC. Refresh the profile list and try again.")
	case errors.Is(err, device.ErrESIMProfileNotEnabled):
		writeError(w, http.StatusConflict, "esim_profile_not_enabled", "This profile is already disabled. Refresh the profile list before retrying.")
	case errors.Is(err, device.ErrESIMDisableDisallowedByPolicy):
		writeError(w, http.StatusConflict, "esim_disable_disallowed_by_policy", "This profile's policy does not allow it to be disabled.")
	case errors.Is(err, device.ErrESIMDisableCATBusy):
		writeError(w, http.StatusConflict, "esim_cat_busy", "The eUICC is busy with a SIM Toolkit operation. Wait a moment and retry disabling the profile.")
	case errors.Is(err, device.ErrInvalidNetworkAPN):
		writeError(w, http.StatusBadRequest, "invalid_apn", "APN must contain only letters, digits, dots, underscores, or hyphens")
	case errors.Is(err, device.ErrRegionBlocked):
		writeError(w, http.StatusForbidden, "region_blocked", err.Error())
	case errors.Is(err, context.DeadlineExceeded), errors.Is(err, modem.ErrCommandTimeout):
		writeError(w, http.StatusGatewayTimeout, "modem_timeout", "the modem did not answer before the command timeout")
	case errors.Is(err, context.Canceled):
		writeError(w, http.StatusRequestTimeout, "request_canceled", "the modem request was canceled")
	default:
		s.logger.Warn("device operation failed", "error", err)
		writeError(w, http.StatusBadGateway, "modem_error", err.Error())
	}
}

func (s *Server) deviceSummaries() []map[string]any {
	configs, err := s.store.ListDevices(context.Background())
	if err != nil {
		s.logger.Error("list configured devices", "error", err)
		return []map[string]any{}
	}
	result := make([]map[string]any, 0, len(configs))
	for _, config := range configs {
		entry, _, present := s.physicalForConfig(config)
		if present {
			result = append(result, s.configuredDeviceSummary(config, &entry))
		} else {
			result = append(result, s.configuredDeviceSummary(config, nil))
		}
	}
	return result
}

func (s *Server) dashboardDevices() []map[string]any {
	devices := s.deviceSummaries()
	result := make([]map[string]any, 0, len(devices))
	for _, entry := range devices {
		modemStatus, _ := entry["modem"].(map[string]any)
		runtime, _ := entry["vowifi_runtime"].(map[string]any)
		vowifiActive, _ := runtime["tunnel_ready"].(bool)
		result = append(result, map[string]any{
			"id":                entry["id"],
			"name":              entry["name"],
			"interface":         entry["interface"],
			"proxy_port":        entry["proxy_port"],
			"public_ip":         entry["public_ip"],
			"healthy":           entry["healthy"],
			"operator":          modemStatus["operator"],
			"signal_dbm":        modemStatus["signal_dbm"],
			"network_mode":      modemStatus["network_mode"],
			"network_duplex":    modemStatus["network_duplex"],
			"vowifi_active":     vowifiActive,
			"vowifi_runtime":    runtime,
			"network_connected": false,
			"model":             modemStatus["model"],
		})
	}
	return result
}

func (s *Server) physicalForConfig(config store.Device) (device.Device, string, bool) {
	if s.devices == nil {
		return device.Device{ID: config.ID}, "", false
	}
	if entry, err := s.devices.Get(config.ID); err == nil && entry.Discovered {
		return entry, entry.ID, true
	}
	for _, entry := range s.devices.List() {
		if entry.Discovered && physicalMatchesConfig(entry, config) {
			return entry, entry.ID, true
		}
	}
	return device.Device{ID: config.ID}, "", false
}

func physicalMatchesConfig(entry device.Device, config store.Device) bool {
	candidate := entry.Candidate
	if entry.ID == config.ID {
		return true
	}
	if config.ATPort != "" &&
		(config.ATPort == candidate.ATPort.Path || config.ATPort == candidate.ATPort.OpenPath()) {
		return true
	}
	if config.ControlDevice != "" && config.ControlDevice == candidate.QMIControl {
		return true
	}
	if config.USBPath != "" && config.USBPath == candidate.USBPath {
		return true
	}
	return config.ModemIMEI != "" &&
		entry.Snapshot != nil &&
		config.ModemIMEI == entry.Snapshot.IMEI
}

func (s *Server) configuredDeviceSummary(
	config store.Device,
	entry *device.Device,
) map[string]any {
	var result map[string]any
	if entry != nil {
		result = deviceSummary(*entry)
	} else {
		result = deviceSummary(device.Device{ID: config.ID})
	}
	result["id"] = config.ID
	result["name"] = config.Name
	result["interface"] = config.Interface
	result["proxy_port"] = config.ProxyPort
	result["esim_transport"] = config.ESIMTransport
	result["sms_enabled"] = config.SMSEnabled
	result["network_enabled"] = false
	result["vowifi_enabled"] = config.VoWiFiEnabled
	if runtime, err := s.store.VoWiFiRuntime(context.Background(), config.ID); err == nil {
		runtimeResponse := storedVoWiFiRuntime(runtime)
		result["vowifi_runtime"] = runtimeResponse
		result["vowifi_active"] = runtime.TunnelReady
		if runtime.LocalPhone != "" {
			// The SIM panel reads the top-level local_phone; keep modem.phone_number
			// in sync for the summary/overview consumers that read it there.
			result["local_phone"] = runtime.LocalPhone
			result["phone_number_source"] = runtime.PhoneNumberSource
			if modemStatus, ok := result["modem"].(map[string]any); ok {
				modemStatus["phone_number"] = runtime.LocalPhone
				modemStatus["phone_number_source"] = runtime.PhoneNumberSource
			}
		}
	}
	return result
}

func (s *Server) configuredDeviceOverview(
	config store.Device,
	entry device.Device,
	present bool,
) map[string]any {
	var physical *device.Device
	if present {
		physical = &entry
	}
	result := s.configuredDeviceSummary(config, physical)
	result["id"] = config.ID
	result["name"] = config.Name
	result["interface"] = config.Interface
	result["at_port"] = config.ATPort
	result["audio_device"] = config.AudioDevice
	result["backend_mode"] = config.DeviceBackend
	result["control_device"] = config.ControlDevice
	result["esim_transport"] = config.ESIMTransport
	result["sms_enabled"] = config.SMSEnabled
	result["network_enabled"] = false
	result["vowifi_enabled"] = config.VoWiFiEnabled
	result["radio_live_ok"] = present && entry.Snapshot != nil && entry.Snapshot.Responsive
	result["traffic"] = map[string]string{}
	result["traffic_raw"] = map[string]int64{}
	result["traffic_meta"] = map[string]any{}
	return result
}

func (s *Server) configuredDeviceStatus(
	config store.Device,
	entry device.Device,
	present bool,
) map[string]any {
	var physical *device.Device
	if present {
		physical = &entry
	}
	summary := s.configuredDeviceSummary(config, physical)
	lastUpdated := time.Time{}
	if physical != nil {
		lastUpdated = physical.LastUpdated
	}
	result := map[string]any{
		"healthy":               summary["healthy"],
		"public_ip":             summary["public_ip"],
		"network_connected":     false,
		"modem":                 summary["modem"],
		"vowifi":                summary["vowifi_runtime"],
		"sim_service_table":     map[string]any{},
		"pnn":                   []any{},
		"opl":                   []any{},
		"last_hardware_refresh": lastUpdated,
	}
	result["id"] = config.ID
	result["name"] = config.Name
	result["interface"] = config.Interface
	result["proxy_port"] = config.ProxyPort
	return result
}

func storedVoWiFiRuntime(runtime store.VoWiFiRuntime) map[string]any {
	return map[string]any{
		"device_id":           runtime.DeviceID,
		"phase":               runtime.Phase,
		"dataplane_mode":      runtime.DataplaneMode,
		"iccid":               runtime.ICCID,
		"imsi":                runtime.IMSI,
		"sim_ready":           runtime.SIMReady,
		"access_ready":        runtime.AccessReady,
		"tunnel_ready":        runtime.TunnelReady,
		"ims_ready":           runtime.IMSReady,
		"sms_ready":           runtime.SMSReady,
		"reg_status":          runtime.RegStatus,
		"reg_status_text":     runtime.RegStatusText,
		"network_mode":        runtime.NetworkMode,
		"local_phone":         runtime.LocalPhone,
		"phone_number_source": runtime.PhoneNumberSource,
		"last_error_class":    runtime.LastErrorClass,
		"last_error":          runtime.LastError,
		"last_reason":         runtime.LastReason,
		"updated_at":          runtime.UpdatedAt,
		"tunnel":              rawJSONObject(runtime.Tunnel),
		"imscore":             rawJSONObject(runtime.IMSCore),
		"smsip":               rawJSONObject(runtime.SMSIP),
	}
}

func rawJSONObject(value json.RawMessage) any {
	var result any
	if len(value) != 0 && json.Unmarshal(value, &result) == nil {
		return result
	}
	return map[string]any{}
}

func deviceSummary(entry device.Device) map[string]any {
	snapshot := entry.Snapshot
	healthy := entry.Discovered && snapshot != nil && snapshot.Responsive && entry.LastError == ""
	phone := ""
	phoneSource := ""
	if snapshot != nil {
		phone = snapshot.Phone.Number
		phoneSource = snapshot.Phone.Source
	}
	mode := 0
	modeKnown := false
	if snapshot != nil {
		mode = snapshot.OperatingMode
		modeKnown = snapshot.ModeKnown
	}
	runtime := idleVoWiFiRuntime(entry.ID, snapshot)
	summary := modemSummary(snapshot, phone, phoneSource)
	if model, _ := summary["model"].(string); strings.TrimSpace(model) == "" {
		if p := strings.TrimSpace(entry.Candidate.Product); p != "" {
			summary["model"] = p
		} else if id := strings.TrimSpace(entry.ID); id != "" {
			summary["model"] = id
		}
	}
	return map[string]any{
		"id":                       entry.ID,
		"name":                     deviceName(entry),
		"running":                  entry.Discovered,
		"healthy":                  healthy,
		"control_online":           healthy,
		"physical_present":         entry.Discovered,
		"worker_running":           entry.Discovered,
		"data_connected":           false,
		"radio_registered":         snapshot != nil && snapshot.OperatorName != "",
		"lifecycle_phase":          lifecyclePhase(entry),
		"lifecycle_reason":         entry.LastError,
		"public_ip":                "",
		"private_ip":               "",
		"interface":                entry.Candidate.NetworkInterface,
		"esim_transport":           backendMode(entry.Candidate),
		"sms_enabled":              true,
		"network_enabled":          false,
		"vowifi_enabled":           false,
		"vowifi_active":            false,
		"vowifi_runtime":           runtime,
		"modem":                    summary,
		"local_phone":              phone,
		"phone_number_source":      phoneSource,
		"network_connected":        false,
		"registration_state_label": registrationLabel(snapshot),
		"flight_mode":              modeKnown && (mode == 0 || mode == 4),
	}
}

func deviceOverview(entry device.Device) map[string]any {
	result := deviceSummary(entry)
	result["at_port"] = entry.Candidate.ATPort.OpenPath()
	result["audio_device"] = ""
	result["backend_mode"] = backendMode(entry.Candidate)
	result["control_device"] = firstNonEmpty(entry.Candidate.QMIControl, entry.Candidate.ATPort.OpenPath())
	result["radio_live_ok"] = entry.Snapshot != nil && entry.Snapshot.Responsive
	result["traffic"] = map[string]string{}
	result["traffic_raw"] = map[string]int64{}
	result["traffic_meta"] = map[string]any{}
	return result
}

func deviceStatus(entry device.Device) map[string]any {
	summary := deviceSummary(entry)
	return map[string]any{
		"id":                    entry.ID,
		"name":                  summary["name"],
		"healthy":               summary["healthy"],
		"interface":             summary["interface"],
		"public_ip":             "",
		"proxy_port":            1080,
		"network_connected":     false,
		"modem":                 summary["modem"],
		"vowifi":                summary["vowifi_runtime"],
		"sim_service_table":     map[string]any{},
		"pnn":                   []any{},
		"opl":                   []any{},
		"last_hardware_refresh": entry.LastUpdated,
	}
}

func storedDeviceConfig(config store.Device) map[string]any {
	return map[string]any{
		"id":                   config.ID,
		"name":                 config.Name,
		"interface":            config.Interface,
		"control_device":       config.ControlDevice,
		"at_port":              config.ATPort,
		"usb_path":             config.USBPath,
		"audio_device":         config.AudioDevice,
		"modem_imei":           config.ModemIMEI,
		"apn":                  config.APN,
		"proxy_port":           config.ProxyPort,
		"baud_rate":            config.BaudRate,
		"data_bits":            config.DataBits,
		"stop_bits":            config.StopBits,
		"parity":               config.Parity,
		"device_backend":       config.DeviceBackend,
		"esim_transport":       config.ESIMTransport,
		"qmi_use_proxy":        config.QMIUseProxy,
		"qmi_proxy_path":       config.QMIProxyPath,
		"qmi_proxy_executable": config.QMIProxyExecutable,
		"network_enabled":      false,
		"sms_enabled":          config.SMSEnabled,
		"vowifi_enabled":       config.VoWiFiEnabled,
	}
}

func fillConfigFromPhysical(config *store.Device, entry device.Device) {
	candidate := entry.Candidate
	if config.Interface == "" {
		config.Interface = candidate.NetworkInterface
	}
	if config.ControlDevice == "" {
		config.ControlDevice = firstNonEmpty(candidate.QMIControl, candidate.ATPort.OpenPath())
	}
	if config.ATPort == "" {
		config.ATPort = candidate.ATPort.OpenPath()
	}
	if config.USBPath == "" {
		config.USBPath = candidate.USBPath
	}
	if config.ModemIMEI == "" && entry.Snapshot != nil {
		config.ModemIMEI = entry.Snapshot.IMEI
	}
	if config.DeviceBackend == "" {
		config.DeviceBackend = backendMode(candidate)
	}
	if config.ESIMTransport == "" {
		config.ESIMTransport = config.DeviceBackend
	}
}

func modemSummary(snapshot *device.Snapshot, phone string, phoneSource string) map[string]any {
	if snapshot == nil {
		return map[string]any{
			"operator":            "",
			"native_mcc":          "",
			"native_mnc":          "",
			"card_mcc":            "",
			"card_mnc":            "",
			"card_country":        "",
			"service_blocked":     false,
			"blocked_reason":      "",
			"network_mode":        "",
			"radio_band":          "",
			"radio_channel":       0,
			"signal_dbm":          0,
			"signal_sinr":         0,
			"imei":                "",
			"iccid":               "",
			"reg_status":          0,
			"reg_status_text":     "not refreshed",
			"sim_inserted":        false,
			"phone_number":        phone,
			"phone_number_source": phoneSource,
			"model":               "",
		}
	}
	mcc, mnc := splitPLMN(snapshot.OperatorCode)
	cardMCC, cardMNC := device.CardMCCMNC(snapshot.IMSI)
	blockedReason := device.RegionBlockReason(snapshot.IMSI)
	return map[string]any{
		"operator":            snapshot.OperatorName,
		"native_mcc":          mcc,
		"native_mnc":          mnc,
		"card_mcc":            cardMCC,
		"card_mnc":            cardMNC,
		"card_country":        countryNameForMCC(cardMCC),
		"service_blocked":     blockedReason != "",
		"blocked_reason":      blockedReason,
		"network_mode":        snapshot.AccessTech,
		"network_duplex":      "",
		"radio_band":          snapshot.Band,
		"radio_channel":       parseDecimal(snapshot.Channel),
		"signal_dbm":          pointerInt(snapshot.RSSIDBm),
		"signal_rsrp":         pointerInt(snapshot.RSRP),
		"signal_rsrq":         pointerInt(snapshot.RSRQ),
		"signal_sinr":         pointerInt(snapshot.SINR),
		"imei":                snapshot.IMEI,
		"iccid":               snapshot.ICCID,
		"imsi":                snapshot.IMSI,
		"firmware":            snapshot.Firmware,
		"model":               snapshot.Model,
		"reg_status":          boolInt(snapshot.OperatorName != ""),
		"reg_status_text":     registrationText(snapshot),
		"ps_attached":         false,
		"sim_inserted":        snapshot.SIMStatus != "",
		"operating_mode":      snapshot.OperatingMode,
		"phone_number":        phone,
		"phone_number_source": phoneSource,
	}
}

func idleVoWiFiRuntime(id string, snapshot *device.Snapshot) map[string]any {
	iccid := ""
	imsi := ""
	phone := ""
	source := ""
	simReady := false
	if snapshot != nil {
		iccid = snapshot.ICCID
		imsi = snapshot.IMSI
		phone = snapshot.Phone.Number
		source = snapshot.Phone.Source
		simReady = snapshot.SIMReady
	}
	return map[string]any{
		"device_id":           id,
		"phase":               "idle",
		"dataplane_mode":      "",
		"iccid":               iccid,
		"imsi":                imsi,
		"sim_ready":           simReady,
		"access_ready":        false,
		"tunnel_ready":        false,
		"ims_ready":           false,
		"sms_ready":           false,
		"reg_status":          0,
		"reg_status_text":     "not started",
		"network_mode":        "",
		"local_phone":         phone,
		"phone_number_source": source,
		"last_error_class":    "",
		"last_error":          "",
		"last_reason":         "disabled",
		"updated_at":          time.Now().UTC(),
	}
}

func deviceName(entry device.Device) string {
	if entry.Snapshot != nil && strings.TrimSpace(entry.Snapshot.Model) != "" {
		return entry.Snapshot.Model
	}
	if strings.TrimSpace(entry.Candidate.Product) != "" {
		return entry.Candidate.Product
	}
	return entry.ID
}

func backendMode(candidate modem.Candidate) string {
	if candidate.QMIControl != "" {
		return "qmi"
	}
	return "at"
}

func lifecyclePhase(entry device.Device) string {
	switch {
	case !entry.Discovered:
		return "missing"
	case entry.LastError != "":
		return "degraded"
	case entry.Snapshot == nil:
		return "discovered"
	case entry.Snapshot.Responsive:
		return "ready"
	default:
		return "unresponsive"
	}
}

func registrationLabel(snapshot *device.Snapshot) string {
	if snapshot == nil || snapshot.OperatorName == "" {
		return "unknown"
	}
	return "registered"
}

func registrationText(snapshot *device.Snapshot) string {
	if snapshot.OperatorName != "" {
		return "registered"
	}
	return "unknown"
}

func splitPLMN(value string) (string, string) {
	value = strings.TrimSpace(value)
	if len(value) < 5 {
		return "", ""
	}
	return value[:3], value[3:]
}

func parseDecimal(value string) int {
	number, _ := strconv.Atoi(strings.TrimSpace(value))
	return number
}

func pointerInt(value *int) int {
	if value == nil {
		return 0
	}
	return *value
}

func boolInt(value bool) int {
	if value {
		return 1
	}
	return 0
}

func snapshotString(
	snapshot *device.Snapshot,
	selector func(*device.Snapshot) string,
) string {
	if snapshot == nil {
		return ""
	}
	return selector(snapshot)
}

func firstNonEmpty(values ...string) string {
	for _, value := range values {
		if strings.TrimSpace(value) != "" {
			return value
		}
	}
	return ""
}
