package handlers

import (
	"crypto/rand"
	"crypto/sha256"
	"encoding/xml"
	"fmt"
	"io"
	"log"
	"math/big"
	"net/http"
	"strconv"
	"time"

	"github.com/gesellix/bose-soundtouch/pkg/models"
	"github.com/gesellix/bose-soundtouch/pkg/service/constants"
	"github.com/gesellix/bose-soundtouch/pkg/service/datastore"
	"github.com/gesellix/bose-soundtouch/pkg/service/marge"
	"github.com/go-chi/chi/v5"
)

// sourceProvidersETag returns a stable ETag for the source providers list,
// derived from the serialized content so it only changes when the list changes.
func sourceProvidersETag() string {
	data, err := marge.SourceProvidersToXML()
	if err != nil {
		return "source-providers-v1"
	}

	sum := sha256.Sum256(data)

	return fmt.Sprintf("%x", sum[:8])
}

// HandleMargeCreateAccount creates a new account from Stockholm (XML).
func (s *Server) HandleMargeCreateAccount(w http.ResponseWriter, r *http.Request) {
	body, err := io.ReadAll(r.Body)
	if err != nil {
		http.Error(w, "Failed to read request body", http.StatusBadRequest)
		return
	}

	var req models.MargeAccountCreateRequest

	err = xml.Unmarshal(body, &req)
	if err != nil {
		http.Error(w, "Invalid XML body: "+err.Error(), http.StatusBadRequest)
		return
	}

	// Use provided ID or generate new 7-digit ID
	var id string

	if req.ID != "" {
		id = req.ID
	} else {
		for {
			n, _ := rand.Int(rand.Reader, big.NewInt(9000000))
			id = strconv.FormatInt(n.Int64()+1000000, 10)

			existing, _ := s.ds.GetAccountInfo(id)
			if existing == nil || existing.IsPlaceholder {
				break
			}
		}
	}

	if !datastore.IsSafeIdentifier(id) {
		http.Error(w, "Invalid account ID", http.StatusBadRequest)
		return
	}

	info := &models.ServiceAccountInfo{
		AccountID:         id,
		PreferredLanguage: req.PreferredLanguage,
	}
	if info.PreferredLanguage == "" {
		info.PreferredLanguage = "en"
	}

	err = s.ds.SaveAccountInfo(id, info)
	if err != nil {
		http.Error(w, "Failed to save account", http.StatusInternalServerError)
		return
	}

	// Stockholm expects the account XML in response
	data, err := marge.AccountFullToXML(s.ds, id)
	if err != nil {
		http.Error(w, "Failed to generate account XML", http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/vnd.bose.streaming-v1.2+xml")
	w.WriteHeader(http.StatusCreated)
	_, _ = w.Write(data)
}

// HandleMargeLogin handles account login from Stockholm.
func (s *Server) HandleMargeLogin(w http.ResponseWriter, r *http.Request) {
	body, err := io.ReadAll(r.Body)
	if err != nil {
		http.Error(w, "Failed to read request body", http.StatusBadRequest)
		return
	}

	var req models.MargeLoginRequest
	if err = xml.Unmarshal(body, &req); err != nil {
		http.Error(w, "Invalid XML body: "+err.Error(), http.StatusBadRequest)
		return
	}

	// Simple mock: find account by email or just return a default one if none exists
	// For now, let's just return a fixed one for testing if nothing else matches
	accounts, err := s.ds.ListAccounts()

	accountID := ""

	if err == nil {
		for _, id := range accounts {
			if id == "default" {
				continue
			}
			// In a real system we'd check email/password
			// Here we just pick the first one or use fallback
			accountID = id

			break
		}
	}

	if accountID == "" {
		http.Error(w, "No accounts found", http.StatusUnauthorized)
		return
	}

	data, err := marge.AccountFullToXML(s.ds, accountID)
	if err != nil {
		http.Error(w, "Failed to generate account XML", http.StatusInternalServerError)
		return
	}

	// Bose returns a token in the Credentials header
	w.Header().Set("Credentials", "mock-token-"+accountID)
	w.Header().Set("Content-Type", "application/vnd.bose.streaming-v1.2+xml")
	_, _ = w.Write(data)
}

// HandleMargeSourceProviders returns the Marge source providers.
func (s *Server) HandleMargeSourceProviders(w http.ResponseWriter, r *http.Request) {
	etag := sourceProvidersETag()
	if r.Header.Get("If-None-Match") == etag {
		w.WriteHeader(http.StatusNotModified)
		return
	}

	data, err := marge.SourceProvidersToXML()
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/vnd.bose.streaming-v1.2+xml")
	w.Header()["ETag"] = []string{etag}
	_, _ = w.Write(data)
}

// HandleMargeAccountFull returns the full Marge account information.
func (s *Server) HandleMargeAccountFull(w http.ResponseWriter, r *http.Request) {
	account := chi.URLParam(r, "account")

	device := r.URL.Query().Get("device")

	etag := s.ds.GetETagForAccount(account, device)
	if r.Header.Get("If-None-Match") == etag {
		w.WriteHeader(http.StatusNotModified)
		return
	}

	data, err := marge.AccountFullToXML(s.ds, account)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/vnd.bose.streaming-v1.2+xml")
	w.Header()["ETag"] = []string{etag}
	_, _ = w.Write(data)
}

// HandleMargeAccountSources returns the Marge account sources.
func (s *Server) HandleMargeAccountSources(w http.ResponseWriter, r *http.Request) {
	account := chi.URLParam(r, "account")

	device := r.URL.Query().Get("device")

	etag := s.ds.GetETagForAccount(account, device)
	if r.Header.Get("If-None-Match") == etag {
		w.WriteHeader(http.StatusNotModified)
		return
	}

	data, err := marge.AccountSourcesToXML(s.ds, account)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/vnd.bose.streaming-v1.1+xml")
	w.Header()["ETag"] = []string{etag}
	_, _ = w.Write(data)
}

// HandleMargeAccountDevices returns the Marge account devices.
func (s *Server) HandleMargeAccountDevices(w http.ResponseWriter, r *http.Request) {
	account := chi.URLParam(r, "account")

	device := r.URL.Query().Get("device")

	etag := s.ds.GetETagForAccount(account, device)
	if r.Header.Get("If-None-Match") == etag {
		w.WriteHeader(http.StatusNotModified)
		return
	}

	data, err := marge.AccountDevicesToXML(s.ds, account)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/vnd.bose.streaming-v1.1+xml")
	w.Header()["ETag"] = []string{etag}
	_, _ = w.Write(data)
}

// HandleMargePowerOn handles the Marge power on request.
func (s *Server) HandleMargePowerOn(w http.ResponseWriter, r *http.Request) {
	body, err := io.ReadAll(r.Body)
	if err != nil {
		log.Printf("[Marge] Failed to read power_on body: %v", err)
		w.WriteHeader(http.StatusOK)

		return
	}

	var req models.CustomerSupportRequest
	if err := xml.Unmarshal(body, &req); err != nil {
		log.Printf("[Marge] Failed to parse power_on body: %v", err)

		// Fallback to remote address if body parsing fails
		if host := clientHost(r); host != "" {
			go s.PrimeDeviceWithSpotify(host)
		}

		w.WriteHeader(http.StatusOK)

		return
	}

	deviceID := req.Device.ID
	deviceIP := req.DiagnosticData.DeviceLandscape.IPAddress

	log.Printf("[Marge] Device %s powered on (IP: %s)", sanitizeLog(deviceID), sanitizeLog(deviceIP))

	// Persist device details provided in the power_on request
	if deviceID != "" && s.ds != nil {
		// Use "default" account if not found or if the device is not yet mapped to an account.
		// In a real scenario, this might be resolved differently if we already have the account info.
		accountID := "default"
		if existing := s.findExistingDeviceInfoByDeviceID(deviceID); existing != nil && existing.AccountID != "" {
			accountID = existing.AccountID
		}

		macAddress := ""
		if len(req.DiagnosticData.DeviceLandscape.MacAddresses) > 0 {
			macAddress = req.DiagnosticData.DeviceLandscape.MacAddresses[0]
		}

		info := &models.ServiceDeviceInfo{
			DeviceID:            deviceID,
			AccountID:           accountID,
			ProductCode:         req.Device.Product.ProductCode,
			DeviceSerialNumber:  req.Device.SerialNumber,
			ProductSerialNumber: req.Device.Product.SerialNumber,
			FirmwareVersion:     req.Device.FirmwareVersion,
			IPAddress:           deviceIP,
			MacAddress:          macAddress,
			DiscoveryMethod:     "power_on",
		}

		if err := s.ds.SaveDeviceInfo(accountID, deviceID, info); err != nil {
			log.Printf("[Marge] Failed to save device info for %s: %s", sanitizeLog(deviceID), sanitizeErr(err))
		}
	}

	// Prefer the TCP source address over the body's self-reported IP for
	// any outbound credential push. The body field is attacker-controllable
	// (a malicious LAN-resident speaker can set it to any value), while
	// clientHost(r) is the actual peer — and if the service runs behind a
	// trusted reverse proxy, the ClientIP middleware has already populated
	// the context from X-Forwarded-For. We log when the two
	// disagree so the discrepancy is investigable but never trust the body.
	remoteHost := clientHost(r)

	if deviceIP != "" && remoteHost != "" && deviceIP != remoteHost {
		log.Printf("[Marge] power_on body IP %q differs from TCP source %q for device %s — using TCP source for credential push",
			sanitizeLog(deviceIP), sanitizeLog(remoteHost), sanitizeLog(deviceID))
	}

	target := remoteHost
	if target == "" {
		// RemoteAddr was unparseable (shouldn't happen under net/http) —
		// fall back to the body so we don't silently skip the push.
		target = deviceIP
	}

	if target != "" {
		go s.PrimeDeviceWithSpotify(target)
	}

	w.WriteHeader(http.StatusOK)
}

// HandleMargeAccountProfile returns the account profile.
func (s *Server) HandleMargeAccountProfile(w http.ResponseWriter, r *http.Request) {
	accountID := chi.URLParam(r, "account")

	// Mock profile data
	profile := models.AccountProfileResponse{
		AccountID:    accountID,
		Email:        "user@example.com",
		FirstName:    "SoundTouch",
		LastName:     "User",
		CountryCode:  "US",
		LanguageCode: "en",
	}

	data, err := xml.MarshalIndent(profile, "", "  ")
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/xml")
	_, _ = w.Write([]byte(xml.Header))
	_, _ = w.Write(data)
}

// HandleMargeUpdateAccountProfile updates the account profile.
func (s *Server) HandleMargeUpdateAccountProfile(w http.ResponseWriter, _ *http.Request) {
	// Stub implementation
	w.WriteHeader(http.StatusOK)
}

// HandleMargeChangePassword changes the account password.
func (s *Server) HandleMargeChangePassword(w http.ResponseWriter, _ *http.Request) {
	// Stub implementation
	w.WriteHeader(http.StatusOK)
}

// HandleMargeGetEmailAddress returns the account email address.
func (s *Server) HandleMargeGetEmailAddress(w http.ResponseWriter, _ *http.Request) {
	resp := models.EmailAddressResponse{
		Email: "user@example.com",
	}

	data, err := xml.MarshalIndent(resp, "", "  ")
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/vnd.bose.streaming-v1.2+xml")
	_, _ = w.Write([]byte(xml.Header))
	_, _ = w.Write(data)
}

// HandleMargeGetDeviceSettings returns device settings.
func (s *Server) HandleMargeGetDeviceSettings(w http.ResponseWriter, _ *http.Request) {
	resp := models.DeviceSettingsResponse{
		Settings: []models.DeviceSetting{
			{Name: "CLOCK_FORMAT", Value: "24HR"},
		},
	}

	data, err := xml.MarshalIndent(resp, "", "  ")
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/vnd.bose.streaming-v1.2+xml")
	_, _ = w.Write([]byte(xml.Header))
	_, _ = w.Write(data)
}

// HandleMargeUpdateDeviceSettings updates device settings.
func (s *Server) HandleMargeUpdateDeviceSettings(w http.ResponseWriter, _ *http.Request) {
	// Stub implementation
	w.WriteHeader(http.StatusOK)
}

// HandleMargeSoftwareUpdate returns the Marge software update information.
func (s *Server) HandleMargeSoftwareUpdate(w http.ResponseWriter, r *http.Request) {
	etag := "default-embedded"
	if r.Header.Get("If-None-Match") == etag {
		w.WriteHeader(http.StatusNotModified)
		return
	}

	w.Header().Set("Content-Type", "application/vnd.bose.streaming-v1.2+xml")
	w.Header()["ETag"] = []string{etag}

	// For the account-specific firmware route, always return the software_update tag.
	// This route is specifically used by firmware like Bose_Lisa/27.0.6.
	if chi.URLParam(r, "account") != "" {
		w.Header().Set("Content-Type", "application/vnd.bose.streaming-v1.2+xml")

		xmlData := marge.SoftwareUpdateToXML()
		w.Header().Set("Content-Length", strconv.Itoa(len(xmlData)))
		_, _ = w.Write([]byte(xmlData))

		return
	}

	if len(swUpdateXML) > 0 {
		w.Header().Set("Content-Length", strconv.Itoa(len(swUpdateXML)))
		_, _ = w.Write(swUpdateXML)
	} else {
		xmlData := marge.SoftwareUpdateToXML()
		w.Header().Set("Content-Length", strconv.Itoa(len(xmlData)))
		_, _ = w.Write([]byte(xmlData))
	}
}

// HandleMargeAccountPresets handles the GET /streaming/account/{account}/presets/all request.
func (s *Server) HandleMargeAccountPresets(w http.ResponseWriter, r *http.Request) {
	account := chi.URLParam(r, "account")

	data, err := marge.AccountPresetsToXML(s.ds, account)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/vnd.bose.streaming-v1.1+xml")
	w.WriteHeader(http.StatusOK)
	_, _ = w.Write(data)
}

// HandleMargePresets returns the Marge presets for a device.
func (s *Server) HandleMargePresets(w http.ResponseWriter, r *http.Request) {
	account := chi.URLParam(r, "account")

	device := chi.URLParam(r, "device")
	if !validatePathID(account) || !validatePathID(device) {
		http.Error(w, "Invalid account or device ID", http.StatusBadRequest)
		return
	}

	etag := strconv.FormatInt(s.ds.GetETagForPresets(account, device), 10)
	if r.Header.Get("If-None-Match") == etag {
		w.WriteHeader(http.StatusNotModified)
		return
	}

	data, err := marge.PresetsToXML(s.ds, account, device)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/vnd.bose.streaming-v1.2+xml")
	w.Header()["ETag"] = []string{etag}
	_, _ = w.Write(data)
}

// HandleMargeUpdatePreset updates a Marge preset.
func (s *Server) HandleMargeUpdatePreset(w http.ResponseWriter, r *http.Request) {
	account := chi.URLParam(r, "account")

	device := chi.URLParam(r, "device")
	if !validatePathID(account) || !validatePathID(device) {
		http.Error(w, "Invalid account or device ID", http.StatusBadRequest)
		return
	}

	etag := strconv.FormatInt(s.ds.GetETagForPresets(account, device), 10)
	w.Header()["ETag"] = []string{etag}

	presetNumberStr := chi.URLParam(r, "presetNumber")

	presetNumber, err := strconv.Atoi(presetNumberStr)
	if err != nil {
		log.Printf("[Marge] Invalid preset number: %s", sanitizeLog(presetNumberStr))
		http.Error(w, "Invalid preset number", http.StatusBadRequest)

		return
	}

	body, err := io.ReadAll(r.Body)
	if err != nil {
		log.Printf("[Marge] Failed to read body: %v", err)
		http.Error(w, "Failed to read body", http.StatusInternalServerError)

		return
	}

	data, err := marge.UpdatePreset(s.ds, account, device, presetNumber, body)
	if err != nil {
		log.Printf("[Marge] UpdatePreset failed for account=%s, device=%s, preset=%d: %s", sanitizeLog(account), sanitizeLog(device), presetNumber, sanitizeErr(err))
		http.Error(w, err.Error(), http.StatusInternalServerError)

		return
	}

	w.Header().Set("Content-Type", "application/vnd.bose.streaming-v1.2+xml")
	_, _ = w.Write(data)
}

// HandleMargeRecents returns the Marge recents for a device.
func (s *Server) HandleMargeRecents(w http.ResponseWriter, r *http.Request) {
	account := chi.URLParam(r, "account")

	device := chi.URLParam(r, "device")
	if !validatePathID(account) || !validatePathID(device) {
		http.Error(w, "Invalid account or device ID", http.StatusBadRequest)
		return
	}

	etag := strconv.FormatInt(s.ds.GetETagForRecents(account, device), 10)
	if r.Header.Get("If-None-Match") == etag {
		w.WriteHeader(http.StatusNotModified)
		return
	}

	data, err := marge.RecentsToXML(s.ds, account, device)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/vnd.bose.streaming-v1.2+xml")
	w.Header()["ETag"] = []string{etag}
	_, _ = w.Write(data)
}

// HandleMargeAddRecent adds a recent item to Marge.
func (s *Server) HandleMargeAddRecent(w http.ResponseWriter, r *http.Request) {
	account := chi.URLParam(r, "account")

	device := chi.URLParam(r, "device")
	if !validatePathID(account) || !validatePathID(device) {
		http.Error(w, "Invalid account or device ID", http.StatusBadRequest)
		return
	}

	etag := strconv.FormatInt(s.ds.GetETagForRecents(account, device), 10)
	w.Header()["ETag"] = []string{etag}

	body, err := io.ReadAll(r.Body)
	if err != nil {
		http.Error(w, "Failed to read body", http.StatusInternalServerError)
		return
	}

	data, err := marge.AddRecent(s.ds, account, device, body)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/vnd.bose.streaming-v1.2+xml")
	w.WriteHeader(http.StatusCreated)
	_, _ = w.Write(data)
}

// HandleMargeAddDevice adds a device to a Marge account.
func (s *Server) HandleMargeAddDevice(w http.ResponseWriter, r *http.Request) {
	account := chi.URLParam(r, "account")
	if !validatePathID(account) {
		http.Error(w, "Invalid account ID", http.StatusBadRequest)
		return
	}

	body, err := io.ReadAll(r.Body)
	if err != nil {
		http.Error(w, "Failed to read body", http.StatusInternalServerError)
		return
	}

	deviceID, data, err := marge.AddDeviceToAccount(s.ds, account, body, clientHost(r))
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/vnd.bose.streaming-v1.2+xml")
	w.Header().Set("Location", s.serverURL+"/account/"+account+"/device/"+deviceID)
	w.WriteHeader(http.StatusCreated)
	_, _ = w.Write(data)
}

// HandleMargeUpdateDevice handles the speaker's rename PUT against
// /streaming/account/{account}/device/{device}. The speaker fires
// this whenever the user renames it via the Bose App or via
// `soundtouch-cli name set`; before this handler existed AfterTouch
// returned 502, the speaker retried in a loop, and the App showed
// the rename hanging indefinitely (issue #285).
//
// The expected payload mirrors the POST shape:
//
//	<device deviceid="DEVID"><name>NEW</name><macaddress>DEVID</macaddress></device>
//
// AddDeviceToAccount is already an upsert via ds.SaveDeviceInfo, so
// rather than introduce a parallel UpdateDevice function we route
// the PUT through the same persistence path. The semantic delta is
// purely in the HTTP envelope: 200 (not 201), no Location header,
// and the deviceID in the body has to match the URL — a mismatch
// means the speaker is targeting the wrong record and we refuse
// rather than silently re-key.
func (s *Server) HandleMargeUpdateDevice(w http.ResponseWriter, r *http.Request) {
	account := chi.URLParam(r, "account")
	if !validatePathID(account) {
		http.Error(w, "Invalid account ID", http.StatusBadRequest)
		return
	}

	device := chi.URLParam(r, "device")
	if !validatePathID(device) {
		http.Error(w, "Invalid device ID", http.StatusBadRequest)
		return
	}

	body, err := io.ReadAll(r.Body)
	if err != nil {
		http.Error(w, "Failed to read body", http.StatusInternalServerError)
		return
	}

	// Validate body deviceID against the URL segment *before* the
	// upsert in AddDeviceToAccount runs — otherwise a mismatched PUT
	// would still persist a row for the body's deviceID before the
	// 400 response, leaving spurious state in the datastore.
	var probe struct {
		DeviceID string `xml:"deviceid,attr"`
	}
	if xmlErr := xml.Unmarshal(body, &probe); xmlErr != nil {
		http.Error(w, xmlErr.Error(), http.StatusBadRequest)
		return
	}

	if probe.DeviceID != device {
		http.Error(w,
			fmt.Sprintf("device ID in body (%q) does not match URL (%q)", probe.DeviceID, device),
			http.StatusBadRequest)

		return
	}

	_, data, err := marge.AddDeviceToAccount(s.ds, account, body, clientHost(r))
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/vnd.bose.streaming-v1.2+xml")
	w.WriteHeader(http.StatusOK)
	_, _ = w.Write(data)
}

// HandleMargeRemovePreset removes a preset for the specified account and device.
func (s *Server) HandleMargeRemovePreset(w http.ResponseWriter, r *http.Request) {
	account := chi.URLParam(r, "account")
	if !validatePathID(account) {
		http.Error(w, "Invalid account ID", http.StatusBadRequest)
		return
	}

	device := chi.URLParam(r, "device")
	if !validatePathID(device) {
		http.Error(w, "Invalid device ID", http.StatusBadRequest)
		return
	}

	presetStr := chi.URLParam(r, "presetNumber")

	presetNumber, err := strconv.Atoi(presetStr)
	if err != nil || presetNumber < 1 || presetNumber > 6 {
		http.Error(w, "Invalid preset number", http.StatusBadRequest)
		return
	}

	if err := marge.RemovePreset(s.ds, account, device, presetNumber); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.WriteHeader(http.StatusOK)
}

// HandleMargeRemoveDevice removes a device from a Marge account.
func (s *Server) HandleMargeRemoveDevice(w http.ResponseWriter, r *http.Request) {
	account := chi.URLParam(r, "account")
	if !validatePathID(account) {
		http.Error(w, "Invalid account ID", http.StatusBadRequest)
		return
	}

	device := chi.URLParam(r, "device")
	if !validatePathID(device) {
		http.Error(w, "Invalid device ID", http.StatusBadRequest)
		return
	}

	if err := marge.RemoveDeviceFromAccount(s.ds, account, device); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.WriteHeader(http.StatusOK)
	_, _ = w.Write([]byte(`{"ok": true}`))
}

// HandleMargeAddSource handles adding a new music source to the account.
// POST /streaming/account/{account}/source
func (s *Server) HandleMargeAddSource(w http.ResponseWriter, r *http.Request) {
	account := chi.URLParam(r, "account")
	if !validatePathID(account) {
		http.Error(w, "Invalid account ID", http.StatusBadRequest)
		return
	}

	body, err := io.ReadAll(r.Body)
	if err != nil {
		log.Printf("[Marge] Failed to read body: %v", err)
		http.Error(w, "Bad Request", http.StatusBadRequest)

		return
	}

	resp, err := marge.AddSourceToAccount(s.ds, account, body)
	if err != nil {
		log.Printf("[Marge] Failed to add source: %v", err)
		http.Error(w, "Internal Server Error", http.StatusInternalServerError)

		return
	}

	w.Header().Set("Content-Type", "application/vnd.bose.streaming-v1.2+xml")
	w.WriteHeader(http.StatusCreated)
	_, _ = w.Write(resp)
}

// HandleMargeDeleteSource removes a configured source from the account
// (DELETE /streaming/account/{account}/source/{sourceID}). It mirrors the
// account-level POST add-source: the source is removed from every device of the
// account. Returns 200 with an empty body, matching the upstream contract.
func (s *Server) HandleMargeDeleteSource(w http.ResponseWriter, r *http.Request) {
	account := chi.URLParam(r, "account")
	sourceID := chi.URLParam(r, "sourceID")

	if !validatePathID(account) || sourceID == "" {
		http.Error(w, "Invalid account or source ID", http.StatusBadRequest)
		return
	}

	if err := marge.RemoveSourceFromAccount(s.ds, account, sourceID); err != nil {
		log.Printf("[Marge] Failed to remove source %s: %v", sanitizeLog(sourceID), sanitizeErr(err))
		http.Error(w, "Internal Server Error", http.StatusInternalServerError)

		return
	}

	w.WriteHeader(http.StatusOK)
}

// HandleMargeProviderSettings returns Marge provider settings.
func (s *Server) HandleMargeProviderSettings(w http.ResponseWriter, r *http.Request) {
	account := chi.URLParam(r, "account")
	if !validatePathID(account) {
		http.Error(w, "Invalid account ID", http.StatusBadRequest)
		return
	}

	w.Header().Set("Content-Type", "application/vnd.bose.streaming-v1.2+xml")
	_, _ = w.Write([]byte(marge.ProviderSettingsToXML(account)))
}

// HandleMargeStreamingToken returns a streaming token for the device.
func (s *Server) HandleMargeStreamingToken(w http.ResponseWriter, _ *http.Request) {
	// Simple mock token for offline use.
	// In a real production environment, this would be a JWT or similar signed token.
	// Some speakers might expect a specific format; we use a distinctive prefix
	// to indicate it's a locally generated token.
	tokenValue := "st-local-token-" + strconv.FormatInt(time.Now().Unix(), 10)
	bearerToken := models.NewBearerToken(tokenValue)

	data, err := xml.Marshal(bearerToken)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/vnd.bose.streaming-v1.2+xml")
	w.Header().Set("Authorization", bearerToken.GetAuthHeader())
	w.WriteHeader(http.StatusOK)
	_, _ = w.Write([]byte(constants.XMLHeader))
	_, _ = w.Write(data)
}

// HandleMargeDeviceGroup returns grouping information for a device.
func (s *Server) HandleMargeDeviceGroup(w http.ResponseWriter, r *http.Request) {
	account := chi.URLParam(r, "account")
	device := chi.URLParam(r, "device")

	w.Header().Set("Content-Type", "application/vnd.bose.streaming-v1.2+xml")

	group, err := s.ds.GetGroupForDevice(account, device)
	if err != nil {
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte(constants.XMLHeader + `<group/>`))

		return
	}

	data, err := xml.Marshal(group)
	if err != nil {
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte(constants.XMLHeader + `<group/>`))

		return
	}

	w.WriteHeader(http.StatusOK)
	_, _ = w.Write([]byte(constants.XMLHeader))
	_, _ = w.Write(data)
}

