package main

import (
	"context"
	"log"
	"time"

	"SatMaster/backend/doppler"
	"SatMaster/backend/flexradio"
	"SatMaster/backend/propagator"
	"SatMaster/backend/rotor"
	"SatMaster/backend/skycat"
	"SatMaster/backend/tle"

	"github.com/wailsapp/wails/v2/pkg/runtime"
)

// RadioClient is the common interface for FlexRadio and SkyCAT backends.
type RadioClient interface {
	IsConnected() bool
	SetFrequency(downHz, upHz float64) error
	SetMode(mode string) error
	ResetDeadband()
	Disconnect()
}

// App holds all subsystems and is the Wails binding target.
type App struct {
	dopplerEnabled bool
	rotorEnabled   bool
	rotorAzOnly    bool
	trackFreqMode  bool // Track freq+mode for current sat
	trackAzimuth   bool // Track azimuth for current sat
	savedRxSlice   int
	savedTxSlice   int
	radioType      string // "flex" or "skycat"
	ctx            context.Context

	tleManager  *tle.Manager
	propagator  *propagator.Engine
	flexRadio   *flexradio.Client
	skyCat      *skycat.Client
	rotorClient *rotor.PstRotatorClient
	dopplerCalc *doppler.Calculator

	// Tracking state
	trackedSat  string
	trackingOn  bool
	cancelTrack context.CancelFunc
	watchlist   []string
}

func NewApp() *App {
	return &App{
		dopplerEnabled: true,
		rotorEnabled:   true,
		rotorAzOnly:    true,
		trackFreqMode:  false,
		trackAzimuth:   false,
		savedRxSlice:   0, // defaults — overridden by SetSliceConfig from Settings on connect
		savedTxSlice:   1,
		radioType:      "flex",
		tleManager:     tle.NewManager(),
		propagator:     propagator.NewEngine(),
		flexRadio:      flexradio.NewClient(),
		skyCat:         skycat.NewClient(),
		rotorClient:    rotor.NewPstRotatorClient(),
		dopplerCalc:    doppler.NewCalculator(),
		watchlist:      []string{"ISS (ZARYA)", "AO-7", "AO-27", "SO-50", "RS-44", "AO-91", "FO-29"},
	}
}

func (a *App) startup(ctx context.Context) {
	a.ctx = ctx
	log.Println("[SatMaster] Startup")

	// Load TLEs on startup (try remote, fallback local)
	go func() {
		if err := a.tleManager.FetchAndCache(); err != nil {
			log.Printf("[TLE] Remote fetch failed: %v — loading local cache", err)
			a.tleManager.LoadLocal()
		}
		runtime.EventsEmit(ctx, "tle:loaded", a.tleManager.SatelliteNames())
		// Immediately emit positions after TLE load
		allSats := a.tleManager.All()
		log.Printf("[TLE] Loaded %d satellites, watchlist=%v", len(allSats), a.watchlist)
		var initSats []*tle.Satellite
		for _, name := range a.watchlist {
			if s := a.tleManager.Get(name); s != nil {
				initSats = append(initSats, s)
			}
		}
		if len(initSats) == 0 {
			log.Printf("[TLE] Watchlist resolved 0 sats, using all %d", len(allSats))
			initSats = allSats
		} else {
			log.Printf("[TLE] Watchlist resolved %d sats", len(initSats))
		}
		if positions := a.propagator.AllPositions(initSats, time.Now()); len(positions) > 0 {
			log.Printf("[TLE] Emitting %d initial positions", len(positions))
			runtime.EventsEmit(ctx, "sat:positions", positions)
		}
	}()

	// Emit position updates every second (filtered by watchlist)
	go a.positionLoop(ctx)
}

// activeRadio returns the currently selected radio client.
func (a *App) activeRadio() RadioClient {
	if a.radioType == "skycat" {
		return a.skyCat
	}
	return a.flexRadio
}

