// Command rebreak-supervise-magic ist die CLI für unsupervised→supervised
// Migration ohne Datenverlust via Apple MCInstall-Service.
package main

import (
	"bufio"
	"flag"
	"fmt"
	"os"
	"sort"
	"strings"
	"time"

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

	"github.com/raynis/rebreak-supervise-magic/internal/cert"
	"github.com/raynis/rebreak-supervise-magic/internal/device"
	"github.com/raynis/rebreak-supervise-magic/internal/dlmessage"
	"github.com/raynis/rebreak-supervise-magic/internal/mcinstall"
	"github.com/raynis/rebreak-supervise-magic/internal/preflight"
	"github.com/raynis/rebreak-supervise-magic/internal/supervise"
)

func init() {
	// Wire authoritative IsSupervised-check via MCInstall.GetCloudConfiguration.
	device.CheckSupervisedFunc = func(dev ios.DeviceEntry) (bool, error) {
		mc, err := mcinstall.Open(dev)
		if err != nil {
			return false, nil // can't check → assume unsupervised
		}
		defer mc.Close()
		_ = mc.HelloHostIdentifier()
		cfg, err := mc.GetCloudConfiguration()
		if err != nil || cfg == nil {
			return false, nil
		}
		v, _ := cfg["IsSupervised"].(bool)
		return v, nil
	}
}

func dlmsgDebugSet() {
	dlmessage.DebugMode = true
}

const usage = `rebreak-supervise-magic — iPhone/iPad-Supervision ohne Datenverlust

Usage:
  rebreak-supervise-magic <command> [flags]

Commands:
  check         Print device info + supervision status. No writes.
  cert-info     Print supervision-cert path + status. No writes.
  cloud-config  Read current Cloud-Configuration via MCInstall. No writes.
  supervise     SetCloudConfiguration(IsSupervised=true) + reboot.
  unsupervise   Reverse-flow — IsSupervised=false + reboot.

Global flags:
  -v           Verbose / debug output.
  -udid <id>   Target specific device (default: first detected).
  -org <name>  OrganizationName shown in Settings (default: "ReBreak").
  -dry-run     Run all checks + plan but skip writes/reboot.
  -yes         Skip confirmation prompt (use in scripts).
  -force       Allow supervise on already-supervised devices (re-supervise / change orgname).

Examples:
  rebreak-supervise-magic check
  rebreak-supervise-magic cloud-config
  rebreak-supervise-magic supervise -org "ReBreak" -yes
  rebreak-supervise-magic -v -dry-run supervise
`

type cliOpts struct {
	verbose bool
	udid    string
	orgName string
	dryRun  bool
	yes     bool
	force   bool
}

func main() {
	if len(os.Args) < 2 {
		fmt.Fprint(os.Stderr, usage)
		os.Exit(2)
	}

	cmd := os.Args[1]
	if cmd == "-h" || cmd == "--help" || cmd == "help" {
		fmt.Print(usage)
		return
	}

	fs := flag.NewFlagSet("rebreak-supervise-magic", flag.ExitOnError)
	opts := &cliOpts{}
	fs.BoolVar(&opts.verbose, "v", false, "verbose output")
	fs.StringVar(&opts.udid, "udid", "", "target UDID (default: first device)")
	fs.StringVar(&opts.orgName, "org", "ReBreak", "OrganizationName")
	fs.BoolVar(&opts.dryRun, "dry-run", false, "skip writes/reboot")
	fs.BoolVar(&opts.yes, "yes", false, "skip confirm prompt")
	fs.BoolVar(&opts.force, "force", false, "allow supervise on already-supervised devices")
	// Optional DLMessage debug-mode via env
	if os.Getenv("REBREAK_DLMSG_DEBUG") == "1" {
		dlmsgDebugSet()
	}
	if err := fs.Parse(os.Args[2:]); err != nil {
		fmt.Fprintln(os.Stderr, "flag parse error:", err)
		os.Exit(2)
	}

	switch cmd {
	case "check":
		exitOnErr(runCheck(opts))
	case "cert-info":
		exitOnErr(runCertInfo(opts))
	case "cloud-config":
		exitOnErr(runCloudConfig(opts))
	case "supervise":
		exitOnErr(runSupervise(opts))
	case "unsupervise":
		exitOnErr(runUnsupervise(opts))
	default:
		fmt.Fprintf(os.Stderr, "unknown command: %s\n\n", cmd)
		fmt.Fprint(os.Stderr, usage)
		os.Exit(2)
	}
}

func runCheck(opts *cliOpts) error {
	conn, err := device.Connect(opts.udid)
	if err != nil {
		return err
	}
	defer conn.Close()

	res, err := preflight.Run(conn)
	if err != nil {
		return err
	}

	fmt.Println("Device:")
	fmt.Printf("  UDID:            %s\n", res.Device.UDID)
	fmt.Printf("  Name:            %s\n", res.Device.DeviceName)
	fmt.Printf("  Type:            %s\n", res.Device.ProductType)
	fmt.Printf("  iOS:             %s\n", res.Device.ProductVersion)
	fmt.Printf("  ActivationState: %s\n", res.Device.ActivationState)
	fmt.Printf("  FindMyEnabled:   %v\n", res.Device.FindMyEnabled)
	fmt.Printf("  IsSupervised:    %v\n", res.Device.IsSupervised)
	fmt.Println()
	if res.OK {
		fmt.Println("Pre-Flight: ✓ ready for supervise")
	} else {
		fmt.Println("Pre-Flight: ✗ not ready:")
		for _, r := range res.Reasons {
			fmt.Printf("  - %s\n", r)
		}
		return fmt.Errorf("pre-flight failed")
	}
	return nil
}