// HandleMargeDeviceGroupServer returns grouping server information (404 by default if not a server).
func (s *Server) HandleMargeDeviceGroupServer(w http.ResponseWriter, r *http.Request) {
	http.NotFound(w, r)
}

// HandleMargeDeviceGroupMember returns grouping member information (404 by default if not a member).
func (s *Server) HandleMargeDeviceGroupMember(w http.ResponseWriter, r *http.Request) {
	http.NotFound(w, r)
}

// HandleMargeAddGroup creates a new stereo group for an account.
func (s *Server) HandleMargeAddGroup(w http.ResponseWriter, r *http.Request) {
	account := chi.URLParam(r, "account")
	if !validatePathID(account) {
		http.Error(w, "Invalid account ID", http.StatusBadRequest)
		return
	}

	body, err := io.ReadAll(r.Body)
	if err != nil {
		http.Error(w, "Failed to read body", http.StatusInternalServerError)
		return
	}

	var group models.Group
	if xmlErr := xml.Unmarshal(body, &group); xmlErr != nil {
		http.Error(w, "Invalid XML", http.StatusBadRequest)
		return
	}

	id, err := s.ds.AddGroup(account, &group)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	data, err := xml.Marshal(&group)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/vnd.bose.streaming-v1.2+xml")
	w.Header().Set("Location", s.serverURL+"/account/"+account+"/group/"+id)
	w.WriteHeader(http.StatusCreated)
	_, _ = w.Write([]byte(constants.XMLHeader))
	_, _ = w.Write(data)
}