func (a *App) shutdown(ctx context.Context) {
	a.StopTracking()
	a.flexRadio.Disconnect()
	a.skyCat.Disconnect()
}

// positionLoop emits sat positions to frontend every second.
func (a *App) positionLoop(ctx context.Context) {
	ticker := time.NewTicker(1 * time.Second)
	defer ticker.Stop()
	for {
		select {
		case <-ctx.Done():
			return
		case <-ticker.C:
			// Build sat list from watchlist; fall back to all if empty/unresolved
			allSats := a.tleManager.All()
			var sats []*tle.Satellite
			for _, name := range a.watchlist {
				if s := a.tleManager.Get(name); s != nil {
					sats = append(sats, s)
				}
			}
			if len(sats) == 0 {
				sats = allSats
			}
			positions := a.propagator.AllPositions(sats, time.Now())
			log.Printf("[Loop] watchlist=%d resolved=%d positions=%d", len(a.watchlist), len(sats), len(positions))
			runtime.EventsEmit(ctx, "sat:positions", positions)

			// If tracking active, update doppler + rotor
			if a.trackingOn && a.trackedSat != "" {
				a.updateTracking()
			}
		}
	}
}

func (a *App) updateTracking() {
	sat := a.tleManager.Get(a.trackedSat)
	if sat == nil {
		return
	}
	now := time.Now()
	pos := a.propagator.Position(sat, now)
	if pos == nil {
		return
	}
	obs := a.propagator.ObserverPosition()

	// Doppler: only when globally enabled AND tracking enabled for this sat AND el >= 0
	if a.activeRadio().IsConnected() && pos.Elevation >= 0 && a.dopplerEnabled && a.trackFreqMode {
		downFreq, upFreq := a.dopplerCalc.Correct(pos, obs, now)
		if err := a.activeRadio().SetFrequency(downFreq, upFreq); err != nil {
			log.Printf("[Doppler] SetFrequency error: %v", err)
		}
	}

	// Rotor: only when globally enabled AND tracking enabled for this sat AND el >= 0
	if a.rotorClient.IsConnected() && a.rotorEnabled && a.trackAzimuth && pos.Elevation >= 0 {
		var rotErr error
		if a.rotorAzOnly {
			rotErr = a.rotorClient.SetAzOnly(pos.Azimuth)
		} else {
			rotErr = a.rotorClient.SetAzEl(pos.Azimuth, pos.Elevation)
		}
		if rotErr != nil {
			log.Printf("[Rotor] error: %v", rotErr)
		}
	}
}

// ─── Wails-exposed methods ──────────────────────────────────────────────────

// GetSatelliteList returns all satellite names from loaded TLEs.
func (a *App) GetSatelliteList() []string {
	return a.tleManager.SatelliteNames()
}

// GetPasses returns upcoming passes for a satellite over the next 24h.
func (a *App) GetPasses(satName string, hours float64) []propagator.Pass {
	sat := a.tleManager.Get(satName)
	if sat == nil {
		return nil
	}
	return a.propagator.ComputePasses(sat, time.Now(), hours)
}

// GetCurrentPosition returns az/el/lat/lon for a satellite right now.
func (a *App) GetCurrentPosition(satName string) *propagator.SatPosition {
	sat := a.tleManager.Get(satName)
	if sat == nil {
		return nil
	}
	return a.propagator.Position(sat, time.Now())
}

// SetObserverLocation sets the QTH for pass prediction and Doppler.
func (a *App) SetObserverLocation(lat, lon, altM float64) {
	a.propagator.SetObserver(lat, lon, altM)
	a.dopplerCalc.SetObserver(lat, lon, altM)
}

// SetSatelliteFrequencies configures nominal uplink/downlink for Doppler.
func (a *App) SetSatelliteFrequencies(downHz, upHz float64) {
	a.dopplerCalc.SetNominal(downHz, upHz)
}

