package network

import (
	"fmt"
	"time"

	"github.com/godbus/dbus/v5"
)

func (b *IWDBackend) StartMonitoring(onStateChange func()) error {
	b.onStateChange = onStateChange

	if b.promptBroker != nil {
		agent, err := NewIWDAgent(b.conn, b.promptBroker)
		if err != nil {
			return fmt.Errorf("failed to start IWD agent: %w", err)
		}
		agent.onUserCanceled = b.OnUserCanceledPrompt
		agent.onPromptRetry = b.OnPromptRetry
		b.iwdAgent = agent
	}

	sigChan := make(chan *dbus.Signal, 100)
	b.conn.Signal(sigChan)

	if b.devicePath != "" {
		err := b.conn.AddMatchSignal(
			dbus.WithMatchObjectPath(b.devicePath),
			dbus.WithMatchInterface(dbusPropertiesInterface),
			dbus.WithMatchMember("PropertiesChanged"),
		)
		if err != nil {
			return fmt.Errorf("failed to add device signal match: %w", err)
		}
	}

	if b.stationPath != "" {
		err := b.conn.AddMatchSignal(
			dbus.WithMatchObjectPath(b.stationPath),
			dbus.WithMatchInterface(dbusPropertiesInterface),
			dbus.WithMatchMember("PropertiesChanged"),
		)
		if err != nil {
			return fmt.Errorf("failed to add station signal match: %w", err)
		}
	}

	b.sigWG.Add(1)
	go b.signalHandler(sigChan)

	return nil
}