// HandleMargeModifyGroup updates the name of an existing stereo group.
func (s *Server) HandleMargeModifyGroup(w http.ResponseWriter, r *http.Request) {
	account := chi.URLParam(r, "account")
	groupID := chi.URLParam(r, "groupId")

	if !validatePathID(account) || !validatePathID(groupID) {
		http.Error(w, "Invalid ID", http.StatusBadRequest)
		return
	}

	body, err := io.ReadAll(r.Body)
	if err != nil {
		http.Error(w, "Failed to read body", http.StatusInternalServerError)
		return
	}

	var req models.Group
	if xmlErr := xml.Unmarshal(body, &req); xmlErr != nil {
		http.Error(w, "Invalid XML", http.StatusBadRequest)
		return
	}

	updated, err := s.ds.ModifyGroup(account, groupID, req.Name)
	if err != nil {
		http.Error(w, err.Error(), http.StatusNotFound)
		return
	}

	data, err := xml.Marshal(updated)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/vnd.bose.streaming-v1.2+xml")
	_, _ = w.Write([]byte(constants.XMLHeader))
	_, _ = w.Write(data)
}

// HandleMargeDeleteGroup removes a stereo group identified by {groupId}.
func (s *Server) HandleMargeDeleteGroup(w http.ResponseWriter, r *http.Request) {
	account := chi.URLParam(r, "account")
	groupID := chi.URLParam(r, "groupId")

	if !validatePathID(account) || !validatePathID(groupID) {
		http.Error(w, "Invalid ID", http.StatusBadRequest)
		return
	}

	if err := s.ds.DeleteGroup(account, groupID); err != nil {
		http.Error(w, err.Error(), http.StatusNotFound)
		return
	}

	w.Header().Set("Content-Type", "application/vnd.bose.streaming-v1.2+xml")
	w.WriteHeader(http.StatusOK)
	_, _ = w.Write([]byte(constants.XMLHeader + `<status>Group deleted successfully</status>`))
}

