// Package device wrappt go-ios's DeviceEntry. Liefert die hochlevel-Calls
// die wir brauchen: Info dumpen, IsSupervised checken, FMI-Status, Reboot,
// WaitForReconnect.
package device

import (
	"errors"
	"fmt"
	"time"

	ios "github.com/danielpaulus/go-ios/ios"
	"github.com/danielpaulus/go-ios/ios/diagnostics"
)

// Conn ist unser Wrapper. Hält die go-ios DeviceEntry + cached UDID.
type Conn struct {
	device ios.DeviceEntry
	udid   string
}

// Connect öffnet eine Verbindung zum (ersten) verbundenen iPhone.
// Wenn udid != "" filtert auf spezifisches Gerät.
func Connect(udid string) (*Conn, error) {
	list, err := ios.ListDevices()
	if err != nil {
		return nil, fmt.Errorf("device: list devices: %w", err)
	}
	if len(list.DeviceList) == 0 {
		return nil, errors.New("device: no iPhone/iPad detected via USB — connect device + tap 'Trust this computer'")
	}

	for _, d := range list.DeviceList {
		if udid == "" || d.Properties.SerialNumber == udid {
			return &Conn{
				device: d,
				udid:   d.Properties.SerialNumber,
			}, nil
		}
	}
	return nil, fmt.Errorf("device: no device matching UDID %s", udid)
}

// UDID exposes the device serial.
func (c *Conn) UDID() string { return c.udid }

// Device returnt das underlying go-ios DeviceEntry für direct API-Calls
// (z.B. mcinstall.New(conn.Device())).
func (c *Conn) Device() ios.DeviceEntry { return c.device }

// Info dumps Lockdown-Values als map. Top-Level (kein Domain).
func (c *Conn) Info() (map[string]interface{}, error) {
	return ios.GetValuesPlist(c.device)
}

// IsSupervised — wird via lazy-injected mcinstall-call gechecked. Default
// returnt false; callers können CheckSupervisedFunc setzen um authoritative
// MCInstall-check zu enablen (vermeidet circular import).
//
// Wir nutzen einen package-level function-pointer der vom main-package gesetzt
// wird (siehe cmd/supervise/main.go init). So bleibt device-package unabhängig
// von mcinstall.
func (c *Conn) IsSupervised() (bool, error) {
	if CheckSupervisedFunc != nil {
		return CheckSupervisedFunc(c.device)
	}
	return false, nil
}

// CheckSupervisedFunc — set by main package to inject mcinstall-based check.
// Signature: takes go-ios DeviceEntry, returns (supervised, error).
var CheckSupervisedFunc func(ios.DeviceEntry) (bool, error)

// FindMyEnabled — parsed NonVolatileRAM["fm-activation-locked"] als ASCII-
// String. "YES" = FMI an, "NO" = aus. Empirisch verifiziert 2026-05-26 (iOS 26.5).
func (c *Conn) FindMyEnabled() (bool, error) {
	info, err := c.Info()
	if err != nil {
		return false, nil
	}
	nvram, ok := info["NonVolatileRAM"].(map[string]interface{})
	if !ok {
		return false, nil
	}
	lock, ok := nvram["fm-activation-locked"]
	if !ok {
		return false, nil
	}
	switch v := lock.(type) {
	case []byte:
		return string(v) == "YES", nil
	case []interface{}:
		bytes := make([]byte, 0, len(v))
		for _, b := range v {
			if f, ok := b.(uint64); ok {
				bytes = append(bytes, byte(f))
			} else if f, ok := b.(float64); ok {
				bytes = append(bytes, byte(f))
			}
		}
		return string(bytes) == "YES", nil
	case string:
		return v == "YES", nil
	}
	return false, nil
}

// ActivationState — interessant für check-CLI.
func (c *Conn) ActivationState() (string, error) {
	info, err := c.Info()
	if err != nil {
		return "", err
	}
	if s, ok := info["ActivationState"].(string); ok {
		return s, nil
	}
	return "", nil
}

// GetValueForDomain — wrapper über go-ios's Lockdown-Domain-Query.
// Brauchen wir für restriction-Domain-Heuristik.
func (c *Conn) GetValueForDomain(domain, key string) (interface{}, error) {
	conn, err := ios.ConnectLockdownWithSession(c.device)
	if err != nil {
		return nil, err
	}
	defer conn.Close()
	return conn.GetValueForDomain(key, domain)
}

// Reboot triggert iPhone-Restart via Diagnostics-Service.
func (c *Conn) Reboot() error {
	return diagnostics.Reboot(c.device)
}

// WaitForReconnect pollt bis das Gerät nach Reboot wieder via usbmux
// sichtbar ist (oder Timeout). Default 90s.
func WaitForReconnect(udid string, timeout time.Duration) (*Conn, error) {
	deadline := time.Now().Add(timeout)
	for time.Now().Before(deadline) {
		conn, err := Connect(udid)
		if err == nil {
			return conn, nil
		}
		time.Sleep(2 * time.Second)
	}
	return nil, fmt.Errorf("device: reconnect timeout after %v", timeout)
}

// Close — go-ios DeviceEntry hat keinen expliziten Close, alles wird per-call connected.
func (c *Conn) Close() error { return nil }