func runCertInfo(opts *cliOpts) error {
	dir := cert.DefaultDir()
	fmt.Printf("Supervision identity directory: %s\n", dir)
	id, err := cert.LoadOrCreate()
	if err != nil {
		return err
	}
	fmt.Printf("  Cert (DER):  %d bytes\n", len(id.CertDER))
	fmt.Printf("  Key  (DER):  %d bytes\n", len(id.PrivateKeyDER))
	fmt.Println()
	fmt.Println("Identity ready — usable for supervise command.")
	return nil
}

func runCloudConfig(opts *cliOpts) error {
	conn, err := device.Connect(opts.udid)
	if err != nil {
		return err
	}
	defer conn.Close()

	mc, err := mcinstall.Open(conn.Device())
	if err != nil {
		return err
	}
	defer mc.Close()
	if err := mc.HelloHostIdentifier(); err != nil {
		return err
	}
	cfg, err := mc.GetCloudConfiguration()
	if err != nil {
		return err
	}

	if cfg == nil {
		fmt.Println("(no cloud configuration set on device)")
		return nil
	}

	keys := make([]string, 0, len(cfg))
	for k := range cfg {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	fmt.Println("Cloud Configuration (live from device MCInstall):")
	for _, k := range keys {
		val := cfg[k]
		switch v := val.(type) {
		case []byte:
			fmt.Printf("  %-40s = [%d bytes binary]\n", k, len(v))
		case [][]byte:
			fmt.Printf("  %-40s = [%d certs]\n", k, len(v))
		default:
			fmt.Printf("  %-40s = %v\n", k, v)
		}
	}
	return nil
}

func runSupervise(opts *cliOpts) error {
	conn, err := device.Connect(opts.udid)
	if err != nil {
		return err
	}
	res, err := preflight.Run(conn)
	conn.Close()
	if err != nil {
		return err
	}
	if !res.OK {
		fmt.Println("Pre-Flight failed:")
		for _, r := range res.Reasons {
			fmt.Printf("  - %s\n", r)
		}
		return fmt.Errorf("pre-flight blocking supervise")
	}
	if res.Device.IsSupervised && !opts.force {
		fmt.Printf("Device already supervised (UDID=%s). Use --force to re-supervise (overwrites existing supervisor cert).\n", res.Device.UDID)
		return nil
	}

	fmt.Printf("About to supervise device:\n  %s (%s, iOS %s)\n",
		res.Device.DeviceName, res.Device.ProductType, res.Device.ProductVersion)
	if res.Device.IsSupervised {
		fmt.Println("⚠ Device IS already supervised — will OVERWRITE existing supervisor cert with our own.")
		fmt.Println("  Existing cert cannot be restored without external tool (TechLockdown / Apple Configurator).")
	}
	fmt.Printf("OrganizationName: %s\n", opts.orgName)
	fmt.Println("Device will reboot. No data loss expected.")
	if !opts.yes && !opts.dryRun {
		if !confirm("Continue? [y/N]: ") {
			return fmt.Errorf("aborted by user")
		}
	}

	return supervise.Supervise(res.Device.UDID, supervise.Options{
		OrgName:    opts.orgName,
		DryRun:     opts.dryRun,
		Verbose:    opts.verbose,
		BackupPath: defaultBackupPath(res.Device.UDID),
	})
}

func defaultBackupPath(udid string) string {
	short := udid
	if len(short) > 12 {
		short = short[:12]
	}
	ts := time.Now().UTC().Format("20060102-150405")
	return fmt.Sprintf("/tmp/rebreak-supervise-backup-%s-%s.json", short, ts)
}

func runUnsupervise(opts *cliOpts) error {
	conn, err := device.Connect(opts.udid)
	if err != nil {
		return err
	}
	res, err := preflight.Run(conn)
	conn.Close()
	if err != nil {
		return err
	}
	if !res.Device.IsSupervised {
		fmt.Printf("Device already un-supervised (UDID=%s). Nothing to do.\n", res.Device.UDID)
		return nil
	}

	fmt.Printf("About to UN-supervise device:\n  %s (%s, iOS %s)\n",
		res.Device.DeviceName, res.Device.ProductType, res.Device.ProductVersion)
	if !opts.yes && !opts.dryRun {
		if !confirm("Continue? [y/N]: ") {
			return fmt.Errorf("aborted by user")
		}
	}
	return supervise.Unsupervise(res.Device.UDID, supervise.Options{
		OrgName: opts.orgName,
		DryRun:  opts.dryRun,
		Verbose: opts.verbose,
	})
}

func confirm(prompt string) bool {
	fmt.Print(prompt)
	reader := bufio.NewReader(os.Stdin)
	line, _ := reader.ReadString('\n')
	line = strings.TrimSpace(strings.ToLower(line))
	return line == "y" || line == "yes"
}

func exitOnErr(err error) {
	if err == nil {
		return
	}
	if !strings.HasPrefix(err.Error(), "aborted") {
		fmt.Fprintln(os.Stderr, "ERROR:", err)
	}
	os.Exit(1)
}