// HandleMargeDeleteAccountGroups removes all stereo groups stored for an
// account. Speakers send DELETE /streaming/account/{id}/group/ (trailing
// slash, no group ID) during stereo-pair teardown. Master and slave often
// live in different accounts, so each speaker deletes its own copy here.
func (s *Server) HandleMargeDeleteAccountGroups(w http.ResponseWriter, r *http.Request) {
	account := chi.URLParam(r, "account")

	if !validatePathID(account) {
		http.Error(w, "Invalid account ID", http.StatusBadRequest)
		return
	}

	if err := s.ds.DeleteAllGroupsForAccount(account); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/vnd.bose.streaming-v1.2+xml")
	w.WriteHeader(http.StatusOK)
	_, _ = w.Write([]byte(constants.XMLHeader + `<status>Group deleted successfully</status>`))
}

// HandleMusicProviderIsEligible returns the music provider eligibility.
func (s *Server) HandleMusicProviderIsEligible(w http.ResponseWriter, _ *http.Request) {
	// For now, we return false as seen in the interaction sample.
	resp := models.EligibilityResponse{
		IsEligible: false,
	}

	data, err := xml.Marshal(resp)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/vnd.bose.streaming-v1.1+xml")
	w.WriteHeader(http.StatusOK)
	_, _ = w.Write([]byte(constants.XMLHeader))
	_, _ = w.Write(data)
}

