package server

import (
	"context"
	"net/http"
	"strings"
	"time"

	"vocat/internal/exportproxy"
	"vocat/internal/store"
)

type cachedPublicIP struct {
	ICCID string
	Info  exportproxy.PublicIPInfo
}

type publicIPResponse struct {
	Detected bool `json:"detected"`
	exportproxy.PublicIPInfo
}

func (s *Server) clearPublicIP(deviceID string) {
	s.publicIPMu.Lock()
	delete(s.publicIPs, strings.TrimSpace(deviceID))
	s.publicIPMu.Unlock()
}

func (s *Server) loadPublicIP(deviceID, iccid string) (exportproxy.PublicIPInfo, bool) {
	deviceID = strings.TrimSpace(deviceID)
	iccid = strings.TrimSpace(iccid)
	s.publicIPMu.RLock()
	entry, ok := s.publicIPs[deviceID]
	s.publicIPMu.RUnlock()
	if !ok {
		return exportproxy.PublicIPInfo{}, false
	}
	// A missing live ICCID means the modem is resetting or no card is present.
	// A different ICCID means the SIM/eSIM profile changed. Either transition
	// invalidates the old cellular exit immediately.
	if iccid == "" || !strings.EqualFold(strings.TrimSpace(entry.ICCID), iccid) {
		s.clearPublicIP(deviceID)
		return exportproxy.PublicIPInfo{}, false
	}
	return entry.Info, true
}

func (s *Server) savePublicIP(deviceID, iccid string, info exportproxy.PublicIPInfo) {
	s.publicIPMu.Lock()
	s.publicIPs[strings.TrimSpace(deviceID)] = cachedPublicIP{
		ICCID: strings.TrimSpace(iccid),
		Info:  info,
	}
	s.publicIPMu.Unlock()
}

func (s *Server) handleCellularPublicIP(w http.ResponseWriter, r *http.Request, config store.Device, iccid string) bool {
	if r.Method != http.MethodGet && r.Method != http.MethodPost {
		w.Header().Set("Allow", "GET, POST")
		writeError(w, http.StatusMethodNotAllowed, "method_not_allowed", "method not allowed")
		return true
	}
	if !s.developerActive(r.Context()) {
		writeError(w, http.StatusForbidden, "developer_mode_required", "public IP detection through roaming data is available only in developer mode")
		return true
	}
	w.Header().Set("Cache-Control", "no-store")
	if r.Method == http.MethodGet {
		info, ok := s.loadPublicIP(config.ID, iccid)
		writeJSON(w, http.StatusOK, map[string]any{"data": publicIPResponse{Detected: ok, PublicIPInfo: info}})
		return true
	}
	if !config.NetworkEnabled {
		writeError(w, http.StatusConflict, "cellular_data_disabled", "enable roaming data before detecting its public IP")
		return true
	}
	if strings.TrimSpace(iccid) == "" {
		writeError(w, http.StatusConflict, "sim_identity_unavailable", "the modem has no current ICCID; refresh it before detecting the public IP")
		return true
	}
	if strings.TrimSpace(config.Interface) == "" {
		writeError(w, http.StatusConflict, "cellular_interface_missing", "the device has no cellular network interface")
		return true
	}
	ctx, cancel := context.WithTimeout(r.Context(), 15*time.Second)
	defer cancel()
	info, err := exportproxy.LookupPublicIP(ctx, config.Interface)
	if err != nil {
		s.logger.Warn("detect roaming public IP failed", "device_id", config.ID, "interface", config.Interface, "error", err)
		writeError(w, http.StatusBadGateway, "public_ip_lookup_failed", err.Error())
		return true
	}
	s.savePublicIP(config.ID, iccid, info)
	writeJSON(w, http.StatusOK, map[string]any{"data": publicIPResponse{Detected: true, PublicIPInfo: info}})
	return true
}