// SetSatelliteMode sets the radio mode for satellite operation.
// Called automatically when a frequency is selected in the frontend.
func (a *App) SetSatelliteMode(mode string) {
	radio := a.activeRadio()
	if !radio.IsConnected() {
		return
	}
	var converted string
	if a.radioType == "skycat" {
		converted = skycat.SatModeToSkyCAT(mode)
	} else {
		converted = flexradio.SatModeToFlex(mode)
	}
	if err := radio.SetMode(converted); err != nil {
		log.Printf("[Radio] SetMode error: %v", err)
	}
}

// StartTracking begins active tracking of a satellite.
func (a *App) StartTracking(satName string) string {
	sat := a.tleManager.Get(satName)
	if sat == nil {
		return "Satellite not found: " + satName
	}
	// Reset per-satellite tracking toggles on satellite change
	if a.trackedSat != satName {
		a.trackFreqMode = false
		a.trackAzimuth = false
	}
	a.trackedSat = satName
	a.trackingOn = true
	log.Printf("[Tracking] Started: %s", satName)
	return "OK"
}

// StopTracking stops active tracking.
func (a *App) StopTracking() {
	a.trackingOn = false
	a.trackedSat = ""
	if a.cancelTrack != nil {
		a.cancelTrack()
	}
}

// ConnectFlexRadio connects to the FlexRadio 8600 SmartSDR TCP API.
func (a *App) ConnectFlexRadio(host string, port int) string {
	if err := a.flexRadio.Connect(host, port); err != nil {
		return "Error: " + err.Error()
	}
	// Restore saved slice config and query slices
	go func() {
		time.Sleep(800 * time.Millisecond)
		a.flexRadio.SetSlices(a.savedRxSlice, a.savedTxSlice)
		a.flexRadio.QuerySlices()
	}()
	return "OK"
}

// DisconnectFlexRadio closes the FlexRadio connection.
func (a *App) DisconnectFlexRadio() {
	a.flexRadio.Disconnect()
}

// ConnectRotor connects to PstRotator UDP.
func (a *App) ConnectRotor(host string, port int) string {
	if err := a.rotorClient.Connect(host, port); err != nil {
		return "Error: " + err.Error()
	}
	return "OK"
}

// DisconnectRotor closes the PstRotator connection.
func (a *App) DisconnectRotor() {
	a.rotorClient.Disconnect()
}

// RefreshTLE forces a re-fetch of TLE data.
func (a *App) RefreshTLE() string {
	if err := a.tleManager.FetchAndCache(); err != nil {
		return "Error: " + err.Error()
	}
	runtime.EventsEmit(a.ctx, "tle:loaded", a.tleManager.SatelliteNames())
	return "OK"
}

// SetRotorAzOnly sets azimuth-only mode (true) or az+el mode (false).
func (a *App) SetRotorAzOnly(azOnly bool) {
	a.rotorAzOnly = azOnly
	mode := "Az+El"
	if azOnly {
		mode = "Az only"
	}
	log.Printf("[Rotor] mode: %s", mode)
}

// SetRotorEnabled enables or disables rotator tracking.
func (a *App) SetRotorEnabled(enabled bool) {
	a.rotorEnabled = enabled
	log.Printf("[Rotor] tracking %s", map[bool]string{true: "enabled", false: "disabled"}[enabled])
}

// GetRotorEnabled returns current rotator tracking state.
func (a *App) GetRotorEnabled() bool {
	return a.rotorEnabled
}

// SetTrackFreqMode enables/disables frequency+mode tracking for current satellite.
func (a *App) SetTrackFreqMode(enabled bool) {
	a.trackFreqMode = enabled
	// Reset dead-band so freq is sent immediately when tracking starts
	if enabled {
		a.activeRadio().ResetDeadband()
	}
	log.Printf("[Doppler] Freq/Mode tracking: %v", enabled)
}

