package setup

import (
	"encoding/xml"
	"fmt"
	"net/url"
	"os"
	"strings"
)

// applyProbeToSummary populates the SSH-derived fields of a
// MigrationSummary directly from a batched speakerProbe. Mirrors what
// the per-helper path (checkCurrentConfig + checkRemoteServices +
// checkCACertTrusted + the inline resolv read) used to do across
// multiple SSH dials.
//
// One subtle difference from the legacy path: the original
// checkCurrentConfig has a fallback that reads the file via base64 when
// `cat` returns empty but the file has size > 0. The batched script
// already does base64 for every file, so that fallback is implicit —
// any readable file appears in probe.Files.
func (m *Manager) applyProbeToSummary(
	summary *MigrationSummary,
	probe *speakerProbe,
	plannedCfg *PrivateCfg,
	proxyURL, targetURL string,
	options map[string]string,
) {
	summary.SSHSuccess = probe.SSHOK

	if !probe.SSHOK && probe.Err != nil {
		summary.CurrentConfig = fmt.Sprintf("SSH connection failed: %v", probe.Err)
	}

	m.applyProbeCurrentConfig(summary, probe, plannedCfg, proxyURL, targetURL, options)
	applyProbeResolvConf(summary, probe)
	applyProbeRemoteServices(summary, probe)
	m.applyProbeCACert(summary, probe)
}

// applyProbeCurrentConfig populates CurrentConfig / OriginalConfig and
// parses the on-disk SoundTouchSdkPrivateCfg.xml into ParsedCurrentConfig.
// Also applies proxy options to the planned config when the caller asks
// for it — that path needs the parsed current config.
func (m *Manager) applyProbeCurrentConfig(
	summary *MigrationSummary,
	probe *speakerProbe,
	plannedCfg *PrivateCfg,
	proxyURL, targetURL string,
	options map[string]string,
) {
	if cfg, ok := probe.Files[SoundTouchSdkPrivateCfgPath]; ok && cfg != "" {
		summary.CurrentConfig = cfg
		fmt.Printf("Current config from %s (length: %d):\n%q\n", probeDeviceTagFor(summary), len(cfg), cfg)

		var currentCfg PrivateCfg
		if xml.Unmarshal([]byte(cfg), &currentCfg) == nil {
			summary.ParsedCurrentConfig = &currentCfg

			if proxyURL == "" {
				proxyURL = targetURL
			}

			if options != nil {
				m.applyProxyOptions(plannedCfg, proxyURL, options, &currentCfg)
			}
		}
	}

	if orig, ok := probe.Files[SoundTouchSdkPrivateCfgPath+".original"]; ok && orig != "" {
		summary.OriginalConfig = orig
	}
}

// applyProbeResolvConf caches the /etc/resolv.conf contents on the
// summary so checkIsMigratedFromProbe doesn't have to make another dial.
func applyProbeResolvConf(summary *MigrationSummary, probe *speakerProbe) {
	if resolv, ok := probe.Files["/etc/resolv.conf"]; ok {
		summary.CurrentResolvConf = resolv
	}
}

// applyProbeRemoteServices records which remote_services marker files
// exist on the device — these toggle SSH enablement state.
func applyProbeRemoteServices(summary *MigrationSummary, probe *speakerProbe) {
	for _, loc := range []string{"/etc/remote_services", "/mnt/nv/remote_services", "/tmp/remote_services"} {
		if !probe.Exists[loc] {
			continue
		}

		summary.RemoteServicesFound = append(summary.RemoteServicesFound, loc)
		summary.RemoteServicesEnabled = true

		if loc != "/tmp/remote_services" {
			summary.RemoteServicesPersistent = true
		}
	}
}

// applyProbeCACert checks the device's CA bundle for our injection. The
// fast path is the CALabel grep (works without Manager.Crypto and is the
// only path the CLI ever uses). The fallback that compares the actual
// cert payload runs only when Manager.Crypto is configured — i.e., in
// the web-UI / in-process flow.
func (m *Manager) applyProbeCACert(summary *MigrationSummary, probe *speakerProbe) {
	bundle, ok := probe.Files["/etc/pki/tls/certs/ca-bundle.crt"]
	if !ok || bundle == "" {
		return
	}

	if strings.Contains(bundle, CALabel) {
		summary.CACertTrusted = true
		return
	}

	if m.Crypto == nil {
		return
	}

	caCertPEM, err := os.ReadFile(m.Crypto.GetCACertPath())
	if err != nil {
		return
	}

	for _, line := range strings.Split(string(caCertPEM), "\n") {
		if line == "" || strings.Contains(line, "BEGIN CERTIFICATE") || strings.Contains(line, "END CERTIFICATE") {
			continue
		}

		if strings.Contains(bundle, line) {
			summary.CACertTrusted = true
		}

		break
	}
}

// probeDeviceTagFor returns a short identifier for the device used in the
// "Current config from …" log line. We keep the legacy log shape so any
// downstream log scraping continues to work.
func probeDeviceTagFor(summary *MigrationSummary) string {
	if summary.DeviceID != "" {
		return summary.DeviceID
	}

	return "unknown"
}

// checkIsMigratedFromProbe is the probe-driven equivalent of
// checkIsMigrated. Unlike the legacy path it makes no fresh SSH dials —
// every file it inspects came from the single batched probe.
//
// Behavioural note: the legacy isResolvConfMigrated has a third fallback
// that DNS-resolves the target host via SSH (`getent` on the device) so
// it can match the *resolved* IP against resolv.conf. The probe path
// skips that — it would force a second SSH dial just for the corner
// case where the device's resolv.conf has the resolved IP but neither
// the marker comment nor the target hostname. In practice the hook file
// or marker comment is always present, so this is acceptable.
func (m *Manager) checkIsMigratedFromProbe(summary *MigrationSummary, probe *speakerProbe) {
	summary.TelnetMigrated = m.isTelnetMigrated(summary)

	if probe.SSHOK {
		summary.XMLMigrated = m.isXMLMigrated(summary)
		summary.HostsMigrated = isHostsMigratedFromProbe(probe, summary)
		summary.ResolvMigrated = m.isResolvConfMigratedFromProbe(probe, summary)
	}

	summary.IsMigrated = summary.TelnetMigrated ||
		summary.XMLMigrated ||
		summary.HostsMigrated ||
		summary.ResolvMigrated
}

func isHostsMigratedFromProbe(probe *speakerProbe, summary *MigrationSummary) bool {
	hostsContent, ok := probe.Files["/etc/hosts"]
	if !ok {
		return false
	}

	boseDomains := []string{
		"streaming.bose.com",
		"updates.bose.com",
		"stats.bose.com",
		"bmx.bose.com",
	}

	for _, domain := range boseDomains {
		if strings.Contains(hostsContent, domain) && summary.CACertTrusted {
			return true
		}
	}

	return false
}

func (m *Manager) isResolvConfMigratedFromProbe(probe *speakerProbe, summary *MigrationSummary) bool {
	if probe.Exists["/mnt/nv/aftertouch.resolv.conf"] {
		return summary.CACertTrusted
	}

	if summary.CurrentResolvConf == "" {
		return false
	}

	if strings.Contains(summary.CurrentResolvConf, "# Priority nameserver for Bose service redirection") && summary.CACertTrusted {
		return true
	}

	parsedTarget, err := url.Parse(m.ServerURL)
	if err != nil {
		return false
	}

	targetHost := parsedTarget.Hostname()
	if strings.Contains(summary.CurrentResolvConf, targetHost) && summary.CACertTrusted {
		return true
	}

	return false
}