// HandleMargeAPIVersions returns the XML response for Marge API versions.
func (s *Server) HandleMargeAPIVersions(w http.ResponseWriter, _ *http.Request) {
	w.Header().Set("Content-Type", "text/xml")

	output, err := marge.APIVersionsToXML()
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	_, _ = w.Write(output)
}

// HandleMargeCustomerSupport handles Marge customer support uploads.
func (s *Server) HandleMargeCustomerSupport(w http.ResponseWriter, r *http.Request) {
	body, err := io.ReadAll(r.Body)
	if err != nil {
		http.Error(w, "Failed to read body", http.StatusBadRequest)
		return
	}

	var req models.CustomerSupportRequest
	if err = xml.Unmarshal(body, &req); err != nil {
		// Log error but might still return 200 as Bose expects
		log.Printf("Failed to unmarshal CustomerSupportRequest: %v", err)
	}

	// Create a DeviceEvent for support data
	event := models.DeviceEvent{
		Type:     "customer-support-upload",
		Time:     time.Now().Format(time.RFC3339),
		MonoTime: time.Now().UnixNano() / int64(time.Millisecond),
		Data: map[string]interface{}{
			"firmware": req.Device.FirmwareVersion,
			"product":  req.Device.Product.ProductCode,
			"ip":       req.DiagnosticData.DeviceLandscape.IPAddress,
			"rssi":     req.DiagnosticData.DeviceLandscape.RSSI,
		},
	}
	s.ds.AddDeviceEvent(req.Device.ID, event)

	// Update DeviceInfo if possible
	devices, err := s.ds.ListAllDevices()
	if err == nil {
		var account string

		for i := range devices {
			dev := &devices[i]
			if dev.DeviceID == req.Device.ID {
				account = dev.AccountID
				break
			}
		}

		if account != "" {
			info, err := s.ds.GetDeviceInfo(account, req.Device.ID)
			if err == nil && info != nil {
				info.IPAddress = req.DiagnosticData.DeviceLandscape.IPAddress

				info.FirmwareVersion = req.Device.FirmwareVersion
				if len(req.DiagnosticData.DeviceLandscape.MacAddresses) > 0 {
					info.MacAddress = req.DiagnosticData.DeviceLandscape.MacAddresses[0]
				}

				_ = s.ds.SaveDeviceInfo(account, req.Device.ID, info)
			}
		}
	}

	w.WriteHeader(http.StatusOK)
}