// SetTrackAzimuth enables/disables azimuth tracking for current satellite.
func (a *App) SetTrackAzimuth(enabled bool) {
	a.trackAzimuth = enabled
	// Reset rotor dead-band so it moves immediately
	if enabled {
		a.rotorClient.ResetDeadband()
	}
	log.Printf("[Rotor] Azimuth tracking: %v", enabled)
}

// SetDopplerEnabled enables or disables Doppler correction.
func (a *App) SetDopplerEnabled(enabled bool) {
	a.dopplerEnabled = enabled
	log.Printf("[Doppler] %s", map[bool]string{true: "enabled", false: "disabled"}[enabled])
}

// GetDopplerEnabled returns current Doppler state.
func (a *App) GetDopplerEnabled() bool {
	return a.dopplerEnabled
}

// GetFlexRadioStatus returns FlexRadio connection status.
func (a *App) GetFlexRadioStatus() bool {
	return a.flexRadio.IsConnected()
}

// SetRadioType switches between "flex" and "skycat" backends.
func (a *App) SetRadioType(radioType string) {
	a.radioType = radioType
	log.Printf("[Radio] Backend switched to: %s", radioType)
}

// GetRadioType returns the current radio backend type.
func (a *App) GetRadioType() string {
	return a.radioType
}

// ConnectSkyCAT connects to skycatd TCP server.
func (a *App) ConnectSkyCAT(host string, port int) string {
	if err := a.skyCat.Connect(host, port); err != nil {
		return "Error: " + err.Error()
	}
	return "OK"
}

// DisconnectSkyCAT closes the SkyCAT connection.
func (a *App) DisconnectSkyCAT() {
	a.skyCat.Disconnect()
}

// GetSkyCATStatus returns SkyCAT connection status.
func (a *App) GetSkyCATStatus() bool {
	return a.skyCat.IsConnected()
}

// SetSkyCATMode sets the SkyCAT satellite operating mode (Duplex/Split/Simplex).
func (a *App) SetSkyCATMode(mode string) string {
	var m skycat.SatMode
	switch mode {
	case "Split":
		m = skycat.ModeSplit
	case "Simplex":
		m = skycat.ModeSimplex
	default:
		m = skycat.ModeDuplex
	}
	if err := a.skyCat.SetSatMode(m); err != nil {
		return "Error: " + err.Error()
	}
	return "OK"
}

// GetSliceConfig returns current RX/TX slice indices.
func (a *App) GetSliceConfig() map[string]int {
	rx, tx := a.flexRadio.GetSlices()
	return map[string]int{"rx": rx, "tx": tx}
}

// SetSliceConfig sets RX and TX slice indices (0=A, 1=B, ...).
func (a *App) SetSliceConfig(rxIdx, txIdx int) {
	a.savedRxSlice = rxIdx
	a.savedTxSlice = txIdx
	a.flexRadio.SetSlices(rxIdx, txIdx)
	log.Printf("[FlexRadio] Slice config saved: RX=%d TX=%d", rxIdx, txIdx)
}

// GetRotorStatus returns connection status.
func (a *App) GetRotorStatus() bool {
	return a.rotorClient.IsConnected()
}

// GetTLEAge returns the age of the TLE cache in hours.
func (a *App) GetTLEAge() float64 {
	return a.tleManager.AgeHours()
}

// GetGroundtrack returns lat/lon points for the next N minutes of orbit.
func (a *App) GetGroundtrack(satName string, minutes float64) []propagator.GroundtrackPoint {
	sat := a.tleManager.Get(satName)
	if sat == nil {
		return nil
	}
	return a.propagator.ComputeGroundtrack(sat, time.Now(), minutes)
}

// GetWatchlist returns the current list of satellites to display on map.
func (a *App) GetWatchlist() []string {
	return a.watchlist
}

// SetWatchlist sets which satellites to display on the map.
func (a *App) SetWatchlist(names []string) {
	a.watchlist = names
}