func (b *IWDBackend) signalHandler(sigChan chan *dbus.Signal) {
	defer b.sigWG.Done()

	for {
		select {
		case <-b.stopChan:
			b.conn.RemoveSignal(sigChan)
			close(sigChan)
			return

		case sig := <-sigChan:
			if sig == nil {
				return
			}

			if sig.Name != dbusPropertiesInterface+".PropertiesChanged" {
				continue
			}

			if len(sig.Body) < 2 {
				continue
			}

			iface, ok := sig.Body[0].(string)
			if !ok {
				continue
			}

			changed, ok := sig.Body[1].(map[string]dbus.Variant)
			if !ok {
				continue
			}

			stateChanged := false

			switch iface {
			case iwdDeviceInterface:
				if sig.Path == b.devicePath {
					if poweredVar, ok := changed["Powered"]; ok {
						if powered, ok := poweredVar.Value().(bool); ok {
							b.stateMutex.Lock()
							if b.state.WiFiEnabled != powered {
								b.state.WiFiEnabled = powered
								stateChanged = true
							}
							b.stateMutex.Unlock()
						}
					}
				}

			case iwdStationInterface:
				if sig.Path == b.stationPath {
					if scanningVar, ok := changed["Scanning"]; ok {
						if scanning, ok := scanningVar.Value().(bool); ok && !scanning {
							networks, err := b.updateWiFiNetworks()
							if err == nil {
								b.stateMutex.Lock()
								b.state.WiFiNetworks = networks
								b.stateMutex.Unlock()
								stateChanged = true
							}

							b.stateMutex.RLock()
							wifiConnected := b.state.WiFiConnected
							b.stateMutex.RUnlock()

							if wifiConnected {
								stationObj := b.conn.Object(iwdBusName, b.stationPath)
								connNetVar, err := stationObj.GetProperty(iwdStationInterface + ".ConnectedNetwork")
								if err == nil && connNetVar.Value() != nil {
									if netPath, ok := connNetVar.Value().(dbus.ObjectPath); ok && netPath != "/" {
										var orderedNetworks [][]dbus.Variant
										err = stationObj.Call(iwdStationInterface+".GetOrderedNetworks", 0).Store(&orderedNetworks)
										if err == nil {
											for _, netData := range orderedNetworks {
												if len(netData) < 2 {
													continue
												}
												currentNetPath, ok := netData[0].Value().(dbus.ObjectPath)
												if !ok || currentNetPath != netPath {
													continue
												}
												signalStrength, ok := netData[1].Value().(int16)
												if !ok {
													continue
												}
												signalDbm := signalStrength / 100
												signal := uint8(signalDbm + 100)
												if signalDbm > 0 {
													signal = 100
												} else if signalDbm < -100 {
													signal = 0
												}
												b.stateMutex.Lock()
												if b.state.WiFiSignal != signal {
													b.state.WiFiSignal = signal
													stateChanged = true
												}
												b.stateMutex.Unlock()
												break
											}
										}
									}
								}
							}
						}
					}

					if stateVar, ok := changed["State"]; ok {
						if state, ok := stateVar.Value().(string); ok {
							b.attemptMutex.RLock()
							att := b.curAttempt
							b.attemptMutex.RUnlock()

							var connPath dbus.ObjectPath
							if v, ok := changed["ConnectedNetwork"]; ok {
								if v.Value() != nil {
									if p, ok := v.Value().(dbus.ObjectPath); ok {
										connPath = p
									}
								}
							}
							if connPath == "" {
								station := b.conn.Object(iwdBusName, b.stationPath)
								if cnVar, err := station.GetProperty(iwdStationInterface + ".ConnectedNetwork"); err == nil && cnVar.Value() != nil {
									cnVar.Store(&connPath)
								}
							}

							b.stateMutex.RLock()
							prevConnected := b.state.WiFiConnected
							prevSSID := b.state.WiFiSSID
							b.stateMutex.RUnlock()

							targetPath := dbus.ObjectPath("")
							if att != nil {
								targetPath = att.netPath
							}

							isTarget := att != nil && targetPath != "" && connPath == targetPath

							if att != nil {
								switch state {
								case "authenticating", "associating", "associated", "roaming":
									att.mu.Lock()
									att.sawAuthish = true
									att.mu.Unlock()
								}
							}

							if att != nil && state == "connected" && isTarget {
								att.mu.Lock()
								if att.connectedAt.IsZero() {
									att.connectedAt = time.Now()
								}
								att.mu.Unlock()
							}

							if att != nil && state == "configuring" {
								att.mu.Lock()
								att.sawIPConfig = true
								att.mu.Unlock()
							}

							switch state {
							case "connected":
								b.stateMutex.Lock()
								b.state.WiFiConnected = true
								b.state.NetworkStatus = StatusWiFi
								b.state.IsConnecting = false
								b.state.ConnectingSSID = ""
								b.state.LastError = ""
								b.stateMutex.Unlock()

								if connPath != "" && connPath != "/" {
									netObj := b.conn.Object(iwdBusName, connPath)
									if nameVar, err := netObj.GetProperty(iwdNetworkInterface + ".Name"); err == nil {
										if name, ok := nameVar.Value().(string); ok {
											b.stateMutex.Lock()
											b.state.WiFiSSID = name
											b.stateMutex.Unlock()
										}
									}
								}

								stateChanged = true

								if att != nil && isTarget {
									go func(attLocal *connectAttempt, tgt dbus.ObjectPath) {
										time.Sleep(3 * time.Second)
										station := b.conn.Object(iwdBusName, b.stationPath)
										var nowState string
										if stVar, err := station.GetProperty(iwdStationInterface + ".State"); err == nil {
											stVar.Store(&nowState)
										}
										var nowConn dbus.ObjectPath
										if cnVar, err := station.GetProperty(iwdStationInterface + ".ConnectedNetwork"); err == nil && cnVar.Value() != nil {
											cnVar.Store(&nowConn)
										}

										if nowState == "connected" && nowConn == tgt {
											b.finalizeAttempt(attLocal, "")
											b.attemptMutex.Lock()
											if b.curAttempt == attLocal {
												b.curAttempt = nil
											}
											b.attemptMutex.Unlock()
										}
									}(att, targetPath)
								}

							case "disconnecting", "disconnected":
								if att != nil {
									wasConnectedToTarget := prevConnected && prevSSID == att.ssid
									if wasConnectedToTarget || isTarget {
										code := b.classifyAttempt(att)
										b.finalizeAttempt(att, code)
										b.attemptMutex.Lock()
										if b.curAttempt == att {
											b.curAttempt = nil
										}
										b.attemptMutex.Unlock()
									}
								}

								b.stateMutex.Lock()
								b.state.WiFiConnected = false
								if state == "disconnected" {
									b.state.NetworkStatus = StatusDisconnected
								}
								b.stateMutex.Unlock()
								stateChanged = true
							}
						}
					}

					if connNetVar, ok := changed["ConnectedNetwork"]; ok {
						if netPath, ok := connNetVar.Value().(dbus.ObjectPath); ok && netPath != "/" {
							netObj := b.conn.Object(iwdBusName, netPath)
							nameVar, err := netObj.GetProperty(iwdNetworkInterface + ".Name")
							if err == nil {
								if name, ok := nameVar.Value().(string); ok {
									b.stateMutex.Lock()
									if b.state.WiFiSSID != name {
										b.state.WiFiSSID = name
										stateChanged = true
									}
									b.stateMutex.Unlock()
								}
							}

							stationObj := b.conn.Object(iwdBusName, b.stationPath)
							var orderedNetworks [][]dbus.Variant
							err = stationObj.Call(iwdStationInterface+".GetOrderedNetworks", 0).Store(&orderedNetworks)
							if err == nil {
								for _, netData := range orderedNetworks {
									if len(netData) < 2 {
										continue
									}
									currentNetPath, ok := netData[0].Value().(dbus.ObjectPath)
									if !ok || currentNetPath != netPath {
										continue
									}
									signalStrength, ok := netData[1].Value().(int16)
									if !ok {
										continue
									}
									signalDbm := signalStrength / 100
									signal := uint8(signalDbm + 100)
									if signalDbm > 0 {
										signal = 100
									} else if signalDbm < -100 {
										signal = 0
									}
									b.stateMutex.Lock()
									if b.state.WiFiSignal != signal {
										b.state.WiFiSignal = signal
										stateChanged = true
									}
									b.stateMutex.Unlock()
									break
								}
							}
						} else {
							b.stateMutex.Lock()
							if b.state.WiFiSSID != "" {
								b.state.WiFiSSID = ""
								b.state.WiFiSignal = 0
								stateChanged = true
							}
							b.stateMutex.Unlock()
						}
					}
				}
			}

			if stateChanged && b.onStateChange != nil {
				b.onStateChange()
			}
		}
	}
}
